Skip to content

Commit

Permalink
Merge pull request #30138 from hashicorp/alisdair/json-module-call-pr…
Browse files Browse the repository at this point in the history
…oviders-mapping

jsonconfig: Improve provider configuration output
  • Loading branch information
alisdair committed Feb 10, 2022
2 parents 0a95038 + fe8183c commit d801827
Show file tree
Hide file tree
Showing 14 changed files with 775 additions and 12 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
2 changes: 1 addition & 1 deletion internal/command/jsonplan/plan.go
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions internal/command/show_test.go
Expand Up @@ -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))
}
Expand Down Expand Up @@ -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))
}
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 d801827

Please sign in to comment.