Skip to content

Commit

Permalink
jsonconfig: Improve provider configuration output
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
alisdair committed Jan 28, 2022
1 parent 0900c7e commit c834461
Show file tree
Hide file tree
Showing 12 changed files with 768 additions and 11 deletions.
110 changes: 106 additions & 4 deletions internal/command/jsonconfig/config.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -120,14 +122,22 @@ 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 {
return nil, err
}
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
}
Expand All @@ -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),
Expand All @@ -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())
Expand All @@ -188,6 +223,7 @@ func marshalProviderConfigs(
// constraints.
p := providerConfig{
Name: pr.Name,
FullName: pr.Type.String(),
ModuleAddress: c.Path.String(),
}

Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions internal/command/testdata/show-json-sensitive/output.json
Expand Up @@ -164,6 +164,7 @@
"provider_config": {
"test": {
"name": "test",
"full_name": "registry.terraform.io/hashicorp/test",
"expressions": {
"region": {
"constant_value": "somewhere"
Expand Down
Expand Up @@ -152,6 +152,7 @@
"provider_config": {
"test": {
"name": "test",
"full_name": "registry.terraform.io/hashicorp/test",
"expressions": {
"region": {
"constant_value": "somewhere"
Expand Down
7 changes: 4 additions & 3 deletions internal/command/testdata/show-json/modules/output.json
Expand Up @@ -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": [
Expand Down Expand Up @@ -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": [
Expand All @@ -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"
}
}
}
Expand Down
Expand Up @@ -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": [
Expand Down
@@ -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
}
}
@@ -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
}
34 changes: 34 additions & 0 deletions 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
}
}

0 comments on commit c834461

Please sign in to comment.