7 "github.com/hashicorp/go-multierror"
8 "github.com/hashicorp/hcl"
9 "github.com/hashicorp/hcl/hcl/ast"
10 "github.com/mitchellh/mapstructure"
13 // hclConfigurable is an implementation of configurable that knows
14 // how to turn HCL configuration into a *Config object.
15 type hclConfigurable struct {
20 var ReservedDataSourceFields = []string{
29 var ReservedResourceFields = []string{
39 var ReservedProviderFields = []string{
44 func (t *hclConfigurable) Config() (*Config, error) {
45 validKeys := map[string]struct{}{
51 "provider": struct{}{},
52 "resource": struct{}{},
53 "terraform": struct{}{},
54 "variable": struct{}{},
57 // Top-level item should be the object list
58 list, ok := t.Root.Node.(*ast.ObjectList)
60 return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
63 // Start building up the actual configuration.
67 if o := list.Filter("terraform"); len(o.Items) > 0 {
69 config.Terraform, err = loadTerraformHcl(o)
75 // Build the variables
76 if vars := list.Filter("variable"); len(vars.Items) > 0 {
78 config.Variables, err = loadVariablesHcl(vars)
85 if locals := list.Filter("locals"); len(locals.Items) > 0 {
87 config.Locals, err = loadLocalsHcl(locals)
93 // Get Atlas configuration
94 if atlas := list.Filter("atlas"); len(atlas.Items) > 0 {
96 config.Atlas, err = loadAtlasHcl(atlas)
103 if modules := list.Filter("module"); len(modules.Items) > 0 {
105 config.Modules, err = loadModulesHcl(modules)
111 // Build the provider configs
112 if providers := list.Filter("provider"); len(providers.Items) > 0 {
114 config.ProviderConfigs, err = loadProvidersHcl(providers)
120 // Build the resources
123 managedResourceConfigs := list.Filter("resource")
124 dataResourceConfigs := list.Filter("data")
126 config.Resources = make(
128 len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items),
131 managedResources, err := loadManagedResourcesHcl(managedResourceConfigs)
135 dataResources, err := loadDataResourcesHcl(dataResourceConfigs)
140 config.Resources = append(config.Resources, dataResources...)
141 config.Resources = append(config.Resources, managedResources...)
145 if outputs := list.Filter("output"); len(outputs.Items) > 0 {
147 config.Outputs, err = loadOutputsHcl(outputs)
153 // Check for invalid keys
154 for _, item := range list.Items {
155 if len(item.Keys) == 0 {
156 // Not sure how this would happen, but let's avoid a panic
160 k := item.Keys[0].Token.Value().(string)
161 if _, ok := validKeys[k]; ok {
165 config.unknownKeys = append(config.unknownKeys, k)
171 // loadFileHcl is a fileLoaderFunc that knows how to read HCL
172 // files and turn them into hclConfigurables.
173 func loadFileHcl(root string) (configurable, []string, error) {
174 // Read the HCL file and prepare for parsing
175 d, err := ioutil.ReadFile(root)
177 return nil, nil, fmt.Errorf(
178 "Error reading %s: %s", root, err)
182 hclRoot, err := hcl.Parse(string(d))
184 return nil, nil, fmt.Errorf(
185 "Error parsing %s: %s", root, err)
188 // Start building the result
189 result := &hclConfigurable{
194 // Dive in, find the imports. This is disabled for now since
195 // imports were removed prior to Terraform 0.1. The code is
196 // remaining here commented for historical purposes.
198 imports := obj.Get("import")
201 return result, nil, nil
204 if imports.Type() != libucl.ObjectTypeString {
207 return nil, nil, fmt.Errorf(
208 "Error in %s: all 'import' declarations should be in the format\n"+
209 "`import \"foo\"` (Got type %s)",
214 // Gather all the import paths
215 importPaths := make([]string, 0, imports.Len())
216 iter := imports.Iterate(false)
217 for imp := iter.Next(); imp != nil; imp = iter.Next() {
218 path := imp.ToString()
219 if !filepath.IsAbs(path) {
220 // Relative paths are relative to the Terraform file itself
221 dir := filepath.Dir(root)
222 path = filepath.Join(dir, path)
225 importPaths = append(importPaths, path)
234 return result, nil, nil
237 // Given a handle to a HCL object, this transforms it into the Terraform config
238 func loadTerraformHcl(list *ast.ObjectList) (*Terraform, error) {
239 if len(list.Items) > 1 {
240 return nil, fmt.Errorf("only one 'terraform' block allowed per module")
244 item := list.Items[0]
246 // This block should have an empty top level ObjectItem. If there are keys
247 // here, it's likely because we have a flattened JSON object, and we can
248 // lift this into a nested ObjectList to decode properly.
249 if len(item.Keys) > 0 {
250 item = &ast.ObjectItem{
251 Val: &ast.ObjectType{
252 List: &ast.ObjectList{
253 Items: []*ast.ObjectItem{item},
259 // We need the item value as an ObjectList
260 var listVal *ast.ObjectList
261 if ot, ok := item.Val.(*ast.ObjectType); ok {
264 return nil, fmt.Errorf("terraform block: should be an object")
267 // NOTE: We purposely don't validate unknown HCL keys here so that
268 // we can potentially read _future_ Terraform version config (to
269 // still be able to validate the required version).
271 // We should still keep track of unknown keys to validate later, but
272 // HCL doesn't currently support that.
275 if err := hcl.DecodeObject(&config, item.Val); err != nil {
276 return nil, fmt.Errorf(
277 "Error reading terraform config: %s",
281 // If we have provisioners, then parse those out
282 if os := listVal.Filter("backend"); len(os.Items) > 0 {
284 config.Backend, err = loadTerraformBackendHcl(os)
286 return nil, fmt.Errorf(
287 "Error reading backend config for terraform block: %s",
295 // Loads the Backend configuration from an object list.
296 func loadTerraformBackendHcl(list *ast.ObjectList) (*Backend, error) {
297 if len(list.Items) > 1 {
298 return nil, fmt.Errorf("only one 'backend' block allowed")
302 item := list.Items[0]
305 if len(item.Keys) != 1 {
306 return nil, fmt.Errorf(
307 "position %s: 'backend' must be followed by exactly one string: a type",
311 typ := item.Keys[0].Token.Value().(string)
313 // Decode the raw config
314 var config map[string]interface{}
315 if err := hcl.DecodeObject(&config, item.Val); err != nil {
316 return nil, fmt.Errorf(
317 "Error reading backend config: %s",
321 rawConfig, err := NewRawConfig(config)
323 return nil, fmt.Errorf(
324 "Error reading backend config: %s",
330 RawConfig: rawConfig,
337 // Given a handle to a HCL object, this transforms it into the Atlas
339 func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) {
340 if len(list.Items) > 1 {
341 return nil, fmt.Errorf("only one 'atlas' block allowed")
345 item := list.Items[0]
347 var config AtlasConfig
348 if err := hcl.DecodeObject(&config, item.Val); err != nil {
349 return nil, fmt.Errorf(
350 "Error reading atlas config: %s",
357 // Given a handle to a HCL object, this recurses into the structure
358 // and pulls out a list of modules.
360 // The resulting modules may not be unique, but each module
361 // represents exactly one module definition in the HCL configuration.
362 // We leave it up to another pass to merge them together.
363 func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) {
364 if err := assertAllBlocksHaveNames("module", list); err != nil {
368 list = list.Children()
369 if len(list.Items) == 0 {
373 // Where all the results will go
376 // Now go over all the types and their children in order to get
377 // all of the actual resources.
378 for _, item := range list.Items {
379 k := item.Keys[0].Token.Value().(string)
381 var listVal *ast.ObjectList
382 if ot, ok := item.Val.(*ast.ObjectType); ok {
385 return nil, fmt.Errorf("module '%s': should be an object", k)
388 var config map[string]interface{}
389 if err := hcl.DecodeObject(&config, item.Val); err != nil {
390 return nil, fmt.Errorf(
391 "Error reading config for %s: %s",
396 rawConfig, err := NewRawConfig(config)
398 return nil, fmt.Errorf(
399 "Error reading config for %s: %s",
404 // Remove the fields we handle specially
405 delete(config, "source")
406 delete(config, "version")
407 delete(config, "providers")
410 if o := listVal.Filter("source"); len(o.Items) > 0 {
411 err = hcl.DecodeObject(&source, o.Items[0].Val)
413 return nil, fmt.Errorf(
414 "Error parsing source for %s: %s",
421 if o := listVal.Filter("version"); len(o.Items) > 0 {
422 err = hcl.DecodeObject(&version, o.Items[0].Val)
424 return nil, fmt.Errorf(
425 "Error parsing version for %s: %s",
431 var providers map[string]string
432 if o := listVal.Filter("providers"); len(o.Items) > 0 {
433 err = hcl.DecodeObject(&providers, o.Items[0].Val)
435 return nil, fmt.Errorf(
436 "Error parsing providers for %s: %s",
442 result = append(result, &Module{
446 Providers: providers,
447 RawConfig: rawConfig,
454 // loadLocalsHcl recurses into the given HCL object turns it into
456 func loadLocalsHcl(list *ast.ObjectList) ([]*Local, error) {
458 result := make([]*Local, 0, len(list.Items))
460 for _, block := range list.Items {
461 if len(block.Keys) > 0 {
462 return nil, fmt.Errorf(
463 "locals block at %s should not have label %q",
464 block.Pos(), block.Keys[0].Token.Value(),
468 blockObj, ok := block.Val.(*ast.ObjectType)
470 return nil, fmt.Errorf("locals value at %s should be a block", block.Val.Pos())
473 // blockObj now contains directly our local decls
474 for _, item := range blockObj.List.Items {
475 if len(item.Keys) != 1 {
476 return nil, fmt.Errorf("local declaration at %s may not be a block", item.Val.Pos())
479 // By the time we get here there can only be one item left, but
480 // we'll decode into a map anyway because it's a convenient way
481 // to extract both the key and the value robustly.
482 kv := map[string]interface{}{}
483 hcl.DecodeObject(&kv, item)
484 for k, v := range kv {
485 rawConfig, err := NewRawConfig(map[string]interface{}{
490 return nil, fmt.Errorf(
491 "error parsing local value %q at %s: %s",
492 k, item.Val.Pos(), err,
496 result = append(result, &Local{
498 RawConfig: rawConfig,
507 // LoadOutputsHcl recurses into the given HCL object and turns
508 // it into a mapping of outputs.
509 func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) {
510 if err := assertAllBlocksHaveNames("output", list); err != nil {
514 list = list.Children()
516 // Go through each object and turn it into an actual result.
517 result := make([]*Output, 0, len(list.Items))
518 for _, item := range list.Items {
519 n := item.Keys[0].Token.Value().(string)
521 var listVal *ast.ObjectList
522 if ot, ok := item.Val.(*ast.ObjectType); ok {
525 return nil, fmt.Errorf("output '%s': should be an object", n)
528 var config map[string]interface{}
529 if err := hcl.DecodeObject(&config, item.Val); err != nil {
533 // Delete special keys
534 delete(config, "depends_on")
535 delete(config, "description")
537 rawConfig, err := NewRawConfig(config)
539 return nil, fmt.Errorf(
540 "Error reading config for output %s: %s",
545 // If we have depends fields, then add those in
546 var dependsOn []string
547 if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
548 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
550 return nil, fmt.Errorf(
551 "Error reading depends_on for output %q: %s",
557 // If we have a description field, then filter that
558 var description string
559 if o := listVal.Filter("description"); len(o.Items) > 0 {
560 err := hcl.DecodeObject(&description, o.Items[0].Val)
562 return nil, fmt.Errorf(
563 "Error reading description for output %q: %s",
569 result = append(result, &Output{
571 RawConfig: rawConfig,
572 DependsOn: dependsOn,
573 Description: description,
580 // LoadVariablesHcl recurses into the given HCL object and turns
581 // it into a list of variables.
582 func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) {
583 if err := assertAllBlocksHaveNames("variable", list); err != nil {
587 list = list.Children()
589 // hclVariable is the structure each variable is decoded into
590 type hclVariable struct {
591 DeclaredType string `hcl:"type"`
594 Fields []string `hcl:",decodedFields"`
597 // Go through each object and turn it into an actual result.
598 result := make([]*Variable, 0, len(list.Items))
599 for _, item := range list.Items {
600 // Clean up items from JSON
601 unwrapHCLObjectKeysFromJSON(item, 1)
604 if len(item.Keys) != 1 {
605 return nil, fmt.Errorf(
606 "position %s: 'variable' must be followed by exactly one strings: a name",
610 n := item.Keys[0].Token.Value().(string)
611 if !NameRegexp.MatchString(n) {
612 return nil, fmt.Errorf(
613 "position %s: 'variable' name must match regular expression: %s",
614 item.Pos(), NameRegexp)
617 // Check for invalid keys
618 valid := []string{"type", "default", "description"}
619 if err := checkHCLKeys(item.Val, valid); err != nil {
620 return nil, multierror.Prefix(err, fmt.Sprintf(
624 // Decode into hclVariable to get typed values
625 var hclVar hclVariable
626 if err := hcl.DecodeObject(&hclVar, item.Val); err != nil {
630 // Defaults turn into a slice of map[string]interface{} and
631 // we need to make sure to convert that down into the
632 // proper type for Config.
633 if ms, ok := hclVar.Default.([]map[string]interface{}); ok {
634 def := make(map[string]interface{})
635 for _, m := range ms {
636 for k, v := range m {
644 // Build the new variable and do some basic validation
647 DeclaredType: hclVar.DeclaredType,
648 Default: hclVar.Default,
649 Description: hclVar.Description,
651 if err := newVar.ValidateTypeAndDefault(); err != nil {
655 result = append(result, newVar)
661 // LoadProvidersHcl recurses into the given HCL object and turns
662 // it into a mapping of provider configs.
663 func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) {
664 if err := assertAllBlocksHaveNames("provider", list); err != nil {
668 list = list.Children()
669 if len(list.Items) == 0 {
673 // Go through each object and turn it into an actual result.
674 result := make([]*ProviderConfig, 0, len(list.Items))
675 for _, item := range list.Items {
676 n := item.Keys[0].Token.Value().(string)
678 var listVal *ast.ObjectList
679 if ot, ok := item.Val.(*ast.ObjectType); ok {
682 return nil, fmt.Errorf("module '%s': should be an object", n)
685 var config map[string]interface{}
686 if err := hcl.DecodeObject(&config, item.Val); err != nil {
690 delete(config, "alias")
691 delete(config, "version")
693 rawConfig, err := NewRawConfig(config)
695 return nil, fmt.Errorf(
696 "Error reading config for provider config %s: %s",
701 // If we have an alias field, then add those in
703 if a := listVal.Filter("alias"); len(a.Items) > 0 {
704 err := hcl.DecodeObject(&alias, a.Items[0].Val)
706 return nil, fmt.Errorf(
707 "Error reading alias for provider[%s]: %s",
713 // If we have a version field then extract it
715 if a := listVal.Filter("version"); len(a.Items) > 0 {
716 err := hcl.DecodeObject(&version, a.Items[0].Val)
718 return nil, fmt.Errorf(
719 "Error reading version for provider[%s]: %s",
725 result = append(result, &ProviderConfig{
729 RawConfig: rawConfig,
736 // Given a handle to a HCL object, this recurses into the structure
737 // and pulls out a list of data sources.
739 // The resulting data sources may not be unique, but each one
740 // represents exactly one data definition in the HCL configuration.
741 // We leave it up to another pass to merge them together.
742 func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
743 if err := assertAllBlocksHaveNames("data", list); err != nil {
747 list = list.Children()
748 if len(list.Items) == 0 {
752 // Where all the results will go
753 var result []*Resource
755 // Now go over all the types and their children in order to get
756 // all of the actual resources.
757 for _, item := range list.Items {
758 if len(item.Keys) != 2 {
759 return nil, fmt.Errorf(
760 "position %s: 'data' must be followed by exactly two strings: a type and a name",
764 t := item.Keys[0].Token.Value().(string)
765 k := item.Keys[1].Token.Value().(string)
767 var listVal *ast.ObjectList
768 if ot, ok := item.Val.(*ast.ObjectType); ok {
771 return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k)
774 var config map[string]interface{}
775 if err := hcl.DecodeObject(&config, item.Val); err != nil {
776 return nil, fmt.Errorf(
777 "Error reading config for %s[%s]: %s",
783 // Remove the fields we handle specially
784 delete(config, "depends_on")
785 delete(config, "provider")
786 delete(config, "count")
788 rawConfig, err := NewRawConfig(config)
790 return nil, fmt.Errorf(
791 "Error reading config for %s[%s]: %s",
797 // If we have a count, then figure it out
798 var count string = "1"
799 if o := listVal.Filter("count"); len(o.Items) > 0 {
800 err = hcl.DecodeObject(&count, o.Items[0].Val)
802 return nil, fmt.Errorf(
803 "Error parsing count for %s[%s]: %s",
809 countConfig, err := NewRawConfig(map[string]interface{}{
815 countConfig.Key = "count"
817 // If we have depends fields, then add those in
818 var dependsOn []string
819 if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
820 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
822 return nil, fmt.Errorf(
823 "Error reading depends_on for %s[%s]: %s",
830 // If we have a provider, then parse it out
832 if o := listVal.Filter("provider"); len(o.Items) > 0 {
833 err := hcl.DecodeObject(&provider, o.Items[0].Val)
835 return nil, fmt.Errorf(
836 "Error reading provider for %s[%s]: %s",
843 result = append(result, &Resource{
844 Mode: DataResourceMode,
847 RawCount: countConfig,
848 RawConfig: rawConfig,
850 Provisioners: []*Provisioner{},
851 DependsOn: dependsOn,
852 Lifecycle: ResourceLifecycle{},
859 // Given a handle to a HCL object, this recurses into the structure
860 // and pulls out a list of managed resources.
862 // The resulting resources may not be unique, but each resource
863 // represents exactly one "resource" block in the HCL configuration.
864 // We leave it up to another pass to merge them together.
865 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
866 list = list.Children()
867 if len(list.Items) == 0 {
871 // Where all the results will go
872 var result []*Resource
874 // Now go over all the types and their children in order to get
875 // all of the actual resources.
876 for _, item := range list.Items {
877 // GH-4385: We detect a pure provisioner resource and give the user
878 // an error about how to do it cleanly.
879 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" {
880 return nil, fmt.Errorf(
881 "position %s: provisioners in a resource should be wrapped in a list\n\n"+
882 "Example: \"provisioner\": [ { \"local-exec\": ... } ]",
887 unwrapHCLObjectKeysFromJSON(item, 2)
889 if len(item.Keys) != 2 {
890 return nil, fmt.Errorf(
891 "position %s: resource must be followed by exactly two strings, a type and a name",
895 t := item.Keys[0].Token.Value().(string)
896 k := item.Keys[1].Token.Value().(string)
898 var listVal *ast.ObjectList
899 if ot, ok := item.Val.(*ast.ObjectType); ok {
902 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k)
905 var config map[string]interface{}
906 if err := hcl.DecodeObject(&config, item.Val); err != nil {
907 return nil, fmt.Errorf(
908 "Error reading config for %s[%s]: %s",
914 // Remove the fields we handle specially
915 delete(config, "connection")
916 delete(config, "count")
917 delete(config, "depends_on")
918 delete(config, "provisioner")
919 delete(config, "provider")
920 delete(config, "lifecycle")
922 rawConfig, err := NewRawConfig(config)
924 return nil, fmt.Errorf(
925 "Error reading config for %s[%s]: %s",
931 // If we have a count, then figure it out
932 var count string = "1"
933 if o := listVal.Filter("count"); len(o.Items) > 0 {
934 err = hcl.DecodeObject(&count, o.Items[0].Val)
936 return nil, fmt.Errorf(
937 "Error parsing count for %s[%s]: %s",
943 countConfig, err := NewRawConfig(map[string]interface{}{
949 countConfig.Key = "count"
951 // If we have depends fields, then add those in
952 var dependsOn []string
953 if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
954 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
956 return nil, fmt.Errorf(
957 "Error reading depends_on for %s[%s]: %s",
964 // If we have connection info, then parse those out
965 var connInfo map[string]interface{}
966 if o := listVal.Filter("connection"); len(o.Items) > 0 {
967 err := hcl.DecodeObject(&connInfo, o.Items[0].Val)
969 return nil, fmt.Errorf(
970 "Error reading connection info for %s[%s]: %s",
977 // If we have provisioners, then parse those out
978 var provisioners []*Provisioner
979 if os := listVal.Filter("provisioner"); len(os.Items) > 0 {
981 provisioners, err = loadProvisionersHcl(os, connInfo)
983 return nil, fmt.Errorf(
984 "Error reading provisioners for %s[%s]: %s",
991 // If we have a provider, then parse it out
993 if o := listVal.Filter("provider"); len(o.Items) > 0 {
994 err := hcl.DecodeObject(&provider, o.Items[0].Val)
996 return nil, fmt.Errorf(
997 "Error reading provider for %s[%s]: %s",
1004 // Check if the resource should be re-created before
1005 // destroying the existing instance
1006 var lifecycle ResourceLifecycle
1007 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 {
1008 if len(o.Items) > 1 {
1009 return nil, fmt.Errorf(
1010 "%s[%s]: Multiple lifecycle blocks found, expected one",
1014 // Check for invalid keys
1015 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"}
1016 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil {
1017 return nil, multierror.Prefix(err, fmt.Sprintf(
1021 var raw map[string]interface{}
1022 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil {
1023 return nil, fmt.Errorf(
1024 "Error parsing lifecycle for %s[%s]: %s",
1030 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil {
1031 return nil, fmt.Errorf(
1032 "Error parsing lifecycle for %s[%s]: %s",
1039 result = append(result, &Resource{
1040 Mode: ManagedResourceMode,
1043 RawCount: countConfig,
1044 RawConfig: rawConfig,
1045 Provisioners: provisioners,
1047 DependsOn: dependsOn,
1048 Lifecycle: lifecycle,
1055 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) {
1056 if err := assertAllBlocksHaveNames("provisioner", list); err != nil {
1060 list = list.Children()
1061 if len(list.Items) == 0 {
1065 // Go through each object and turn it into an actual result.
1066 result := make([]*Provisioner, 0, len(list.Items))
1067 for _, item := range list.Items {
1068 n := item.Keys[0].Token.Value().(string)
1070 var listVal *ast.ObjectList
1071 if ot, ok := item.Val.(*ast.ObjectType); ok {
1074 return nil, fmt.Errorf("provisioner '%s': should be an object", n)
1077 var config map[string]interface{}
1078 if err := hcl.DecodeObject(&config, item.Val); err != nil {
1082 // Parse the "when" value
1083 when := ProvisionerWhenCreate
1084 if v, ok := config["when"]; ok {
1087 when = ProvisionerWhenCreate
1089 when = ProvisionerWhenDestroy
1091 return nil, fmt.Errorf(
1092 "position %s: 'provisioner' when must be 'create' or 'destroy'",
1097 // Parse the "on_failure" value
1098 onFailure := ProvisionerOnFailureFail
1099 if v, ok := config["on_failure"]; ok {
1102 onFailure = ProvisionerOnFailureContinue
1104 onFailure = ProvisionerOnFailureFail
1106 return nil, fmt.Errorf(
1107 "position %s: 'provisioner' on_failure must be 'continue' or 'fail'",
1112 // Delete fields we special case
1113 delete(config, "connection")
1114 delete(config, "when")
1115 delete(config, "on_failure")
1117 rawConfig, err := NewRawConfig(config)
1122 // Check if we have a provisioner-level connection
1123 // block that overrides the resource-level
1124 var subConnInfo map[string]interface{}
1125 if o := listVal.Filter("connection"); len(o.Items) > 0 {
1126 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val)
1132 // Inherit from the resource connInfo any keys
1133 // that are not explicitly overriden.
1134 if connInfo != nil && subConnInfo != nil {
1135 for k, v := range connInfo {
1136 if _, ok := subConnInfo[k]; !ok {
1140 } else if subConnInfo == nil {
1141 subConnInfo = connInfo
1144 // Parse the connInfo
1145 connRaw, err := NewRawConfig(subConnInfo)
1150 result = append(result, &Provisioner{
1152 RawConfig: rawConfig,
1155 OnFailure: onFailure,
1163 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode {
1164 objects := make(map[string][]*hclobj.Object)
1166 for _, o := range os.Elem(false) {
1167 for _, elem := range o.Elem(true) {
1168 val, ok := objects[elem.Key]
1170 val = make([]*hclobj.Object, 0, 1)
1173 val = append(val, elem)
1174 objects[elem.Key] = val
1182 // assertAllBlocksHaveNames returns an error if any of the items in
1183 // the given object list are blocks without keys (like "module {}")
1184 // or simple assignments (like "module = 1"). It returns nil if
1185 // neither of these things are true.
1187 // The given name is used in any generated error messages, and should
1188 // be the name of the block we're dealing with. The given list should
1189 // be the result of calling .Filter on an object list with that same
1191 func assertAllBlocksHaveNames(name string, list *ast.ObjectList) error {
1192 if elem := list.Elem(); len(elem.Items) != 0 {
1193 switch et := elem.Items[0].Val.(type) {
1194 case *ast.ObjectType:
1196 return fmt.Errorf("%s: %q must be followed by a name", pos, name)
1198 pos := elem.Items[0].Val.Pos()
1199 return fmt.Errorf("%s: %q must be a configuration block", pos, name)
1205 func checkHCLKeys(node ast.Node, valid []string) error {
1206 var list *ast.ObjectList
1207 switch n := node.(type) {
1208 case *ast.ObjectList:
1210 case *ast.ObjectType:
1213 return fmt.Errorf("cannot check HCL keys of type %T", n)
1216 validMap := make(map[string]struct{}, len(valid))
1217 for _, v := range valid {
1218 validMap[v] = struct{}{}
1222 for _, item := range list.Items {
1223 key := item.Keys[0].Token.Value().(string)
1224 if _, ok := validMap[key]; !ok {
1225 result = multierror.Append(result, fmt.Errorf(
1226 "invalid key: %s", key))
1233 // unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when
1234 // parsing JSON as input: if we're parsing JSON then directly nested
1235 // items will show up as additional "keys".
1237 // For objects that expect a fixed number of keys, this breaks the
1238 // decoding process. This function unwraps the object into what it would've
1239 // looked like if it came directly from HCL by specifying the number of keys
1244 // { "foo": { "baz": {} } }
1246 // Will show up with Keys being: []string{"foo", "baz"}
1247 // when we really just want the first two. This function will fix this.
1248 func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) {
1249 if len(item.Keys) > depth && item.Keys[0].Token.JSON {
1250 for len(item.Keys) > depth {
1251 // Pop off the last key
1253 key := item.Keys[n-1]
1254 item.Keys[n-1] = nil
1255 item.Keys = item.Keys[:n-1]
1257 // Wrap our value in a list
1258 item.Val = &ast.ObjectType{
1259 List: &ast.ObjectList{
1260 Items: []*ast.ObjectItem{
1262 Keys: []*ast.ObjectKey{key},