Skip to content

Commit c834461

Browse files
committed
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.
1 parent 0900c7e commit c834461

File tree

12 files changed

+768
-11
lines changed

12 files changed

+768
-11
lines changed

internal/command/jsonconfig/config.go

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ type config struct {
2727
// module boundaries.
2828
type providerConfig struct {
2929
Name string `json:"name,omitempty"`
30+
FullName string `json:"full_name,omitempty"`
3031
Alias string `json:"alias,omitempty"`
3132
VersionConstraint string `json:"version_constraint,omitempty"`
3233
ModuleAddress string `json:"module_address,omitempty"`
3334
Expressions map[string]interface{} `json:"expressions,omitempty"`
35+
parentKey string
3436
}
3537

3638
type module struct {
@@ -120,14 +122,22 @@ func Marshal(c *configs.Config, schemas *terraform.Schemas) ([]byte, error) {
120122

121123
pcs := make(map[string]providerConfig)
122124
marshalProviderConfigs(c, schemas, pcs)
123-
output.ProviderConfigs = pcs
124125

125126
rootModule, err := marshalModule(c, schemas, "")
126127
if err != nil {
127128
return nil, err
128129
}
129130
output.RootModule = rootModule
130131

132+
normalizeModuleProviderKeys(&rootModule, pcs)
133+
134+
for name, pc := range pcs {
135+
if pc.parentKey != "" {
136+
delete(pcs, name)
137+
}
138+
}
139+
output.ProviderConfigs = pcs
140+
131141
ret, err := json.Marshal(output)
132142
return ret, err
133143
}
@@ -154,6 +164,7 @@ func marshalProviderConfigs(
154164

155165
p := providerConfig{
156166
Name: pc.Name,
167+
FullName: providerFqn.String(),
157168
Alias: pc.Alias,
158169
ModuleAddress: c.Path.String(),
159170
Expressions: marshalExpressions(pc.Config, schema),
@@ -176,6 +187,30 @@ func marshalProviderConfigs(
176187
// Ensure that any required providers with no associated configuration
177188
// block are included in the set.
178189
for k, pr := range c.Module.ProviderRequirements.RequiredProviders {
190+
// If a provider has aliases defined, process those first.
191+
for _, alias := range pr.Aliases {
192+
// If there exists a value for this provider, we have nothing to add
193+
// to it, so skip.
194+
key := opaqueProviderKey(alias.StringCompact(), c.Path.String())
195+
if _, exists := m[key]; exists {
196+
continue
197+
}
198+
// Given no provider configuration block exists, the only fields we can
199+
// fill here are the local name, FQN, module address, and version
200+
// constraints.
201+
p := providerConfig{
202+
Name: pr.Name,
203+
FullName: pr.Type.String(),
204+
ModuleAddress: c.Path.String(),
205+
}
206+
207+
if vc, ok := reqs[pr.Type]; ok {
208+
p.VersionConstraint = getproviders.VersionConstraintsString(vc)
209+
}
210+
211+
m[key] = p
212+
}
213+
179214
// If there exists a value for this provider, we have nothing to add
180215
// to it, so skip.
181216
key := opaqueProviderKey(k, c.Path.String())
@@ -188,6 +223,7 @@ func marshalProviderConfigs(
188223
// constraints.
189224
p := providerConfig{
190225
Name: pr.Name,
226+
FullName: pr.Type.String(),
191227
ModuleAddress: c.Path.String(),
192228
}
193229

@@ -199,7 +235,53 @@ func marshalProviderConfigs(
199235
}
200236

201237
// Must also visit our child modules, recursively.
202-
for _, cc := range c.Children {
238+
for name, mc := range c.Module.ModuleCalls {
239+
// Keys in c.Children are guaranteed to match those in c.Module.ModuleCalls
240+
cc := c.Children[name]
241+
242+
// Add provider config map entries for passed provider configs,
243+
// pointing at the passed configuration
244+
for _, ppc := range mc.Providers {
245+
// These provider names include aliases, if set
246+
moduleProviderName := ppc.InChild.String()
247+
parentProviderName := ppc.InParent.String()
248+
249+
// Look up the provider FQN from the module context, using the non-aliased local name
250+
providerFqn := cc.ProviderForConfigAddr(addrs.LocalProviderConfig{LocalName: ppc.InChild.Name})
251+
252+
// The presence of passed provider configs means that we cannot have
253+
// any configuration expressions or version constraints here
254+
p := providerConfig{
255+
Name: moduleProviderName,
256+
FullName: providerFqn.String(),
257+
ModuleAddress: cc.Path.String(),
258+
}
259+
260+
key := opaqueProviderKey(moduleProviderName, cc.Path.String())
261+
parentKey := opaqueProviderKey(parentProviderName, cc.Parent.Path.String())
262+
263+
// Traverse up the module call tree until we find the provider
264+
// configuration which has no linked parent config. This is then
265+
// the source of the configuration used in this module call, so
266+
// we link to it directly
267+
for {
268+
parent, exists := m[parentKey]
269+
if !exists {
270+
break
271+
}
272+
p.parentKey = parentKey
273+
parentKey = parent.parentKey
274+
if parentKey == "" {
275+
break
276+
}
277+
}
278+
279+
m[key] = p
280+
}
281+
282+
// Finally, marshal any other provider configs within the called module.
283+
// It is safe to do this last because it is invalid to configure a
284+
// provider which has passed provider configs in the module call.
203285
marshalProviderConfigs(cc, schemas, m)
204286
}
205287
}
@@ -319,7 +401,9 @@ func marshalModuleCall(c *configs.Config, mc *configs.ModuleCall, schemas *terra
319401
}
320402

321403
ret.Expressions = marshalExpressions(mc.Config, schema)
322-
module, _ := marshalModule(c, schemas, mc.Name)
404+
405+
module, _ := marshalModule(c, schemas, c.Path.String())
406+
323407
ret.Module = module
324408

325409
if len(mc.DependsOn) > 0 {
@@ -342,11 +426,12 @@ func marshalModuleCall(c *configs.Config, mc *configs.ModuleCall, schemas *terra
342426
func marshalResources(resources map[string]*configs.Resource, schemas *terraform.Schemas, moduleAddr string) ([]resource, error) {
343427
var rs []resource
344428
for _, v := range resources {
429+
providerConfigKey := opaqueProviderKey(v.ProviderConfigAddr().StringCompact(), moduleAddr)
345430
r := resource{
346431
Address: v.Addr().String(),
347432
Type: v.Type,
348433
Name: v.Name,
349-
ProviderConfigKey: opaqueProviderKey(v.ProviderConfigAddr().StringCompact(), moduleAddr),
434+
ProviderConfigKey: providerConfigKey,
350435
}
351436

352437
switch v.Mode {
@@ -416,6 +501,23 @@ func marshalResources(resources map[string]*configs.Resource, schemas *terraform
416501
return rs, nil
417502
}
418503

504+
// Flatten all resource provider keys in a module and its descendents, such
505+
// that any resources from providers using a configuration passed through the
506+
// module call have a direct refernce to that provider configuration.
507+
func normalizeModuleProviderKeys(m *module, pcs map[string]providerConfig) {
508+
for i, r := range m.Resources {
509+
if pc, exists := pcs[r.ProviderConfigKey]; exists {
510+
if _, hasParent := pcs[pc.parentKey]; hasParent {
511+
m.Resources[i].ProviderConfigKey = pc.parentKey
512+
}
513+
}
514+
}
515+
516+
for _, mc := range m.ModuleCalls {
517+
normalizeModuleProviderKeys(&mc.Module, pcs)
518+
}
519+
}
520+
419521
// opaqueProviderKey generates a unique absProviderConfig-like string from the module
420522
// address and provider
421523
func opaqueProviderKey(provider string, addr string) (key string) {

internal/command/testdata/show-json-sensitive/output.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
"provider_config": {
165165
"test": {
166166
"name": "test",
167+
"full_name": "registry.terraform.io/hashicorp/test",
167168
"expressions": {
168169
"region": {
169170
"constant_value": "somewhere"

internal/command/testdata/show-json/basic-create/output.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
"provider_config": {
153153
"test": {
154154
"name": "test",
155+
"full_name": "registry.terraform.io/hashicorp/test",
155156
"expressions": {
156157
"region": {
157158
"constant_value": "somewhere"

internal/command/testdata/show-json/modules/output.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@
224224
"mode": "managed",
225225
"type": "test_instance",
226226
"name": "test",
227-
"provider_config_key": "module_test_bar:test",
227+
"provider_config_key": "module.module_test_bar:test",
228228
"expressions": {
229229
"ami": {
230230
"references": [
@@ -265,7 +265,7 @@
265265
"mode": "managed",
266266
"type": "test_instance",
267267
"name": "test",
268-
"provider_config_key": "module_test_foo:test",
268+
"provider_config_key": "module.module_test_foo:test",
269269
"expressions": {
270270
"ami": {
271271
"references": [
@@ -291,7 +291,8 @@
291291
"provider_config": {
292292
"module.module_test_foo:test": {
293293
"module_address": "module.module_test_foo",
294-
"name": "test"
294+
"name": "test",
295+
"full_name": "registry.terraform.io/hashicorp/test"
295296
}
296297
}
297298
}

internal/command/testdata/show-json/nested-modules/output.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"mode": "managed",
6969
"type": "test_instance",
7070
"name": "test",
71-
"provider_config_key": "more:test",
71+
"provider_config_key": "module.my_module.module.more:test",
7272
"expressions": {
7373
"ami": {
7474
"references": [
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
terraform {
2+
required_providers {
3+
test = {
4+
source = "hashicorp/test"
5+
configuration_aliases = [test, test.second]
6+
}
7+
}
8+
}
9+
10+
resource "test_instance" "test_primary" {
11+
ami = "primary"
12+
provider = test
13+
}
14+
15+
resource "test_instance" "test_secondary" {
16+
ami = "secondary"
17+
provider = test.second
18+
}
19+
20+
module "grandchild" {
21+
source = "./nested"
22+
providers = {
23+
test = test
24+
test.alt = test.second
25+
}
26+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
terraform {
2+
required_providers {
3+
test = {
4+
source = "hashicorp/test"
5+
configuration_aliases = [test, test.alt]
6+
}
7+
}
8+
}
9+
10+
resource "test_instance" "test_main" {
11+
ami = "main"
12+
provider = test
13+
}
14+
15+
resource "test_instance" "test_alternate" {
16+
ami = "secondary"
17+
provider = test.alt
18+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
provider "test" {
2+
region = "somewhere"
3+
}
4+
5+
provider "test" {
6+
alias = "backup"
7+
region = "elsewhere"
8+
}
9+
10+
resource "test_instance" "test" {
11+
ami = "foo"
12+
provider = test
13+
}
14+
15+
resource "test_instance" "test_backup" {
16+
ami = "foo-backup"
17+
provider = test.backup
18+
}
19+
20+
module "child" {
21+
source = "./child"
22+
providers = {
23+
test = test
24+
test.second = test.backup
25+
}
26+
}
27+
28+
module "sibling" {
29+
source = "./child"
30+
providers = {
31+
test = test
32+
test.second = test
33+
}
34+
}

0 commit comments

Comments
 (0)