diff --git a/tfconfig/provider_ref.go b/tfconfig/provider_ref.go index c173114..a8a956e 100644 --- a/tfconfig/provider_ref.go +++ b/tfconfig/provider_ref.go @@ -17,8 +17,9 @@ type ProviderRef struct { } type ProviderRequirement struct { - Source string `json:"source,omitempty"` - VersionConstraints []string `json:"version_constraints,omitempty"` + Source string `json:"source,omitempty"` + VersionConstraints []string `json:"version_constraints,omitempty"` + ConfigurationAliases []ProviderRef `json:"aliases,omitempty"` } func decodeRequiredProvidersBlock(block *hcl.Block) (map[string]*ProviderRequirement, hcl.Diagnostics) { @@ -85,8 +86,8 @@ func decodeRequiredProvidersBlock(block *hcl.Block) (map[string]*ProviderRequire } case "source": - source, err := kv.Value.Value(nil) - if err != nil || !source.Type().Equals(cty.String) { + source, valDiags := kv.Value.Value(nil) + if valDiags.HasErrors() || !source.Type().Equals(cty.String) { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Unsuitable value type", @@ -99,6 +100,13 @@ func decodeRequiredProvidersBlock(block *hcl.Block) (map[string]*ProviderRequire if !source.IsNull() { pr.Source = source.AsString() } + case "configuration_aliases": + aliases, valDiags := decodeConfigurationAliases(name, kv.Value) + if valDiags.HasErrors() { + diags = append(diags, valDiags...) + continue + } + pr.ConfigurationAliases = append(pr.ConfigurationAliases, aliases...) } reqs[name] = &pr @@ -107,3 +115,84 @@ func decodeRequiredProvidersBlock(block *hcl.Block) (map[string]*ProviderRequire return reqs, diags } + +func decodeConfigurationAliases(localName string, value hcl.Expression) ([]ProviderRef, hcl.Diagnostics) { + aliases := make([]ProviderRef, 0) + var diags hcl.Diagnostics + + exprs, listDiags := hcl.ExprList(value) + if listDiags.HasErrors() { + diags = append(diags, listDiags...) + return aliases, diags + } + + for _, expr := range exprs { + traversal, travDiags := hcl.AbsTraversalForExpr(expr) + if travDiags.HasErrors() { + diags = append(diags, travDiags...) + continue + } + + ref, cfgDiags := parseProviderRef(traversal) + if cfgDiags.HasErrors() { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid configuration_aliases value", + Detail: `Configuration aliases can only contain references to local provider configuration names in the format of provider.alias`, + Subject: value.Range().Ptr(), + }) + continue + } + + if ref.Name != localName { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid configuration_aliases value", + Detail: fmt.Sprintf(`Configuration aliases must be prefixed with the provider name. Expected %q, but found %q.`, localName, ref.Name), + Subject: value.Range().Ptr(), + }) + continue + } + + aliases = append(aliases, ref) + } + + return aliases, diags +} + +func parseProviderRef(traversal hcl.Traversal) (ProviderRef, hcl.Diagnostics) { + var diags hcl.Diagnostics + ret := ProviderRef{ + Name: traversal.RootName(), + } + + if len(traversal) < 2 { + // Just a local name, then. + return ret, diags + } + + aliasStep := traversal[1] + switch ts := aliasStep.(type) { + case hcl.TraverseAttr: + ret.Alias = ts.Name + return ret, diags + default: + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider configuration address", + Detail: "The provider type name must either stand alone or be followed by an alias name separated with a dot.", + Subject: aliasStep.SourceRange().Ptr(), + }) + } + + if len(traversal) > 2 { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid provider configuration address", + Detail: "Extraneous extra operators after provider configuration address.", + Subject: traversal[2:].SourceRange().Ptr(), + }) + } + + return ret, diags +} diff --git a/tfconfig/testdata/provider-aliases-json/provider-aliases-json.out.json b/tfconfig/testdata/provider-aliases-json/provider-aliases-json.out.json new file mode 100644 index 0000000..3c8be0c --- /dev/null +++ b/tfconfig/testdata/provider-aliases-json/provider-aliases-json.out.json @@ -0,0 +1,27 @@ +{ + "path": "testdata/provider-aliases-json", + "variables": {}, + "outputs": {}, + "required_providers": { + "bar": {}, + "baz": {}, + "bleep": { + "aliases": [ + {"name": "bleep", "alias": "bloop"} + ] + }, + "empty": {}, + "foo": {} + }, + "provider_configs": { + "bar.yellow": {"name": "bar", "alias": "yellow"}, + "baz": {"name": "baz"}, + "empty": {"name": "empty"}, + "bar": {"name": "bar"}, + "foo.blue": {"name": "foo", "alias": "blue"}, + "foo.red": {"name": "foo", "alias": "red"} + }, + "managed_resources": {}, + "data_resources": {}, + "module_calls": {} +} diff --git a/tfconfig/testdata/provider-aliases-json/provider-aliases-json.out.md b/tfconfig/testdata/provider-aliases-json/provider-aliases-json.out.md new file mode 100644 index 0000000..7b99e9f --- /dev/null +++ b/tfconfig/testdata/provider-aliases-json/provider-aliases-json.out.md @@ -0,0 +1,10 @@ + +# Module `testdata/provider-aliases-json` + +Provider Requirements: +* **bar:** (any version) +* **baz:** (any version) +* **bleep:** (any version) +* **empty:** (any version) +* **foo:** (any version) + diff --git a/tfconfig/testdata/provider-aliases-json/provider-aliases-json.tf.json b/tfconfig/testdata/provider-aliases-json/provider-aliases-json.tf.json new file mode 100644 index 0000000..81d5322 --- /dev/null +++ b/tfconfig/testdata/provider-aliases-json/provider-aliases-json.tf.json @@ -0,0 +1,35 @@ +{ + "terraform": { + "required_providers": { + "bleep": { + "configuration_aliases": [ + "bleep.bloop" + ] + } + } + }, + "provider": { + "foo": [ + { + "alias": "blue" + }, + { + "alias": "red" + } + ], + "bar": [ + {}, + { + "alias": "yellow" + } + ], + "baz": [ + {} + ], + "empty": [ + { + "alias": "" + } + ] + } +} diff --git a/tfconfig/testdata/provider-aliases/provider-aliases.out.json b/tfconfig/testdata/provider-aliases/provider-aliases.out.json index 7446e68..6cceac4 100644 --- a/tfconfig/testdata/provider-aliases/provider-aliases.out.json +++ b/tfconfig/testdata/provider-aliases/provider-aliases.out.json @@ -5,7 +5,11 @@ "required_providers": { "bar": {}, "baz": {}, - "bleep": {}, + "bleep": { + "aliases": [ + {"name": "bleep", "alias": "bloop"} + ] + }, "empty": {}, "foo": {} },