github.com/LorbusChris/terraform@v0.11.12-beta1/terraform/eval_validate.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/terraform/config" 7 "github.com/mitchellh/mapstructure" 8 ) 9 10 // EvalValidateError is the error structure returned if there were 11 // validation errors. 12 type EvalValidateError struct { 13 Warnings []string 14 Errors []error 15 } 16 17 func (e *EvalValidateError) Error() string { 18 return fmt.Sprintf("Warnings: %s. Errors: %s", e.Warnings, e.Errors) 19 } 20 21 // EvalValidateCount is an EvalNode implementation that validates 22 // the count of a resource. 23 type EvalValidateCount struct { 24 Resource *config.Resource 25 } 26 27 // TODO: test 28 func (n *EvalValidateCount) Eval(ctx EvalContext) (interface{}, error) { 29 var count int 30 var errs []error 31 var err error 32 if _, err := ctx.Interpolate(n.Resource.RawCount, nil); err != nil { 33 errs = append(errs, fmt.Errorf( 34 "Failed to interpolate count: %s", err)) 35 goto RETURN 36 } 37 38 count, err = n.Resource.Count() 39 if err != nil { 40 // If we can't get the count during validation, then 41 // just replace it with the number 1. 42 c := n.Resource.RawCount.Config() 43 c[n.Resource.RawCount.Key] = "1" 44 count = 1 45 } 46 err = nil 47 48 if count < 0 { 49 errs = append(errs, fmt.Errorf( 50 "Count is less than zero: %d", count)) 51 } 52 53 RETURN: 54 if len(errs) != 0 { 55 err = &EvalValidateError{ 56 Errors: errs, 57 } 58 } 59 return nil, err 60 } 61 62 // EvalValidateProvider is an EvalNode implementation that validates 63 // the configuration of a resource. 64 type EvalValidateProvider struct { 65 Provider *ResourceProvider 66 Config **ResourceConfig 67 } 68 69 func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) { 70 provider := *n.Provider 71 config := *n.Config 72 73 warns, errs := provider.Validate(config) 74 if len(warns) == 0 && len(errs) == 0 { 75 return nil, nil 76 } 77 78 return nil, &EvalValidateError{ 79 Warnings: warns, 80 Errors: errs, 81 } 82 } 83 84 // EvalValidateProvisioner is an EvalNode implementation that validates 85 // the configuration of a resource. 86 type EvalValidateProvisioner struct { 87 Provisioner *ResourceProvisioner 88 Config **ResourceConfig 89 ConnConfig **ResourceConfig 90 } 91 92 func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) { 93 provisioner := *n.Provisioner 94 config := *n.Config 95 var warns []string 96 var errs []error 97 98 { 99 // Validate the provisioner's own config first 100 w, e := provisioner.Validate(config) 101 warns = append(warns, w...) 102 errs = append(errs, e...) 103 } 104 105 { 106 // Now validate the connection config, which might either be from 107 // the provisioner block itself or inherited from the resource's 108 // shared connection info. 109 w, e := n.validateConnConfig(*n.ConnConfig) 110 warns = append(warns, w...) 111 errs = append(errs, e...) 112 } 113 114 if len(warns) == 0 && len(errs) == 0 { 115 return nil, nil 116 } 117 118 return nil, &EvalValidateError{ 119 Warnings: warns, 120 Errors: errs, 121 } 122 } 123 124 func (n *EvalValidateProvisioner) validateConnConfig(connConfig *ResourceConfig) (warns []string, errs []error) { 125 // We can't comprehensively validate the connection config since its 126 // final structure is decided by the communicator and we can't instantiate 127 // that until we have a complete instance state. However, we *can* catch 128 // configuration keys that are not valid for *any* communicator, catching 129 // typos early rather than waiting until we actually try to run one of 130 // the resource's provisioners. 131 132 type connConfigSuperset struct { 133 // All attribute types are interface{} here because at this point we 134 // may still have unresolved interpolation expressions, which will 135 // appear as strings regardless of the final goal type. 136 137 Type interface{} `mapstructure:"type"` 138 User interface{} `mapstructure:"user"` 139 Password interface{} `mapstructure:"password"` 140 Host interface{} `mapstructure:"host"` 141 Port interface{} `mapstructure:"port"` 142 Timeout interface{} `mapstructure:"timeout"` 143 ScriptPath interface{} `mapstructure:"script_path"` 144 145 // For type=ssh only (enforced in ssh communicator) 146 PrivateKey interface{} `mapstructure:"private_key"` 147 HostKey interface{} `mapstructure:"host_key"` 148 Agent interface{} `mapstructure:"agent"` 149 BastionHost interface{} `mapstructure:"bastion_host"` 150 BastionHostKey interface{} `mapstructure:"bastion_host_key"` 151 BastionPort interface{} `mapstructure:"bastion_port"` 152 BastionUser interface{} `mapstructure:"bastion_user"` 153 BastionPassword interface{} `mapstructure:"bastion_password"` 154 BastionPrivateKey interface{} `mapstructure:"bastion_private_key"` 155 AgentIdentity interface{} `mapstructure:"agent_identity"` 156 157 // For type=winrm only (enforced in winrm communicator) 158 HTTPS interface{} `mapstructure:"https"` 159 Insecure interface{} `mapstructure:"insecure"` 160 NTLM interface{} `mapstructure:"use_ntlm"` 161 CACert interface{} `mapstructure:"cacert"` 162 } 163 164 var metadata mapstructure.Metadata 165 decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 166 Metadata: &metadata, 167 Result: &connConfigSuperset{}, // result is disregarded; we only care about unused keys 168 }) 169 if err != nil { 170 // should never happen 171 errs = append(errs, err) 172 return 173 } 174 175 if err := decoder.Decode(connConfig.Config); err != nil { 176 errs = append(errs, err) 177 return 178 } 179 180 for _, attrName := range metadata.Unused { 181 errs = append(errs, fmt.Errorf("unknown 'connection' argument %q", attrName)) 182 } 183 return 184 } 185 186 // EvalValidateResource is an EvalNode implementation that validates 187 // the configuration of a resource. 188 type EvalValidateResource struct { 189 Provider *ResourceProvider 190 Config **ResourceConfig 191 ResourceName string 192 ResourceType string 193 ResourceMode config.ResourceMode 194 195 // IgnoreWarnings means that warnings will not be passed through. This allows 196 // "just-in-time" passes of validation to continue execution through warnings. 197 IgnoreWarnings bool 198 } 199 200 func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) { 201 provider := *n.Provider 202 cfg := *n.Config 203 var warns []string 204 var errs []error 205 // Provider entry point varies depending on resource mode, because 206 // managed resources and data resources are two distinct concepts 207 // in the provider abstraction. 208 switch n.ResourceMode { 209 case config.ManagedResourceMode: 210 warns, errs = provider.ValidateResource(n.ResourceType, cfg) 211 case config.DataResourceMode: 212 warns, errs = provider.ValidateDataSource(n.ResourceType, cfg) 213 } 214 215 // If the resource name doesn't match the name regular 216 // expression, show an error. 217 if !config.NameRegexp.Match([]byte(n.ResourceName)) { 218 errs = append(errs, fmt.Errorf( 219 "%s: resource name can only contain letters, numbers, "+ 220 "dashes, and underscores.", n.ResourceName)) 221 } 222 223 if (len(warns) == 0 || n.IgnoreWarnings) && len(errs) == 0 { 224 return nil, nil 225 } 226 227 return nil, &EvalValidateError{ 228 Warnings: warns, 229 Errors: errs, 230 } 231 }