github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/configs/parser_config.go (about) 1 package configs 2 3 import ( 4 "github.com/hashicorp/hcl/v2" 5 ) 6 7 // LoadConfigFile reads the file at the given path and parses it as a config 8 // file. 9 // 10 // If the file cannot be read -- for example, if it does not exist -- then 11 // a nil *File will be returned along with error diagnostics. Callers may wish 12 // to disregard the returned diagnostics in this case and instead generate 13 // their own error message(s) with additional context. 14 // 15 // If the returned diagnostics has errors when a non-nil map is returned 16 // then the map may be incomplete but should be valid enough for careful 17 // static analysis. 18 // 19 // This method wraps LoadHCLFile, and so it inherits the syntax selection 20 // behaviors documented for that method. 21 func (p *Parser) LoadConfigFile(path string) (*File, hcl.Diagnostics) { 22 return p.loadConfigFile(path, false) 23 } 24 25 // LoadConfigFileOverride is the same as LoadConfigFile except that it relaxes 26 // certain required attribute constraints in order to interpret the given 27 // file as an overrides file. 28 func (p *Parser) LoadConfigFileOverride(path string) (*File, hcl.Diagnostics) { 29 return p.loadConfigFile(path, true) 30 } 31 32 func (p *Parser) loadConfigFile(path string, override bool) (*File, hcl.Diagnostics) { 33 34 body, diags := p.LoadHCLFile(path) 35 if body == nil { 36 return nil, diags 37 } 38 39 file := &File{} 40 41 var reqDiags hcl.Diagnostics 42 file.CoreVersionConstraints, reqDiags = sniffCoreVersionRequirements(body) 43 diags = append(diags, reqDiags...) 44 45 // We'll load the experiments first because other decoding logic in the 46 // loop below might depend on these experiments. 47 var expDiags hcl.Diagnostics 48 file.ActiveExperiments, expDiags = sniffActiveExperiments(body) 49 diags = append(diags, expDiags...) 50 51 content, contentDiags := body.Content(configFileSchema) 52 diags = append(diags, contentDiags...) 53 54 for _, block := range content.Blocks { 55 switch block.Type { 56 57 case "terraform": 58 content, contentDiags := block.Body.Content(terraformBlockSchema) 59 diags = append(diags, contentDiags...) 60 61 // We ignore the "terraform_version", "language" and "experiments" 62 // attributes here because sniffCoreVersionRequirements and 63 // sniffActiveExperiments already dealt with those above. 64 65 for _, innerBlock := range content.Blocks { 66 switch innerBlock.Type { 67 68 case "backend": 69 backendCfg, cfgDiags := decodeBackendBlock(innerBlock) 70 diags = append(diags, cfgDiags...) 71 if backendCfg != nil { 72 file.Backends = append(file.Backends, backendCfg) 73 } 74 75 case "cloud": 76 cloudCfg, cfgDiags := decodeCloudBlock(innerBlock) 77 diags = append(diags, cfgDiags...) 78 if cloudCfg != nil { 79 file.CloudConfigs = append(file.CloudConfigs, cloudCfg) 80 } 81 82 case "required_providers": 83 reqs, reqsDiags := decodeRequiredProvidersBlock(innerBlock) 84 diags = append(diags, reqsDiags...) 85 file.RequiredProviders = append(file.RequiredProviders, reqs) 86 87 case "provider_meta": 88 providerCfg, cfgDiags := decodeProviderMetaBlock(innerBlock) 89 diags = append(diags, cfgDiags...) 90 if providerCfg != nil { 91 file.ProviderMetas = append(file.ProviderMetas, providerCfg) 92 } 93 94 default: 95 // Should never happen because the above cases should be exhaustive 96 // for all block type names in our schema. 97 continue 98 99 } 100 } 101 102 case "required_providers": 103 // required_providers should be nested inside a "terraform" block 104 diags = append(diags, &hcl.Diagnostic{ 105 Severity: hcl.DiagError, 106 Summary: "Invalid required_providers block", 107 Detail: "A \"required_providers\" block must be nested inside a \"terraform\" block.", 108 Subject: block.TypeRange.Ptr(), 109 }) 110 111 case "provider": 112 cfg, cfgDiags := decodeProviderBlock(block) 113 diags = append(diags, cfgDiags...) 114 if cfg != nil { 115 file.ProviderConfigs = append(file.ProviderConfigs, cfg) 116 } 117 118 case "variable": 119 cfg, cfgDiags := decodeVariableBlock(block, override) 120 diags = append(diags, cfgDiags...) 121 if cfg != nil { 122 file.Variables = append(file.Variables, cfg) 123 } 124 125 case "locals": 126 defs, defsDiags := decodeLocalsBlock(block) 127 diags = append(diags, defsDiags...) 128 file.Locals = append(file.Locals, defs...) 129 130 case "output": 131 cfg, cfgDiags := decodeOutputBlock(block, override) 132 diags = append(diags, cfgDiags...) 133 if cfg != nil { 134 file.Outputs = append(file.Outputs, cfg) 135 } 136 137 case "module": 138 cfg, cfgDiags := decodeModuleBlock(block, override) 139 diags = append(diags, cfgDiags...) 140 if cfg != nil { 141 file.ModuleCalls = append(file.ModuleCalls, cfg) 142 } 143 144 case "resource": 145 cfg, cfgDiags := decodeResourceBlock(block) 146 diags = append(diags, cfgDiags...) 147 if cfg != nil { 148 file.ManagedResources = append(file.ManagedResources, cfg) 149 } 150 151 case "data": 152 cfg, cfgDiags := decodeDataBlock(block) 153 diags = append(diags, cfgDiags...) 154 if cfg != nil { 155 file.DataResources = append(file.DataResources, cfg) 156 } 157 158 case "moved": 159 cfg, cfgDiags := decodeMovedBlock(block) 160 diags = append(diags, cfgDiags...) 161 if cfg != nil { 162 file.Moved = append(file.Moved, cfg) 163 } 164 165 default: 166 // Should never happen because the above cases should be exhaustive 167 // for all block type names in our schema. 168 continue 169 170 } 171 } 172 173 return file, diags 174 } 175 176 // sniffCoreVersionRequirements does minimal parsing of the given body for 177 // "terraform" blocks with "required_version" attributes, returning the 178 // requirements found. 179 // 180 // This is intended to maximize the chance that we'll be able to read the 181 // requirements (syntax errors notwithstanding) even if the config file contains 182 // constructs that might've been added in future Terraform versions 183 // 184 // This is a "best effort" sort of method which will return constraints it is 185 // able to find, but may return no constraints at all if the given body is 186 // so invalid that it cannot be decoded at all. 187 func sniffCoreVersionRequirements(body hcl.Body) ([]VersionConstraint, hcl.Diagnostics) { 188 rootContent, _, diags := body.PartialContent(configFileTerraformBlockSniffRootSchema) 189 190 var constraints []VersionConstraint 191 192 for _, block := range rootContent.Blocks { 193 content, _, blockDiags := block.Body.PartialContent(configFileVersionSniffBlockSchema) 194 diags = append(diags, blockDiags...) 195 196 attr, exists := content.Attributes["required_version"] 197 if !exists { 198 continue 199 } 200 201 constraint, constraintDiags := decodeVersionConstraint(attr) 202 diags = append(diags, constraintDiags...) 203 if !constraintDiags.HasErrors() { 204 constraints = append(constraints, constraint) 205 } 206 } 207 208 return constraints, diags 209 } 210 211 // configFileSchema is the schema for the top-level of a config file. We use 212 // the low-level HCL API for this level so we can easily deal with each 213 // block type separately with its own decoding logic. 214 var configFileSchema = &hcl.BodySchema{ 215 Blocks: []hcl.BlockHeaderSchema{ 216 { 217 Type: "terraform", 218 }, 219 { 220 // This one is not really valid, but we include it here so we 221 // can create a specialized error message hinting the user to 222 // nest it inside a "terraform" block. 223 Type: "required_providers", 224 }, 225 { 226 Type: "provider", 227 LabelNames: []string{"name"}, 228 }, 229 { 230 Type: "variable", 231 LabelNames: []string{"name"}, 232 }, 233 { 234 Type: "locals", 235 }, 236 { 237 Type: "output", 238 LabelNames: []string{"name"}, 239 }, 240 { 241 Type: "module", 242 LabelNames: []string{"name"}, 243 }, 244 { 245 Type: "resource", 246 LabelNames: []string{"type", "name"}, 247 }, 248 { 249 Type: "data", 250 LabelNames: []string{"type", "name"}, 251 }, 252 { 253 Type: "moved", 254 }, 255 }, 256 } 257 258 // terraformBlockSchema is the schema for a top-level "terraform" block in 259 // a configuration file. 260 var terraformBlockSchema = &hcl.BodySchema{ 261 Attributes: []hcl.AttributeSchema{ 262 {Name: "required_version"}, 263 {Name: "experiments"}, 264 {Name: "language"}, 265 }, 266 Blocks: []hcl.BlockHeaderSchema{ 267 { 268 Type: "backend", 269 LabelNames: []string{"type"}, 270 }, 271 { 272 Type: "cloud", 273 }, 274 { 275 Type: "required_providers", 276 }, 277 { 278 Type: "provider_meta", 279 LabelNames: []string{"provider"}, 280 }, 281 }, 282 } 283 284 // configFileTerraformBlockSniffRootSchema is a schema for 285 // sniffCoreVersionRequirements and sniffActiveExperiments. 286 var configFileTerraformBlockSniffRootSchema = &hcl.BodySchema{ 287 Blocks: []hcl.BlockHeaderSchema{ 288 { 289 Type: "terraform", 290 }, 291 }, 292 } 293 294 // configFileVersionSniffBlockSchema is a schema for sniffCoreVersionRequirements 295 var configFileVersionSniffBlockSchema = &hcl.BodySchema{ 296 Attributes: []hcl.AttributeSchema{ 297 { 298 Name: "required_version", 299 }, 300 }, 301 } 302 303 // configFileExperimentsSniffBlockSchema is a schema for sniffActiveExperiments, 304 // to decode a single attribute from inside a "terraform" block. 305 var configFileExperimentsSniffBlockSchema = &hcl.BodySchema{ 306 Attributes: []hcl.AttributeSchema{ 307 {Name: "experiments"}, 308 {Name: "language"}, 309 }, 310 }