From f5b90f84a8f309d295d1dc53536d5b7f7f7a4753 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Fri, 10 Dec 2021 12:48:32 -0500 Subject: [PATCH 1/3] jsonconfig: Improve provider configuration output When rendering configuration as JSON, we have a single map of provider configurations at the top level, since these are globally applicable. Each resource has an opaque key into this map which points at the configuration data for the provider. This commit fixes two bugs in this implementation: - Resources in non-root modules had an invalid provider config key, which meant that there was never a valid reference to the provider config block. These keys were prefixed with the local module name instead of the path to the module. This is now corrected. - Modules with passed provider configs would point to either an empty provider config block or one which is not present at all. This has been fixed so that these resources point to the provider config block from the calling module (or wherever up the module tree it was originally defined). We also add a "full_name" key-value pair to the provider config block, with the entire fully-qualified provider name including hostname and namespace. --- internal/command/jsonconfig/config.go | 110 +++- .../testdata/show-json-sensitive/output.json | 1 + .../show-json/basic-create/output.json | 1 + .../testdata/show-json/modules/output.json | 7 +- .../show-json/nested-modules/output.json | 2 +- .../show-json/provider-aliasing/child/main.tf | 26 + .../provider-aliasing/child/nested/main.tf | 18 + .../show-json/provider-aliasing/main.tf | 34 ++ .../show-json/provider-aliasing/output.json | 567 ++++++++++++++++++ .../provider-version-no-config/output.json | 1 + .../show-json/provider-version/output.json | 1 + website/docs/internals/json-format.mdx | 11 +- 12 files changed, 768 insertions(+), 11 deletions(-) create mode 100644 internal/command/testdata/show-json/provider-aliasing/child/main.tf create mode 100644 internal/command/testdata/show-json/provider-aliasing/child/nested/main.tf create mode 100644 internal/command/testdata/show-json/provider-aliasing/main.tf create mode 100755 internal/command/testdata/show-json/provider-aliasing/output.json diff --git a/internal/command/jsonconfig/config.go b/internal/command/jsonconfig/config.go index 617181ba608b..807444d6e4f1 100644 --- a/internal/command/jsonconfig/config.go +++ b/internal/command/jsonconfig/config.go @@ -27,10 +27,12 @@ type config struct { // module boundaries. type providerConfig struct { Name string `json:"name,omitempty"` + FullName string `json:"full_name,omitempty"` Alias string `json:"alias,omitempty"` VersionConstraint string `json:"version_constraint,omitempty"` ModuleAddress string `json:"module_address,omitempty"` Expressions map[string]interface{} `json:"expressions,omitempty"` + parentKey string } type module struct { @@ -120,7 +122,6 @@ func Marshal(c *configs.Config, schemas *terraform.Schemas) ([]byte, error) { pcs := make(map[string]providerConfig) marshalProviderConfigs(c, schemas, pcs) - output.ProviderConfigs = pcs rootModule, err := marshalModule(c, schemas, "") if err != nil { @@ -128,6 +129,15 @@ func Marshal(c *configs.Config, schemas *terraform.Schemas) ([]byte, error) { } output.RootModule = rootModule + normalizeModuleProviderKeys(&rootModule, pcs) + + for name, pc := range pcs { + if pc.parentKey != "" { + delete(pcs, name) + } + } + output.ProviderConfigs = pcs + ret, err := json.Marshal(output) return ret, err } @@ -154,6 +164,7 @@ func marshalProviderConfigs( p := providerConfig{ Name: pc.Name, + FullName: providerFqn.String(), Alias: pc.Alias, ModuleAddress: c.Path.String(), Expressions: marshalExpressions(pc.Config, schema), @@ -176,6 +187,30 @@ func marshalProviderConfigs( // Ensure that any required providers with no associated configuration // block are included in the set. for k, pr := range c.Module.ProviderRequirements.RequiredProviders { + // If a provider has aliases defined, process those first. + for _, alias := range pr.Aliases { + // If there exists a value for this provider, we have nothing to add + // to it, so skip. + key := opaqueProviderKey(alias.StringCompact(), c.Path.String()) + if _, exists := m[key]; exists { + continue + } + // Given no provider configuration block exists, the only fields we can + // fill here are the local name, FQN, module address, and version + // constraints. + p := providerConfig{ + Name: pr.Name, + FullName: pr.Type.String(), + ModuleAddress: c.Path.String(), + } + + if vc, ok := reqs[pr.Type]; ok { + p.VersionConstraint = getproviders.VersionConstraintsString(vc) + } + + m[key] = p + } + // If there exists a value for this provider, we have nothing to add // to it, so skip. key := opaqueProviderKey(k, c.Path.String()) @@ -188,6 +223,7 @@ func marshalProviderConfigs( // constraints. p := providerConfig{ Name: pr.Name, + FullName: pr.Type.String(), ModuleAddress: c.Path.String(), } @@ -199,7 +235,53 @@ func marshalProviderConfigs( } // Must also visit our child modules, recursively. - for _, cc := range c.Children { + for name, mc := range c.Module.ModuleCalls { + // Keys in c.Children are guaranteed to match those in c.Module.ModuleCalls + cc := c.Children[name] + + // Add provider config map entries for passed provider configs, + // pointing at the passed configuration + for _, ppc := range mc.Providers { + // These provider names include aliases, if set + moduleProviderName := ppc.InChild.String() + parentProviderName := ppc.InParent.String() + + // Look up the provider FQN from the module context, using the non-aliased local name + providerFqn := cc.ProviderForConfigAddr(addrs.LocalProviderConfig{LocalName: ppc.InChild.Name}) + + // The presence of passed provider configs means that we cannot have + // any configuration expressions or version constraints here + p := providerConfig{ + Name: moduleProviderName, + FullName: providerFqn.String(), + ModuleAddress: cc.Path.String(), + } + + key := opaqueProviderKey(moduleProviderName, cc.Path.String()) + parentKey := opaqueProviderKey(parentProviderName, cc.Parent.Path.String()) + + // Traverse up the module call tree until we find the provider + // configuration which has no linked parent config. This is then + // the source of the configuration used in this module call, so + // we link to it directly + for { + parent, exists := m[parentKey] + if !exists { + break + } + p.parentKey = parentKey + parentKey = parent.parentKey + if parentKey == "" { + break + } + } + + m[key] = p + } + + // Finally, marshal any other provider configs within the called module. + // It is safe to do this last because it is invalid to configure a + // provider which has passed provider configs in the module call. marshalProviderConfigs(cc, schemas, m) } } @@ -319,7 +401,9 @@ func marshalModuleCall(c *configs.Config, mc *configs.ModuleCall, schemas *terra } ret.Expressions = marshalExpressions(mc.Config, schema) - module, _ := marshalModule(c, schemas, mc.Name) + + module, _ := marshalModule(c, schemas, c.Path.String()) + ret.Module = module if len(mc.DependsOn) > 0 { @@ -342,11 +426,12 @@ func marshalModuleCall(c *configs.Config, mc *configs.ModuleCall, schemas *terra func marshalResources(resources map[string]*configs.Resource, schemas *terraform.Schemas, moduleAddr string) ([]resource, error) { var rs []resource for _, v := range resources { + providerConfigKey := opaqueProviderKey(v.ProviderConfigAddr().StringCompact(), moduleAddr) r := resource{ Address: v.Addr().String(), Type: v.Type, Name: v.Name, - ProviderConfigKey: opaqueProviderKey(v.ProviderConfigAddr().StringCompact(), moduleAddr), + ProviderConfigKey: providerConfigKey, } switch v.Mode { @@ -416,6 +501,23 @@ func marshalResources(resources map[string]*configs.Resource, schemas *terraform return rs, nil } +// Flatten all resource provider keys in a module and its descendents, such +// that any resources from providers using a configuration passed through the +// module call have a direct refernce to that provider configuration. +func normalizeModuleProviderKeys(m *module, pcs map[string]providerConfig) { + for i, r := range m.Resources { + if pc, exists := pcs[r.ProviderConfigKey]; exists { + if _, hasParent := pcs[pc.parentKey]; hasParent { + m.Resources[i].ProviderConfigKey = pc.parentKey + } + } + } + + for _, mc := range m.ModuleCalls { + normalizeModuleProviderKeys(&mc.Module, pcs) + } +} + // opaqueProviderKey generates a unique absProviderConfig-like string from the module // address and provider func opaqueProviderKey(provider string, addr string) (key string) { diff --git a/internal/command/testdata/show-json-sensitive/output.json b/internal/command/testdata/show-json-sensitive/output.json index 206fbb7f6e60..9d1ec24bc800 100644 --- a/internal/command/testdata/show-json-sensitive/output.json +++ b/internal/command/testdata/show-json-sensitive/output.json @@ -164,6 +164,7 @@ "provider_config": { "test": { "name": "test", + "full_name": "registry.terraform.io/hashicorp/test", "expressions": { "region": { "constant_value": "somewhere" diff --git a/internal/command/testdata/show-json/basic-create/output.json b/internal/command/testdata/show-json/basic-create/output.json index d1b8aae5361b..83c14458066c 100644 --- a/internal/command/testdata/show-json/basic-create/output.json +++ b/internal/command/testdata/show-json/basic-create/output.json @@ -152,6 +152,7 @@ "provider_config": { "test": { "name": "test", + "full_name": "registry.terraform.io/hashicorp/test", "expressions": { "region": { "constant_value": "somewhere" diff --git a/internal/command/testdata/show-json/modules/output.json b/internal/command/testdata/show-json/modules/output.json index 4ed0ea45d692..1728282592de 100644 --- a/internal/command/testdata/show-json/modules/output.json +++ b/internal/command/testdata/show-json/modules/output.json @@ -224,7 +224,7 @@ "mode": "managed", "type": "test_instance", "name": "test", - "provider_config_key": "module_test_bar:test", + "provider_config_key": "module.module_test_bar:test", "expressions": { "ami": { "references": [ @@ -265,7 +265,7 @@ "mode": "managed", "type": "test_instance", "name": "test", - "provider_config_key": "module_test_foo:test", + "provider_config_key": "module.module_test_foo:test", "expressions": { "ami": { "references": [ @@ -291,7 +291,8 @@ "provider_config": { "module.module_test_foo:test": { "module_address": "module.module_test_foo", - "name": "test" + "name": "test", + "full_name": "registry.terraform.io/hashicorp/test" } } } diff --git a/internal/command/testdata/show-json/nested-modules/output.json b/internal/command/testdata/show-json/nested-modules/output.json index 359ea9ae181c..6d0af8a1468c 100644 --- a/internal/command/testdata/show-json/nested-modules/output.json +++ b/internal/command/testdata/show-json/nested-modules/output.json @@ -68,7 +68,7 @@ "mode": "managed", "type": "test_instance", "name": "test", - "provider_config_key": "more:test", + "provider_config_key": "module.my_module.module.more:test", "expressions": { "ami": { "references": [ diff --git a/internal/command/testdata/show-json/provider-aliasing/child/main.tf b/internal/command/testdata/show-json/provider-aliasing/child/main.tf new file mode 100644 index 000000000000..42555752cfd1 --- /dev/null +++ b/internal/command/testdata/show-json/provider-aliasing/child/main.tf @@ -0,0 +1,26 @@ +terraform { + required_providers { + test = { + source = "hashicorp/test" + configuration_aliases = [test, test.second] + } + } +} + +resource "test_instance" "test_primary" { + ami = "primary" + provider = test +} + +resource "test_instance" "test_secondary" { + ami = "secondary" + provider = test.second +} + +module "grandchild" { + source = "./nested" + providers = { + test = test + test.alt = test.second + } +} diff --git a/internal/command/testdata/show-json/provider-aliasing/child/nested/main.tf b/internal/command/testdata/show-json/provider-aliasing/child/nested/main.tf new file mode 100644 index 000000000000..ff1fe9a1afba --- /dev/null +++ b/internal/command/testdata/show-json/provider-aliasing/child/nested/main.tf @@ -0,0 +1,18 @@ +terraform { + required_providers { + test = { + source = "hashicorp/test" + configuration_aliases = [test, test.alt] + } + } +} + +resource "test_instance" "test_main" { + ami = "main" + provider = test +} + +resource "test_instance" "test_alternate" { + ami = "secondary" + provider = test.alt +} diff --git a/internal/command/testdata/show-json/provider-aliasing/main.tf b/internal/command/testdata/show-json/provider-aliasing/main.tf new file mode 100644 index 000000000000..7f6b0a3e337a --- /dev/null +++ b/internal/command/testdata/show-json/provider-aliasing/main.tf @@ -0,0 +1,34 @@ +provider "test" { + region = "somewhere" +} + +provider "test" { + alias = "backup" + region = "elsewhere" +} + +resource "test_instance" "test" { + ami = "foo" + provider = test +} + +resource "test_instance" "test_backup" { + ami = "foo-backup" + provider = test.backup +} + +module "child" { + source = "./child" + providers = { + test = test + test.second = test.backup + } +} + +module "sibling" { + source = "./child" + providers = { + test = test + test.second = test + } +} diff --git a/internal/command/testdata/show-json/provider-aliasing/output.json b/internal/command/testdata/show-json/provider-aliasing/output.json new file mode 100755 index 000000000000..187141c9cf88 --- /dev/null +++ b/internal/command/testdata/show-json/provider-aliasing/output.json @@ -0,0 +1,567 @@ +{ + "format_version": "1.0", + "terraform_version": "1.1.0-dev", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "foo" + }, + "sensitive_values": {} + }, + { + "address": "test_instance.test_backup", + "mode": "managed", + "type": "test_instance", + "name": "test_backup", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "foo-backup" + }, + "sensitive_values": {} + } + ], + "child_modules": [ + { + "resources": [ + { + "address": "module.child.test_instance.test_primary", + "mode": "managed", + "type": "test_instance", + "name": "test_primary", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "primary" + }, + "sensitive_values": {} + }, + { + "address": "module.child.test_instance.test_secondary", + "mode": "managed", + "type": "test_instance", + "name": "test_secondary", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "secondary" + }, + "sensitive_values": {} + } + ], + "address": "module.child", + "child_modules": [ + { + "resources": [ + { + "address": "module.child.module.grandchild.test_instance.test_alternate", + "mode": "managed", + "type": "test_instance", + "name": "test_alternate", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "secondary" + }, + "sensitive_values": {} + }, + { + "address": "module.child.module.grandchild.test_instance.test_main", + "mode": "managed", + "type": "test_instance", + "name": "test_main", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "main" + }, + "sensitive_values": {} + } + ], + "address": "module.child.module.grandchild" + } + ] + }, + { + "resources": [ + { + "address": "module.sibling.test_instance.test_primary", + "mode": "managed", + "type": "test_instance", + "name": "test_primary", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "primary" + }, + "sensitive_values": {} + }, + { + "address": "module.sibling.test_instance.test_secondary", + "mode": "managed", + "type": "test_instance", + "name": "test_secondary", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "secondary" + }, + "sensitive_values": {} + } + ], + "address": "module.sibling", + "child_modules": [ + { + "resources": [ + { + "address": "module.sibling.module.grandchild.test_instance.test_alternate", + "mode": "managed", + "type": "test_instance", + "name": "test_alternate", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "secondary" + }, + "sensitive_values": {} + }, + { + "address": "module.sibling.module.grandchild.test_instance.test_main", + "mode": "managed", + "type": "test_instance", + "name": "test_main", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "main" + }, + "sensitive_values": {} + } + ], + "address": "module.sibling.module.grandchild" + } + ] + } + ] + } + }, + "resource_changes": [ + { + "address": "module.child.module.grandchild.test_instance.test_alternate", + "module_address": "module.child.module.grandchild", + "mode": "managed", + "type": "test_instance", + "name": "test_alternate", + "provider_name": "registry.terraform.io/hashicorp/test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "secondary" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "module.child.module.grandchild.test_instance.test_main", + "module_address": "module.child.module.grandchild", + "mode": "managed", + "type": "test_instance", + "name": "test_main", + "provider_name": "registry.terraform.io/hashicorp/test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "main" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "module.child.test_instance.test_primary", + "module_address": "module.child", + "mode": "managed", + "type": "test_instance", + "name": "test_primary", + "provider_name": "registry.terraform.io/hashicorp/test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "primary" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "module.child.test_instance.test_secondary", + "module_address": "module.child", + "mode": "managed", + "type": "test_instance", + "name": "test_secondary", + "provider_name": "registry.terraform.io/hashicorp/test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "secondary" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "module.sibling.module.grandchild.test_instance.test_alternate", + "module_address": "module.sibling.module.grandchild", + "mode": "managed", + "type": "test_instance", + "name": "test_alternate", + "provider_name": "registry.terraform.io/hashicorp/test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "secondary" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "module.sibling.module.grandchild.test_instance.test_main", + "module_address": "module.sibling.module.grandchild", + "mode": "managed", + "type": "test_instance", + "name": "test_main", + "provider_name": "registry.terraform.io/hashicorp/test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "main" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "module.sibling.test_instance.test_primary", + "module_address": "module.sibling", + "mode": "managed", + "type": "test_instance", + "name": "test_primary", + "provider_name": "registry.terraform.io/hashicorp/test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "primary" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "module.sibling.test_instance.test_secondary", + "module_address": "module.sibling", + "mode": "managed", + "type": "test_instance", + "name": "test_secondary", + "provider_name": "registry.terraform.io/hashicorp/test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "secondary" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_name": "registry.terraform.io/hashicorp/test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "foo" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "test_instance.test_backup", + "mode": "managed", + "type": "test_instance", + "name": "test_backup", + "provider_name": "registry.terraform.io/hashicorp/test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "foo-backup" + }, + "after_unknown": { + "id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + } + ], + "configuration": { + "provider_config": { + "test": { + "name": "test", + "full_name": "registry.terraform.io/hashicorp/test", + "expressions": { + "region": { + "constant_value": "somewhere" + } + } + }, + "test.backup": { + "name": "test", + "full_name": "registry.terraform.io/hashicorp/test", + "alias": "backup", + "expressions": { + "region": { + "constant_value": "elsewhere" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_config_key": "test", + "expressions": { + "ami": { + "constant_value": "foo" + } + }, + "schema_version": 0 + }, + { + "address": "test_instance.test_backup", + "mode": "managed", + "type": "test_instance", + "name": "test_backup", + "provider_config_key": "test.backup", + "expressions": { + "ami": { + "constant_value": "foo-backup" + } + }, + "schema_version": 0 + } + ], + "module_calls": { + "child": { + "source": "./child", + "module": { + "resources": [ + { + "address": "test_instance.test_primary", + "mode": "managed", + "type": "test_instance", + "name": "test_primary", + "provider_config_key": "test", + "expressions": { + "ami": { + "constant_value": "primary" + } + }, + "schema_version": 0 + }, + { + "address": "test_instance.test_secondary", + "mode": "managed", + "type": "test_instance", + "name": "test_secondary", + "provider_config_key": "test.backup", + "expressions": { + "ami": { + "constant_value": "secondary" + } + }, + "schema_version": 0 + } + ], + "module_calls": { + "grandchild": { + "source": "./nested", + "module": { + "resources": [ + { + "address": "test_instance.test_alternate", + "mode": "managed", + "type": "test_instance", + "name": "test_alternate", + "provider_config_key": "test.backup", + "expressions": { + "ami": { + "constant_value": "secondary" + } + }, + "schema_version": 0 + }, + { + "address": "test_instance.test_main", + "mode": "managed", + "type": "test_instance", + "name": "test_main", + "provider_config_key": "test", + "expressions": { + "ami": { + "constant_value": "main" + } + }, + "schema_version": 0 + } + ] + } + } + } + } + }, + "sibling": { + "source": "./child", + "module": { + "resources": [ + { + "address": "test_instance.test_primary", + "mode": "managed", + "type": "test_instance", + "name": "test_primary", + "provider_config_key": "test", + "expressions": { + "ami": { + "constant_value": "primary" + } + }, + "schema_version": 0 + }, + { + "address": "test_instance.test_secondary", + "mode": "managed", + "type": "test_instance", + "name": "test_secondary", + "provider_config_key": "test", + "expressions": { + "ami": { + "constant_value": "secondary" + } + }, + "schema_version": 0 + } + ], + "module_calls": { + "grandchild": { + "source": "./nested", + "module": { + "resources": [ + { + "address": "test_instance.test_alternate", + "mode": "managed", + "type": "test_instance", + "name": "test_alternate", + "provider_config_key": "test", + "expressions": { + "ami": { + "constant_value": "secondary" + } + }, + "schema_version": 0 + }, + { + "address": "test_instance.test_main", + "mode": "managed", + "type": "test_instance", + "name": "test_main", + "provider_config_key": "test", + "expressions": { + "ami": { + "constant_value": "main" + } + }, + "schema_version": 0 + } + ] + } + } + } + } + } + } + } + } +} diff --git a/internal/command/testdata/show-json/provider-version-no-config/output.json b/internal/command/testdata/show-json/provider-version-no-config/output.json index 6a8b1f451dc0..b5e78402d8b6 100644 --- a/internal/command/testdata/show-json/provider-version-no-config/output.json +++ b/internal/command/testdata/show-json/provider-version-no-config/output.json @@ -152,6 +152,7 @@ "provider_config": { "test": { "name": "test", + "full_name": "registry.terraform.io/hashicorp/test", "version_constraint": ">= 1.2.3" } }, diff --git a/internal/command/testdata/show-json/provider-version/output.json b/internal/command/testdata/show-json/provider-version/output.json index 11fd3bd64c15..4d86a29b05ca 100644 --- a/internal/command/testdata/show-json/provider-version/output.json +++ b/internal/command/testdata/show-json/provider-version/output.json @@ -152,6 +152,7 @@ "provider_config": { "test": { "name": "test", + "full_name": "registry.terraform.io/hashicorp/test", "expressions": { "region": { "constant_value": "somewhere" diff --git a/website/docs/internals/json-format.mdx b/website/docs/internals/json-format.mdx index 556d442eeb36..9d76f4c7473c 100644 --- a/website/docs/internals/json-format.mdx +++ b/website/docs/internals/json-format.mdx @@ -323,7 +323,7 @@ Because the configuration models are produced at a stage prior to expression eva // the configuration tree, flattened into a single map for convenience since // provider configurations are the one concept in Terraform that can span // across module boundaries. - "provider_configs": { + "provider_config": { // Keys in the provider_configs map are to be considered opaque by callers, // and used just for lookups using the "provider_config_key" property in each @@ -333,6 +333,9 @@ Because the configuration models are produced at a stage prior to expression eva // "name" is the name of the provider without any alias "name": "aws", + // "full_name" is the fully-qualified provider name + "full_name": "registry.terraform.io/hashicorp/aws", + // "alias" is the alias set for a non-default configuration, or unset for // a default configuration. "alias": "foo", @@ -378,7 +381,9 @@ Because the configuration models are produced at a stage prior to expression eva // "provider_config_key" is the key into "provider_configs" (shown // above) for the provider configuration that this resource is - // associated with. + // associated with. If the provider configuration was passed into + // this module from the parent module, the key will point to the + // original provider config block. "provider_config_key": "opaque_provider_ref_aws", // "provisioners" is an optional field which describes any provisioners. @@ -440,7 +445,7 @@ Because the configuration models are produced at a stage prior to expression eva // "module" is a representation of the configuration of the child module // itself, using the same structure as the "root_module" object, // recursively describing the full module tree. - "module": , + "module": } } } From ddc81a204f8ce950329f1d5f2d0f8b6cce08c472 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Mon, 7 Feb 2022 15:04:49 -0500 Subject: [PATCH 2/3] json: Disregard format version in tests Instead of manually updating every JSON output test fixture when we change the format version, disregard any differences when testing. --- internal/command/show_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/command/show_test.go b/internal/command/show_test.go index 5f220e906ee0..00a9e555a2e5 100644 --- a/internal/command/show_test.go +++ b/internal/command/show_test.go @@ -576,6 +576,9 @@ func TestShow_json_output(t *testing.T) { } json.Unmarshal([]byte(byteValue), &want) + // Disregard format version to reduce needless test fixture churn + want.FormatVersion = got.FormatVersion + if !cmp.Equal(got, want) { t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) } @@ -667,6 +670,9 @@ func TestShow_json_output_sensitive(t *testing.T) { } json.Unmarshal([]byte(byteValue), &want) + // Disregard format version to reduce needless test fixture churn + want.FormatVersion = got.FormatVersion + if !cmp.Equal(got, want) { t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) } From fe8183c4af882bd92dd52e86e9b11e7251174d1c Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Mon, 7 Feb 2022 15:06:05 -0500 Subject: [PATCH 3/3] json: Increment JSON plan format version The JSON plan configuration data now includes a `full_name` field for providers. This addition warrants a backwards compatible increment to the version number. --- internal/command/jsonplan/plan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/jsonplan/plan.go b/internal/command/jsonplan/plan.go index 06ed97961012..53fe8ec01b45 100644 --- a/internal/command/jsonplan/plan.go +++ b/internal/command/jsonplan/plan.go @@ -22,7 +22,7 @@ import ( // FormatVersion represents the version of the json format and will be // incremented for any change to this format that requires changes to a // consuming parser. -const FormatVersion = "1.0" +const FormatVersion = "1.1" // Plan is the top-level representation of the json format of a plan. It includes // the complete config and current state.