232 lines
6.6 KiB
Go
232 lines
6.6 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
// EvalValidateError is the error structure returned if there were
|
|
// validation errors.
|
|
type EvalValidateError struct {
|
|
Warnings []string
|
|
Errors []error
|
|
}
|
|
|
|
func (e *EvalValidateError) Error() string {
|
|
return fmt.Sprintf("Warnings: %s. Errors: %s", e.Warnings, e.Errors)
|
|
}
|
|
|
|
// EvalValidateCount is an EvalNode implementation that validates
|
|
// the count of a resource.
|
|
type EvalValidateCount struct {
|
|
Resource *config.Resource
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalValidateCount) Eval(ctx EvalContext) (interface{}, error) {
|
|
var count int
|
|
var errs []error
|
|
var err error
|
|
if _, err := ctx.Interpolate(n.Resource.RawCount, nil); err != nil {
|
|
errs = append(errs, fmt.Errorf(
|
|
"Failed to interpolate count: %s", err))
|
|
goto RETURN
|
|
}
|
|
|
|
count, err = n.Resource.Count()
|
|
if err != nil {
|
|
// If we can't get the count during validation, then
|
|
// just replace it with the number 1.
|
|
c := n.Resource.RawCount.Config()
|
|
c[n.Resource.RawCount.Key] = "1"
|
|
count = 1
|
|
}
|
|
err = nil
|
|
|
|
if count < 0 {
|
|
errs = append(errs, fmt.Errorf(
|
|
"Count is less than zero: %d", count))
|
|
}
|
|
|
|
RETURN:
|
|
if len(errs) != 0 {
|
|
err = &EvalValidateError{
|
|
Errors: errs,
|
|
}
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// EvalValidateProvider is an EvalNode implementation that validates
|
|
// the configuration of a resource.
|
|
type EvalValidateProvider struct {
|
|
Provider *ResourceProvider
|
|
Config **ResourceConfig
|
|
}
|
|
|
|
func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) {
|
|
provider := *n.Provider
|
|
config := *n.Config
|
|
|
|
warns, errs := provider.Validate(config)
|
|
if len(warns) == 0 && len(errs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, &EvalValidateError{
|
|
Warnings: warns,
|
|
Errors: errs,
|
|
}
|
|
}
|
|
|
|
// EvalValidateProvisioner is an EvalNode implementation that validates
|
|
// the configuration of a resource.
|
|
type EvalValidateProvisioner struct {
|
|
Provisioner *ResourceProvisioner
|
|
Config **ResourceConfig
|
|
ConnConfig **ResourceConfig
|
|
}
|
|
|
|
func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) {
|
|
provisioner := *n.Provisioner
|
|
config := *n.Config
|
|
var warns []string
|
|
var errs []error
|
|
|
|
{
|
|
// Validate the provisioner's own config first
|
|
w, e := provisioner.Validate(config)
|
|
warns = append(warns, w...)
|
|
errs = append(errs, e...)
|
|
}
|
|
|
|
{
|
|
// Now validate the connection config, which might either be from
|
|
// the provisioner block itself or inherited from the resource's
|
|
// shared connection info.
|
|
w, e := n.validateConnConfig(*n.ConnConfig)
|
|
warns = append(warns, w...)
|
|
errs = append(errs, e...)
|
|
}
|
|
|
|
if len(warns) == 0 && len(errs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, &EvalValidateError{
|
|
Warnings: warns,
|
|
Errors: errs,
|
|
}
|
|
}
|
|
|
|
func (n *EvalValidateProvisioner) validateConnConfig(connConfig *ResourceConfig) (warns []string, errs []error) {
|
|
// We can't comprehensively validate the connection config since its
|
|
// final structure is decided by the communicator and we can't instantiate
|
|
// that until we have a complete instance state. However, we *can* catch
|
|
// configuration keys that are not valid for *any* communicator, catching
|
|
// typos early rather than waiting until we actually try to run one of
|
|
// the resource's provisioners.
|
|
|
|
type connConfigSuperset struct {
|
|
// All attribute types are interface{} here because at this point we
|
|
// may still have unresolved interpolation expressions, which will
|
|
// appear as strings regardless of the final goal type.
|
|
|
|
Type interface{} `mapstructure:"type"`
|
|
User interface{} `mapstructure:"user"`
|
|
Password interface{} `mapstructure:"password"`
|
|
Host interface{} `mapstructure:"host"`
|
|
Port interface{} `mapstructure:"port"`
|
|
Timeout interface{} `mapstructure:"timeout"`
|
|
ScriptPath interface{} `mapstructure:"script_path"`
|
|
|
|
// For type=ssh only (enforced in ssh communicator)
|
|
PrivateKey interface{} `mapstructure:"private_key"`
|
|
HostKey interface{} `mapstructure:"host_key"`
|
|
Agent interface{} `mapstructure:"agent"`
|
|
BastionHost interface{} `mapstructure:"bastion_host"`
|
|
BastionHostKey interface{} `mapstructure:"bastion_host_key"`
|
|
BastionPort interface{} `mapstructure:"bastion_port"`
|
|
BastionUser interface{} `mapstructure:"bastion_user"`
|
|
BastionPassword interface{} `mapstructure:"bastion_password"`
|
|
BastionPrivateKey interface{} `mapstructure:"bastion_private_key"`
|
|
AgentIdentity interface{} `mapstructure:"agent_identity"`
|
|
|
|
// For type=winrm only (enforced in winrm communicator)
|
|
HTTPS interface{} `mapstructure:"https"`
|
|
Insecure interface{} `mapstructure:"insecure"`
|
|
NTLM interface{} `mapstructure:"use_ntlm"`
|
|
CACert interface{} `mapstructure:"cacert"`
|
|
}
|
|
|
|
var metadata mapstructure.Metadata
|
|
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
Metadata: &metadata,
|
|
Result: &connConfigSuperset{}, // result is disregarded; we only care about unused keys
|
|
})
|
|
if err != nil {
|
|
// should never happen
|
|
errs = append(errs, err)
|
|
return
|
|
}
|
|
|
|
if err := decoder.Decode(connConfig.Config); err != nil {
|
|
errs = append(errs, err)
|
|
return
|
|
}
|
|
|
|
for _, attrName := range metadata.Unused {
|
|
errs = append(errs, fmt.Errorf("unknown 'connection' argument %q", attrName))
|
|
}
|
|
return
|
|
}
|
|
|
|
// EvalValidateResource is an EvalNode implementation that validates
|
|
// the configuration of a resource.
|
|
type EvalValidateResource struct {
|
|
Provider *ResourceProvider
|
|
Config **ResourceConfig
|
|
ResourceName string
|
|
ResourceType string
|
|
ResourceMode config.ResourceMode
|
|
|
|
// IgnoreWarnings means that warnings will not be passed through. This allows
|
|
// "just-in-time" passes of validation to continue execution through warnings.
|
|
IgnoreWarnings bool
|
|
}
|
|
|
|
func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) {
|
|
provider := *n.Provider
|
|
cfg := *n.Config
|
|
var warns []string
|
|
var errs []error
|
|
// Provider entry point varies depending on resource mode, because
|
|
// managed resources and data resources are two distinct concepts
|
|
// in the provider abstraction.
|
|
switch n.ResourceMode {
|
|
case config.ManagedResourceMode:
|
|
warns, errs = provider.ValidateResource(n.ResourceType, cfg)
|
|
case config.DataResourceMode:
|
|
warns, errs = provider.ValidateDataSource(n.ResourceType, cfg)
|
|
}
|
|
|
|
// If the resource name doesn't match the name regular
|
|
// expression, show an error.
|
|
if !config.NameRegexp.Match([]byte(n.ResourceName)) {
|
|
errs = append(errs, fmt.Errorf(
|
|
"%s: resource name can only contain letters, numbers, "+
|
|
"dashes, and underscores.", n.ResourceName))
|
|
}
|
|
|
|
if (len(warns) == 0 || n.IgnoreWarnings) && len(errs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, &EvalValidateError{
|
|
Warnings: warns,
|
|
Errors: errs,
|
|
}
|
|
}
|