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