4 "github.com/hashicorp/hcl2/hcl"
7 // LoadConfigFile reads the file at the given path and parses it as a config
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.
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
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)
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)
32 func (p *Parser) loadConfigFile(path string, override bool) (*File, hcl.Diagnostics) {
34 body, diags := p.LoadHCLFile(path)
41 var reqDiags hcl.Diagnostics
42 file.CoreVersionConstraints, reqDiags = sniffCoreVersionRequirements(body)
43 diags = append(diags, reqDiags...)
45 content, contentDiags := body.Content(configFileSchema)
46 diags = append(diags, contentDiags...)
48 for _, block := range content.Blocks {
52 content, contentDiags := block.Body.Content(terraformBlockSchema)
53 diags = append(diags, contentDiags...)
55 // We ignore the "terraform_version" attribute here because
56 // sniffCoreVersionRequirements already dealt with that above.
58 for _, innerBlock := range content.Blocks {
59 switch innerBlock.Type {
62 backendCfg, cfgDiags := decodeBackendBlock(innerBlock)
63 diags = append(diags, cfgDiags...)
64 if backendCfg != nil {
65 file.Backends = append(file.Backends, backendCfg)
68 case "required_providers":
69 reqs, reqsDiags := decodeRequiredProvidersBlock(innerBlock)
70 diags = append(diags, reqsDiags...)
71 file.ProviderRequirements = append(file.ProviderRequirements, reqs...)
74 // Should never happen because the above cases should be exhaustive
75 // for all block type names in our schema.
82 cfg, cfgDiags := decodeProviderBlock(block)
83 diags = append(diags, cfgDiags...)
85 file.ProviderConfigs = append(file.ProviderConfigs, cfg)
89 cfg, cfgDiags := decodeVariableBlock(block, override)
90 diags = append(diags, cfgDiags...)
92 file.Variables = append(file.Variables, cfg)
96 defs, defsDiags := decodeLocalsBlock(block)
97 diags = append(diags, defsDiags...)
98 file.Locals = append(file.Locals, defs...)
101 cfg, cfgDiags := decodeOutputBlock(block, override)
102 diags = append(diags, cfgDiags...)
104 file.Outputs = append(file.Outputs, cfg)
108 cfg, cfgDiags := decodeModuleBlock(block, override)
109 diags = append(diags, cfgDiags...)
111 file.ModuleCalls = append(file.ModuleCalls, cfg)
115 cfg, cfgDiags := decodeResourceBlock(block)
116 diags = append(diags, cfgDiags...)
118 file.ManagedResources = append(file.ManagedResources, cfg)
122 cfg, cfgDiags := decodeDataBlock(block)
123 diags = append(diags, cfgDiags...)
125 file.DataResources = append(file.DataResources, cfg)
129 // Should never happen because the above cases should be exhaustive
130 // for all block type names in our schema.
139 // sniffCoreVersionRequirements does minimal parsing of the given body for
140 // "terraform" blocks with "required_version" attributes, returning the
141 // requirements found.
143 // This is intended to maximize the chance that we'll be able to read the
144 // requirements (syntax errors notwithstanding) even if the config file contains
145 // constructs that might've been added in future Terraform versions
147 // This is a "best effort" sort of method which will return constraints it is
148 // able to find, but may return no constraints at all if the given body is
149 // so invalid that it cannot be decoded at all.
150 func sniffCoreVersionRequirements(body hcl.Body) ([]VersionConstraint, hcl.Diagnostics) {
151 rootContent, _, diags := body.PartialContent(configFileVersionSniffRootSchema)
153 var constraints []VersionConstraint
155 for _, block := range rootContent.Blocks {
156 content, _, blockDiags := block.Body.PartialContent(configFileVersionSniffBlockSchema)
157 diags = append(diags, blockDiags...)
159 attr, exists := content.Attributes["required_version"]
164 constraint, constraintDiags := decodeVersionConstraint(attr)
165 diags = append(diags, constraintDiags...)
166 if !constraintDiags.HasErrors() {
167 constraints = append(constraints, constraint)
171 return constraints, diags
174 // configFileSchema is the schema for the top-level of a config file. We use
175 // the low-level HCL API for this level so we can easily deal with each
176 // block type separately with its own decoding logic.
177 var configFileSchema = &hcl.BodySchema{
178 Blocks: []hcl.BlockHeaderSchema{
184 LabelNames: []string{"name"},
188 LabelNames: []string{"name"},
195 LabelNames: []string{"name"},
199 LabelNames: []string{"name"},
203 LabelNames: []string{"type", "name"},
207 LabelNames: []string{"type", "name"},
212 // terraformBlockSchema is the schema for a top-level "terraform" block in
213 // a configuration file.
214 var terraformBlockSchema = &hcl.BodySchema{
215 Attributes: []hcl.AttributeSchema{
217 Name: "required_version",
220 Blocks: []hcl.BlockHeaderSchema{
223 LabelNames: []string{"type"},
226 Type: "required_providers",
231 // configFileVersionSniffRootSchema is a schema for sniffCoreVersionRequirements
232 var configFileVersionSniffRootSchema = &hcl.BodySchema{
233 Blocks: []hcl.BlockHeaderSchema{
240 // configFileVersionSniffBlockSchema is a schema for sniffCoreVersionRequirements
241 var configFileVersionSniffBlockSchema = &hcl.BodySchema{
242 Attributes: []hcl.AttributeSchema{
244 Name: "required_version",