github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/configs/module_call.go (about) 1 package configs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hashicorp/hcl/v2/gohcl" 8 "github.com/hashicorp/hcl/v2/hclsyntax" 9 ) 10 11 // ModuleCall represents a "module" block in a module or file. 12 type ModuleCall struct { 13 Name string 14 15 SourceAddr string 16 SourceAddrRange hcl.Range 17 SourceSet bool 18 19 Config hcl.Body 20 21 Version VersionConstraint 22 23 Count hcl.Expression 24 ForEach hcl.Expression 25 26 Providers []PassedProviderConfig 27 28 DependsOn []hcl.Traversal 29 30 DeclRange hcl.Range 31 } 32 33 func decodeModuleBlock(block *hcl.Block, override bool) (*ModuleCall, hcl.Diagnostics) { 34 var diags hcl.Diagnostics 35 36 mc := &ModuleCall{ 37 Name: block.Labels[0], 38 DeclRange: block.DefRange, 39 } 40 41 schema := moduleBlockSchema 42 if override { 43 schema = schemaForOverrides(schema) 44 } 45 46 content, remain, moreDiags := block.Body.PartialContent(schema) 47 diags = append(diags, moreDiags...) 48 mc.Config = remain 49 50 if !hclsyntax.ValidIdentifier(mc.Name) { 51 diags = append(diags, &hcl.Diagnostic{ 52 Severity: hcl.DiagError, 53 Summary: "Invalid module instance name", 54 Detail: badIdentifierDetail, 55 Subject: &block.LabelRanges[0], 56 }) 57 } 58 59 if attr, exists := content.Attributes["source"]; exists { 60 valDiags := gohcl.DecodeExpression(attr.Expr, nil, &mc.SourceAddr) 61 diags = append(diags, valDiags...) 62 mc.SourceAddrRange = attr.Expr.Range() 63 mc.SourceSet = true 64 } 65 66 if attr, exists := content.Attributes["version"]; exists { 67 var versionDiags hcl.Diagnostics 68 mc.Version, versionDiags = decodeVersionConstraint(attr) 69 diags = append(diags, versionDiags...) 70 } 71 72 if attr, exists := content.Attributes["count"]; exists { 73 mc.Count = attr.Expr 74 } 75 76 if attr, exists := content.Attributes["for_each"]; exists { 77 if mc.Count != nil { 78 diags = append(diags, &hcl.Diagnostic{ 79 Severity: hcl.DiagError, 80 Summary: `Invalid combination of "count" and "for_each"`, 81 Detail: `The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created.`, 82 Subject: &attr.NameRange, 83 }) 84 } 85 86 mc.ForEach = attr.Expr 87 } 88 89 if attr, exists := content.Attributes["depends_on"]; exists { 90 deps, depsDiags := decodeDependsOn(attr) 91 diags = append(diags, depsDiags...) 92 mc.DependsOn = append(mc.DependsOn, deps...) 93 } 94 95 if attr, exists := content.Attributes["providers"]; exists { 96 seen := make(map[string]hcl.Range) 97 pairs, pDiags := hcl.ExprMap(attr.Expr) 98 diags = append(diags, pDiags...) 99 for _, pair := range pairs { 100 key, keyDiags := decodeProviderConfigRef(pair.Key, "providers") 101 diags = append(diags, keyDiags...) 102 value, valueDiags := decodeProviderConfigRef(pair.Value, "providers") 103 diags = append(diags, valueDiags...) 104 if keyDiags.HasErrors() || valueDiags.HasErrors() { 105 continue 106 } 107 108 matchKey := key.String() 109 if prev, exists := seen[matchKey]; exists { 110 diags = append(diags, &hcl.Diagnostic{ 111 Severity: hcl.DiagError, 112 Summary: "Duplicate provider address", 113 Detail: fmt.Sprintf("A provider configuration was already passed to %s at %s. Each child provider configuration can be assigned only once.", matchKey, prev), 114 Subject: pair.Value.Range().Ptr(), 115 }) 116 continue 117 } 118 119 rng := hcl.RangeBetween(pair.Key.Range(), pair.Value.Range()) 120 seen[matchKey] = rng 121 mc.Providers = append(mc.Providers, PassedProviderConfig{ 122 InChild: key, 123 InParent: value, 124 }) 125 } 126 } 127 128 var seenEscapeBlock *hcl.Block 129 for _, block := range content.Blocks { 130 switch block.Type { 131 case "_": 132 if seenEscapeBlock != nil { 133 diags = append(diags, &hcl.Diagnostic{ 134 Severity: hcl.DiagError, 135 Summary: "Duplicate escaping block", 136 Detail: fmt.Sprintf( 137 "The special block type \"_\" can be used to force particular arguments to be interpreted as module input variables rather than as meta-arguments, but each module block can have only one such block. The first escaping block was at %s.", 138 seenEscapeBlock.DefRange, 139 ), 140 Subject: &block.DefRange, 141 }) 142 continue 143 } 144 seenEscapeBlock = block 145 146 // When there's an escaping block its content merges with the 147 // existing config we extracted earlier, so later decoding 148 // will see a blend of both. 149 mc.Config = hcl.MergeBodies([]hcl.Body{mc.Config, block.Body}) 150 151 default: 152 // All of the other block types in our schema are reserved. 153 diags = append(diags, &hcl.Diagnostic{ 154 Severity: hcl.DiagError, 155 Summary: "Reserved block type name in module block", 156 Detail: fmt.Sprintf("The block type name %q is reserved for use by Terraform in a future version.", block.Type), 157 Subject: &block.TypeRange, 158 }) 159 } 160 } 161 162 return mc, diags 163 } 164 165 // PassedProviderConfig represents a provider config explicitly passed down to 166 // a child module, possibly giving it a new local address in the process. 167 type PassedProviderConfig struct { 168 InChild *ProviderConfigRef 169 InParent *ProviderConfigRef 170 } 171 172 var moduleBlockSchema = &hcl.BodySchema{ 173 Attributes: []hcl.AttributeSchema{ 174 { 175 Name: "source", 176 Required: true, 177 }, 178 { 179 Name: "version", 180 }, 181 { 182 Name: "count", 183 }, 184 { 185 Name: "for_each", 186 }, 187 { 188 Name: "depends_on", 189 }, 190 { 191 Name: "providers", 192 }, 193 }, 194 Blocks: []hcl.BlockHeaderSchema{ 195 {Type: "_"}, // meta-argument escaping block 196 197 // These are all reserved for future use. 198 {Type: "lifecycle"}, 199 {Type: "locals"}, 200 {Type: "provider", LabelNames: []string{"type"}}, 201 }, 202 }