github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonconfig/config.go (about) 1 package jsonconfig 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "sort" 7 8 "github.com/zclconf/go-cty/cty" 9 ctyjson "github.com/zclconf/go-cty/cty/json" 10 11 "github.com/hashicorp/terraform/internal/addrs" 12 "github.com/hashicorp/terraform/internal/configs" 13 "github.com/hashicorp/terraform/internal/configs/configschema" 14 "github.com/hashicorp/terraform/internal/getproviders" 15 "github.com/hashicorp/terraform/internal/terraform" 16 ) 17 18 // Config represents the complete configuration source 19 type config struct { 20 ProviderConfigs map[string]providerConfig `json:"provider_config,omitempty"` 21 RootModule module `json:"root_module,omitempty"` 22 } 23 24 // ProviderConfig describes all of the provider configurations throughout the 25 // configuration tree, flattened into a single map for convenience since 26 // provider configurations are the one concept in Terraform that can span across 27 // module boundaries. 28 type providerConfig struct { 29 Name string `json:"name,omitempty"` 30 FullName string `json:"full_name,omitempty"` 31 Alias string `json:"alias,omitempty"` 32 VersionConstraint string `json:"version_constraint,omitempty"` 33 ModuleAddress string `json:"module_address,omitempty"` 34 Expressions map[string]interface{} `json:"expressions,omitempty"` 35 parentKey string 36 } 37 38 type module struct { 39 Outputs map[string]output `json:"outputs,omitempty"` 40 // Resources are sorted in a user-friendly order that is undefined at this 41 // time, but consistent. 42 Resources []resource `json:"resources,omitempty"` 43 ModuleCalls map[string]moduleCall `json:"module_calls,omitempty"` 44 Variables variables `json:"variables,omitempty"` 45 } 46 47 type moduleCall struct { 48 Source string `json:"source,omitempty"` 49 Expressions map[string]interface{} `json:"expressions,omitempty"` 50 CountExpression *expression `json:"count_expression,omitempty"` 51 ForEachExpression *expression `json:"for_each_expression,omitempty"` 52 Module module `json:"module,omitempty"` 53 VersionConstraint string `json:"version_constraint,omitempty"` 54 DependsOn []string `json:"depends_on,omitempty"` 55 } 56 57 // variables is the JSON representation of the variables provided to the current 58 // plan. 59 type variables map[string]*variable 60 61 type variable struct { 62 Default json.RawMessage `json:"default,omitempty"` 63 Description string `json:"description,omitempty"` 64 Sensitive bool `json:"sensitive,omitempty"` 65 } 66 67 // Resource is the representation of a resource in the config 68 type resource struct { 69 // Address is the absolute resource address 70 Address string `json:"address,omitempty"` 71 72 // Mode can be "managed" or "data" 73 Mode string `json:"mode,omitempty"` 74 75 Type string `json:"type,omitempty"` 76 Name string `json:"name,omitempty"` 77 78 // ProviderConfigKey is the key into "provider_configs" (shown above) for 79 // the provider configuration that this resource is associated with. 80 // 81 // NOTE: If a given resource is in a ModuleCall, and the provider was 82 // configured outside of the module (in a higher level configuration file), 83 // the ProviderConfigKey will not match a key in the ProviderConfigs map. 84 ProviderConfigKey string `json:"provider_config_key,omitempty"` 85 86 // Provisioners is an optional field which describes any provisioners. 87 // Connection info will not be included here. 88 Provisioners []provisioner `json:"provisioners,omitempty"` 89 90 // Expressions" describes the resource-type-specific content of the 91 // configuration block. 92 Expressions map[string]interface{} `json:"expressions,omitempty"` 93 94 // SchemaVersion indicates which version of the resource type schema the 95 // "values" property conforms to. 96 SchemaVersion uint64 `json:"schema_version"` 97 98 // CountExpression and ForEachExpression describe the expressions given for 99 // the corresponding meta-arguments in the resource configuration block. 100 // These are omitted if the corresponding argument isn't set. 101 CountExpression *expression `json:"count_expression,omitempty"` 102 ForEachExpression *expression `json:"for_each_expression,omitempty"` 103 104 DependsOn []string `json:"depends_on,omitempty"` 105 } 106 107 type output struct { 108 Sensitive bool `json:"sensitive,omitempty"` 109 Expression expression `json:"expression,omitempty"` 110 DependsOn []string `json:"depends_on,omitempty"` 111 Description string `json:"description,omitempty"` 112 } 113 114 type provisioner struct { 115 Type string `json:"type,omitempty"` 116 Expressions map[string]interface{} `json:"expressions,omitempty"` 117 } 118 119 // Marshal returns the json encoding of terraform configuration. 120 func Marshal(c *configs.Config, schemas *terraform.Schemas) ([]byte, error) { 121 var output config 122 123 pcs := make(map[string]providerConfig) 124 marshalProviderConfigs(c, schemas, pcs) 125 126 rootModule, err := marshalModule(c, schemas, "") 127 if err != nil { 128 return nil, err 129 } 130 output.RootModule = rootModule 131 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 141 ret, err := json.Marshal(output) 142 return ret, err 143 } 144 145 func marshalProviderConfigs( 146 c *configs.Config, 147 schemas *terraform.Schemas, 148 m map[string]providerConfig, 149 ) { 150 if c == nil { 151 return 152 } 153 154 // We want to determine only the provider requirements from this module, 155 // ignoring any descendants. Disregard any diagnostics when determining 156 // requirements because we want this marshalling to succeed even if there 157 // are invalid constraints. 158 reqs, _ := c.ProviderRequirementsShallow() 159 160 // Add an entry for each provider configuration block in the module. 161 for k, pc := range c.Module.ProviderConfigs { 162 providerFqn := c.ProviderForConfigAddr(addrs.LocalProviderConfig{LocalName: pc.Name}) 163 schema := schemas.ProviderConfig(providerFqn) 164 165 p := providerConfig{ 166 Name: pc.Name, 167 FullName: providerFqn.String(), 168 Alias: pc.Alias, 169 ModuleAddress: c.Path.String(), 170 Expressions: marshalExpressions(pc.Config, schema), 171 } 172 173 // Store the fully resolved provider version constraint, rather than 174 // using the version argument in the configuration block. This is both 175 // future proof (for when we finish the deprecation of the provider config 176 // version argument) and more accurate (as it reflects the full set of 177 // constraints, in case there are multiple). 178 if vc, ok := reqs[providerFqn]; ok { 179 p.VersionConstraint = getproviders.VersionConstraintsString(vc) 180 } 181 182 key := opaqueProviderKey(k, c.Path.String()) 183 184 m[key] = p 185 } 186 187 // Ensure that any required providers with no associated configuration 188 // block are included in the set. 189 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 214 // If there exists a value for this provider, we have nothing to add 215 // to it, so skip. 216 key := opaqueProviderKey(k, c.Path.String()) 217 if _, exists := m[key]; exists { 218 continue 219 } 220 221 // Given no provider configuration block exists, the only fields we can 222 // fill here are the local name, module address, and version 223 // constraints. 224 p := providerConfig{ 225 Name: pr.Name, 226 FullName: pr.Type.String(), 227 ModuleAddress: c.Path.String(), 228 } 229 230 if vc, ok := reqs[pr.Type]; ok { 231 p.VersionConstraint = getproviders.VersionConstraintsString(vc) 232 } 233 234 if c.Parent != nil { 235 parentKey := opaqueProviderKey(pr.Name, c.Parent.Path.String()) 236 p.parentKey = findSourceProviderKey(parentKey, p.FullName, m) 237 } 238 239 m[key] = p 240 } 241 242 // Providers could be implicitly created or inherited from the parent module 243 // when no requirements and configuration block defined. 244 for req := range reqs { 245 // Only default providers could implicitly exist, 246 // so the provider name must be same as the provider type. 247 key := opaqueProviderKey(req.Type, c.Path.String()) 248 if _, exists := m[key]; exists { 249 continue 250 } 251 252 p := providerConfig{ 253 Name: req.Type, 254 FullName: req.String(), 255 ModuleAddress: c.Path.String(), 256 } 257 258 // In child modules, providers defined in the parent module can be implicitly used. 259 if c.Parent != nil { 260 parentKey := opaqueProviderKey(req.Type, c.Parent.Path.String()) 261 p.parentKey = findSourceProviderKey(parentKey, p.FullName, m) 262 } 263 264 m[key] = p 265 } 266 267 // Must also visit our child modules, recursively. 268 for name, mc := range c.Module.ModuleCalls { 269 // Keys in c.Children are guaranteed to match those in c.Module.ModuleCalls 270 cc := c.Children[name] 271 272 // Add provider config map entries for passed provider configs, 273 // pointing at the passed configuration 274 for _, ppc := range mc.Providers { 275 // These provider names include aliases, if set 276 moduleProviderName := ppc.InChild.String() 277 parentProviderName := ppc.InParent.String() 278 279 // Look up the provider FQN from the module context, using the non-aliased local name 280 providerFqn := cc.ProviderForConfigAddr(addrs.LocalProviderConfig{LocalName: ppc.InChild.Name}) 281 282 // The presence of passed provider configs means that we cannot have 283 // any configuration expressions or version constraints here 284 p := providerConfig{ 285 Name: moduleProviderName, 286 FullName: providerFqn.String(), 287 ModuleAddress: cc.Path.String(), 288 } 289 290 key := opaqueProviderKey(moduleProviderName, cc.Path.String()) 291 parentKey := opaqueProviderKey(parentProviderName, cc.Parent.Path.String()) 292 p.parentKey = findSourceProviderKey(parentKey, p.FullName, m) 293 294 m[key] = p 295 } 296 297 // Finally, marshal any other provider configs within the called module. 298 // It is safe to do this last because it is invalid to configure a 299 // provider which has passed provider configs in the module call. 300 marshalProviderConfigs(cc, schemas, m) 301 } 302 } 303 304 func marshalModule(c *configs.Config, schemas *terraform.Schemas, addr string) (module, error) { 305 var module module 306 var rs []resource 307 308 managedResources, err := marshalResources(c.Module.ManagedResources, schemas, addr) 309 if err != nil { 310 return module, err 311 } 312 dataResources, err := marshalResources(c.Module.DataResources, schemas, addr) 313 if err != nil { 314 return module, err 315 } 316 317 rs = append(managedResources, dataResources...) 318 module.Resources = rs 319 320 outputs := make(map[string]output) 321 for _, v := range c.Module.Outputs { 322 o := output{ 323 Sensitive: v.Sensitive, 324 Expression: marshalExpression(v.Expr), 325 } 326 if v.Description != "" { 327 o.Description = v.Description 328 } 329 if len(v.DependsOn) > 0 { 330 dependencies := make([]string, len(v.DependsOn)) 331 for i, d := range v.DependsOn { 332 ref, diags := addrs.ParseRef(d) 333 // we should not get an error here, because `terraform validate` 334 // would have complained well before this point, but if we do we'll 335 // silenty skip it. 336 if !diags.HasErrors() { 337 dependencies[i] = ref.Subject.String() 338 } 339 } 340 o.DependsOn = dependencies 341 } 342 343 outputs[v.Name] = o 344 } 345 module.Outputs = outputs 346 347 module.ModuleCalls = marshalModuleCalls(c, schemas) 348 349 if len(c.Module.Variables) > 0 { 350 vars := make(variables, len(c.Module.Variables)) 351 for k, v := range c.Module.Variables { 352 var defaultValJSON []byte 353 if v.Default == cty.NilVal { 354 defaultValJSON = nil 355 } else { 356 defaultValJSON, err = ctyjson.Marshal(v.Default, v.Default.Type()) 357 if err != nil { 358 return module, err 359 } 360 } 361 vars[k] = &variable{ 362 Default: defaultValJSON, 363 Description: v.Description, 364 Sensitive: v.Sensitive, 365 } 366 } 367 module.Variables = vars 368 } 369 370 return module, nil 371 } 372 373 func marshalModuleCalls(c *configs.Config, schemas *terraform.Schemas) map[string]moduleCall { 374 ret := make(map[string]moduleCall) 375 376 for name, mc := range c.Module.ModuleCalls { 377 mcConfig := c.Children[name] 378 ret[name] = marshalModuleCall(mcConfig, mc, schemas) 379 } 380 381 return ret 382 } 383 384 func marshalModuleCall(c *configs.Config, mc *configs.ModuleCall, schemas *terraform.Schemas) moduleCall { 385 // It is possible to have a module call with a nil config. 386 if c == nil { 387 return moduleCall{} 388 } 389 390 ret := moduleCall{ 391 // We're intentionally echoing back exactly what the user entered 392 // here, rather than the normalized version in SourceAddr, because 393 // historically we only _had_ the raw address and thus it would be 394 // a (admittedly minor) breaking change to start normalizing them 395 // now, in case consumers of this data are expecting a particular 396 // non-normalized syntax. 397 Source: mc.SourceAddrRaw, 398 VersionConstraint: mc.Version.Required.String(), 399 } 400 cExp := marshalExpression(mc.Count) 401 if !cExp.Empty() { 402 ret.CountExpression = &cExp 403 } else { 404 fExp := marshalExpression(mc.ForEach) 405 if !fExp.Empty() { 406 ret.ForEachExpression = &fExp 407 } 408 } 409 410 schema := &configschema.Block{} 411 schema.Attributes = make(map[string]*configschema.Attribute) 412 for _, variable := range c.Module.Variables { 413 schema.Attributes[variable.Name] = &configschema.Attribute{ 414 Required: variable.Default == cty.NilVal, 415 } 416 } 417 418 ret.Expressions = marshalExpressions(mc.Config, schema) 419 420 module, _ := marshalModule(c, schemas, c.Path.String()) 421 422 ret.Module = module 423 424 if len(mc.DependsOn) > 0 { 425 dependencies := make([]string, len(mc.DependsOn)) 426 for i, d := range mc.DependsOn { 427 ref, diags := addrs.ParseRef(d) 428 // we should not get an error here, because `terraform validate` 429 // would have complained well before this point, but if we do we'll 430 // silenty skip it. 431 if !diags.HasErrors() { 432 dependencies[i] = ref.Subject.String() 433 } 434 } 435 ret.DependsOn = dependencies 436 } 437 438 return ret 439 } 440 441 func marshalResources(resources map[string]*configs.Resource, schemas *terraform.Schemas, moduleAddr string) ([]resource, error) { 442 var rs []resource 443 for _, v := range resources { 444 providerConfigKey := opaqueProviderKey(v.ProviderConfigAddr().StringCompact(), moduleAddr) 445 r := resource{ 446 Address: v.Addr().String(), 447 Type: v.Type, 448 Name: v.Name, 449 ProviderConfigKey: providerConfigKey, 450 } 451 452 switch v.Mode { 453 case addrs.ManagedResourceMode: 454 r.Mode = "managed" 455 case addrs.DataResourceMode: 456 r.Mode = "data" 457 default: 458 return rs, fmt.Errorf("resource %s has an unsupported mode %s", r.Address, v.Mode.String()) 459 } 460 461 cExp := marshalExpression(v.Count) 462 if !cExp.Empty() { 463 r.CountExpression = &cExp 464 } else { 465 fExp := marshalExpression(v.ForEach) 466 if !fExp.Empty() { 467 r.ForEachExpression = &fExp 468 } 469 } 470 471 schema, schemaVer := schemas.ResourceTypeConfig( 472 v.Provider, 473 v.Mode, 474 v.Type, 475 ) 476 if schema == nil { 477 return nil, fmt.Errorf("no schema found for %s (in provider %s)", v.Addr().String(), v.Provider) 478 } 479 r.SchemaVersion = schemaVer 480 481 r.Expressions = marshalExpressions(v.Config, schema) 482 483 // Managed is populated only for Mode = addrs.ManagedResourceMode 484 if v.Managed != nil && len(v.Managed.Provisioners) > 0 { 485 var provisioners []provisioner 486 for _, p := range v.Managed.Provisioners { 487 schema := schemas.ProvisionerConfig(p.Type) 488 prov := provisioner{ 489 Type: p.Type, 490 Expressions: marshalExpressions(p.Config, schema), 491 } 492 provisioners = append(provisioners, prov) 493 } 494 r.Provisioners = provisioners 495 } 496 497 if len(v.DependsOn) > 0 { 498 dependencies := make([]string, len(v.DependsOn)) 499 for i, d := range v.DependsOn { 500 ref, diags := addrs.ParseRef(d) 501 // we should not get an error here, because `terraform validate` 502 // would have complained well before this point, but if we do we'll 503 // silenty skip it. 504 if !diags.HasErrors() { 505 dependencies[i] = ref.Subject.String() 506 } 507 } 508 r.DependsOn = dependencies 509 } 510 511 rs = append(rs, r) 512 } 513 sort.Slice(rs, func(i, j int) bool { 514 return rs[i].Address < rs[j].Address 515 }) 516 return rs, nil 517 } 518 519 // Flatten all resource provider keys in a module and its descendents, such 520 // that any resources from providers using a configuration passed through the 521 // module call have a direct refernce to that provider configuration. 522 func normalizeModuleProviderKeys(m *module, pcs map[string]providerConfig) { 523 for i, r := range m.Resources { 524 if pc, exists := pcs[r.ProviderConfigKey]; exists { 525 if _, hasParent := pcs[pc.parentKey]; hasParent { 526 m.Resources[i].ProviderConfigKey = pc.parentKey 527 } 528 } 529 } 530 531 for _, mc := range m.ModuleCalls { 532 normalizeModuleProviderKeys(&mc.Module, pcs) 533 } 534 } 535 536 // opaqueProviderKey generates a unique absProviderConfig-like string from the module 537 // address and provider 538 func opaqueProviderKey(provider string, addr string) (key string) { 539 key = provider 540 if addr != "" { 541 key = fmt.Sprintf("%s:%s", addr, provider) 542 } 543 return key 544 } 545 546 // Traverse up the module call tree until we find the provider 547 // configuration which has no linked parent config. This is then 548 // the source of the configuration used in this module call, so 549 // we link to it directly 550 func findSourceProviderKey(startKey string, fullName string, m map[string]providerConfig) string { 551 var parentKey string 552 553 key := startKey 554 for key != "" { 555 parent, exists := m[key] 556 if !exists || parent.FullName != fullName { 557 break 558 } 559 560 parentKey = key 561 key = parent.parentKey 562 } 563 564 return parentKey 565 }