github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-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/muratcelep/terraform/not-internal/addrs" 12 "github.com/muratcelep/terraform/not-internal/configs" 13 "github.com/muratcelep/terraform/not-internal/configs/configschema" 14 "github.com/muratcelep/terraform/not-internal/getproviders" 15 "github.com/muratcelep/terraform/not-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 Alias string `json:"alias,omitempty"` 31 VersionConstraint string `json:"version_constraint,omitempty"` 32 ModuleAddress string `json:"module_address,omitempty"` 33 Expressions map[string]interface{} `json:"expressions,omitempty"` 34 } 35 36 type module struct { 37 Outputs map[string]output `json:"outputs,omitempty"` 38 // Resources are sorted in a user-friendly order that is undefined at this 39 // time, but consistent. 40 Resources []resource `json:"resources,omitempty"` 41 ModuleCalls map[string]moduleCall `json:"module_calls,omitempty"` 42 Variables variables `json:"variables,omitempty"` 43 } 44 45 type moduleCall struct { 46 Source string `json:"source,omitempty"` 47 Expressions map[string]interface{} `json:"expressions,omitempty"` 48 CountExpression *expression `json:"count_expression,omitempty"` 49 ForEachExpression *expression `json:"for_each_expression,omitempty"` 50 Module module `json:"module,omitempty"` 51 VersionConstraint string `json:"version_constraint,omitempty"` 52 DependsOn []string `json:"depends_on,omitempty"` 53 } 54 55 // variables is the JSON representation of the variables provided to the current 56 // plan. 57 type variables map[string]*variable 58 59 type variable struct { 60 Default json.RawMessage `json:"default,omitempty"` 61 Description string `json:"description,omitempty"` 62 Sensitive bool `json:"sensitive,omitempty"` 63 } 64 65 // Resource is the representation of a resource in the config 66 type resource struct { 67 // Address is the absolute resource address 68 Address string `json:"address,omitempty"` 69 70 // Mode can be "managed" or "data" 71 Mode string `json:"mode,omitempty"` 72 73 Type string `json:"type,omitempty"` 74 Name string `json:"name,omitempty"` 75 76 // ProviderConfigKey is the key into "provider_configs" (shown above) for 77 // the provider configuration that this resource is associated with. 78 // 79 // NOTE: If a given resource is in a ModuleCall, and the provider was 80 // configured outside of the module (in a higher level configuration file), 81 // the ProviderConfigKey will not match a key in the ProviderConfigs map. 82 ProviderConfigKey string `json:"provider_config_key,omitempty"` 83 84 // Provisioners is an optional field which describes any provisioners. 85 // Connection info will not be included here. 86 Provisioners []provisioner `json:"provisioners,omitempty"` 87 88 // Expressions" describes the resource-type-specific content of the 89 // configuration block. 90 Expressions map[string]interface{} `json:"expressions,omitempty"` 91 92 // SchemaVersion indicates which version of the resource type schema the 93 // "values" property conforms to. 94 SchemaVersion uint64 `json:"schema_version"` 95 96 // CountExpression and ForEachExpression describe the expressions given for 97 // the corresponding meta-arguments in the resource configuration block. 98 // These are omitted if the corresponding argument isn't set. 99 CountExpression *expression `json:"count_expression,omitempty"` 100 ForEachExpression *expression `json:"for_each_expression,omitempty"` 101 102 DependsOn []string `json:"depends_on,omitempty"` 103 } 104 105 type output struct { 106 Sensitive bool `json:"sensitive,omitempty"` 107 Expression expression `json:"expression,omitempty"` 108 DependsOn []string `json:"depends_on,omitempty"` 109 Description string `json:"description,omitempty"` 110 } 111 112 type provisioner struct { 113 Type string `json:"type,omitempty"` 114 Expressions map[string]interface{} `json:"expressions,omitempty"` 115 } 116 117 // Marshal returns the json encoding of terraform configuration. 118 func Marshal(c *configs.Config, schemas *terraform.Schemas) ([]byte, error) { 119 var output config 120 121 pcs := make(map[string]providerConfig) 122 marshalProviderConfigs(c, schemas, pcs) 123 output.ProviderConfigs = pcs 124 125 rootModule, err := marshalModule(c, schemas, "") 126 if err != nil { 127 return nil, err 128 } 129 output.RootModule = rootModule 130 131 ret, err := json.Marshal(output) 132 return ret, err 133 } 134 135 func marshalProviderConfigs( 136 c *configs.Config, 137 schemas *terraform.Schemas, 138 m map[string]providerConfig, 139 ) { 140 if c == nil { 141 return 142 } 143 144 // We want to determine only the provider requirements from this module, 145 // ignoring any descendants. Disregard any diagnostics when determining 146 // requirements because we want this marshalling to succeed even if there 147 // are invalid constraints. 148 reqs, _ := c.ProviderRequirementsShallow() 149 150 // Add an entry for each provider configuration block in the module. 151 for k, pc := range c.Module.ProviderConfigs { 152 providerFqn := c.ProviderForConfigAddr(addrs.LocalProviderConfig{LocalName: pc.Name}) 153 schema := schemas.ProviderConfig(providerFqn) 154 155 p := providerConfig{ 156 Name: pc.Name, 157 Alias: pc.Alias, 158 ModuleAddress: c.Path.String(), 159 Expressions: marshalExpressions(pc.Config, schema), 160 } 161 162 // Store the fully resolved provider version constraint, rather than 163 // using the version argument in the configuration block. This is both 164 // future proof (for when we finish the deprecation of the provider config 165 // version argument) and more accurate (as it reflects the full set of 166 // constraints, in case there are multiple). 167 if vc, ok := reqs[providerFqn]; ok { 168 p.VersionConstraint = getproviders.VersionConstraintsString(vc) 169 } 170 171 key := opaqueProviderKey(k, c.Path.String()) 172 173 m[key] = p 174 } 175 176 // Ensure that any required providers with no associated configuration 177 // block are included in the set. 178 for k, pr := range c.Module.ProviderRequirements.RequiredProviders { 179 // If there exists a value for this provider, we have nothing to add 180 // to it, so skip. 181 key := opaqueProviderKey(k, c.Path.String()) 182 if _, exists := m[key]; exists { 183 continue 184 } 185 186 // Given no provider configuration block exists, the only fields we can 187 // fill here are the local name, module address, and version 188 // constraints. 189 p := providerConfig{ 190 Name: pr.Name, 191 ModuleAddress: c.Path.String(), 192 } 193 194 if vc, ok := reqs[pr.Type]; ok { 195 p.VersionConstraint = getproviders.VersionConstraintsString(vc) 196 } 197 198 m[key] = p 199 } 200 201 // Must also visit our child modules, recursively. 202 for _, cc := range c.Children { 203 marshalProviderConfigs(cc, schemas, m) 204 } 205 } 206 207 func marshalModule(c *configs.Config, schemas *terraform.Schemas, addr string) (module, error) { 208 var module module 209 var rs []resource 210 211 managedResources, err := marshalResources(c.Module.ManagedResources, schemas, addr) 212 if err != nil { 213 return module, err 214 } 215 dataResources, err := marshalResources(c.Module.DataResources, schemas, addr) 216 if err != nil { 217 return module, err 218 } 219 220 rs = append(managedResources, dataResources...) 221 module.Resources = rs 222 223 outputs := make(map[string]output) 224 for _, v := range c.Module.Outputs { 225 o := output{ 226 Sensitive: v.Sensitive, 227 Expression: marshalExpression(v.Expr), 228 } 229 if v.Description != "" { 230 o.Description = v.Description 231 } 232 if len(v.DependsOn) > 0 { 233 dependencies := make([]string, len(v.DependsOn)) 234 for i, d := range v.DependsOn { 235 ref, diags := addrs.ParseRef(d) 236 // we should not get an error here, because `terraform validate` 237 // would have complained well before this point, but if we do we'll 238 // silenty skip it. 239 if !diags.HasErrors() { 240 dependencies[i] = ref.Subject.String() 241 } 242 } 243 o.DependsOn = dependencies 244 } 245 246 outputs[v.Name] = o 247 } 248 module.Outputs = outputs 249 250 module.ModuleCalls = marshalModuleCalls(c, schemas) 251 252 if len(c.Module.Variables) > 0 { 253 vars := make(variables, len(c.Module.Variables)) 254 for k, v := range c.Module.Variables { 255 var defaultValJSON []byte 256 if v.Default == cty.NilVal { 257 defaultValJSON = nil 258 } else { 259 defaultValJSON, err = ctyjson.Marshal(v.Default, v.Default.Type()) 260 if err != nil { 261 return module, err 262 } 263 } 264 vars[k] = &variable{ 265 Default: defaultValJSON, 266 Description: v.Description, 267 Sensitive: v.Sensitive, 268 } 269 } 270 module.Variables = vars 271 } 272 273 return module, nil 274 } 275 276 func marshalModuleCalls(c *configs.Config, schemas *terraform.Schemas) map[string]moduleCall { 277 ret := make(map[string]moduleCall) 278 279 for name, mc := range c.Module.ModuleCalls { 280 mcConfig := c.Children[name] 281 ret[name] = marshalModuleCall(mcConfig, mc, schemas) 282 } 283 284 return ret 285 } 286 287 func marshalModuleCall(c *configs.Config, mc *configs.ModuleCall, schemas *terraform.Schemas) moduleCall { 288 // It is possible to have a module call with a nil config. 289 if c == nil { 290 return moduleCall{} 291 } 292 293 ret := moduleCall{ 294 // We're intentionally echoing back exactly what the user entered 295 // here, rather than the normalized version in SourceAddr, because 296 // historically we only _had_ the raw address and thus it would be 297 // a (admittedly minor) breaking change to start normalizing them 298 // now, in case consumers of this data are expecting a particular 299 // non-normalized syntax. 300 Source: mc.SourceAddrRaw, 301 VersionConstraint: mc.Version.Required.String(), 302 } 303 cExp := marshalExpression(mc.Count) 304 if !cExp.Empty() { 305 ret.CountExpression = &cExp 306 } else { 307 fExp := marshalExpression(mc.ForEach) 308 if !fExp.Empty() { 309 ret.ForEachExpression = &fExp 310 } 311 } 312 313 schema := &configschema.Block{} 314 schema.Attributes = make(map[string]*configschema.Attribute) 315 for _, variable := range c.Module.Variables { 316 schema.Attributes[variable.Name] = &configschema.Attribute{ 317 Required: variable.Default == cty.NilVal, 318 } 319 } 320 321 ret.Expressions = marshalExpressions(mc.Config, schema) 322 module, _ := marshalModule(c, schemas, mc.Name) 323 ret.Module = module 324 325 if len(mc.DependsOn) > 0 { 326 dependencies := make([]string, len(mc.DependsOn)) 327 for i, d := range mc.DependsOn { 328 ref, diags := addrs.ParseRef(d) 329 // we should not get an error here, because `terraform validate` 330 // would have complained well before this point, but if we do we'll 331 // silenty skip it. 332 if !diags.HasErrors() { 333 dependencies[i] = ref.Subject.String() 334 } 335 } 336 ret.DependsOn = dependencies 337 } 338 339 return ret 340 } 341 342 func marshalResources(resources map[string]*configs.Resource, schemas *terraform.Schemas, moduleAddr string) ([]resource, error) { 343 var rs []resource 344 for _, v := range resources { 345 r := resource{ 346 Address: v.Addr().String(), 347 Type: v.Type, 348 Name: v.Name, 349 ProviderConfigKey: opaqueProviderKey(v.ProviderConfigAddr().StringCompact(), moduleAddr), 350 } 351 352 switch v.Mode { 353 case addrs.ManagedResourceMode: 354 r.Mode = "managed" 355 case addrs.DataResourceMode: 356 r.Mode = "data" 357 default: 358 return rs, fmt.Errorf("resource %s has an unsupported mode %s", r.Address, v.Mode.String()) 359 } 360 361 cExp := marshalExpression(v.Count) 362 if !cExp.Empty() { 363 r.CountExpression = &cExp 364 } else { 365 fExp := marshalExpression(v.ForEach) 366 if !fExp.Empty() { 367 r.ForEachExpression = &fExp 368 } 369 } 370 371 schema, schemaVer := schemas.ResourceTypeConfig( 372 v.Provider, 373 v.Mode, 374 v.Type, 375 ) 376 if schema == nil { 377 return nil, fmt.Errorf("no schema found for %s (in provider %s)", v.Addr().String(), v.Provider) 378 } 379 r.SchemaVersion = schemaVer 380 381 r.Expressions = marshalExpressions(v.Config, schema) 382 383 // Managed is populated only for Mode = addrs.ManagedResourceMode 384 if v.Managed != nil && len(v.Managed.Provisioners) > 0 { 385 var provisioners []provisioner 386 for _, p := range v.Managed.Provisioners { 387 schema := schemas.ProvisionerConfig(p.Type) 388 prov := provisioner{ 389 Type: p.Type, 390 Expressions: marshalExpressions(p.Config, schema), 391 } 392 provisioners = append(provisioners, prov) 393 } 394 r.Provisioners = provisioners 395 } 396 397 if len(v.DependsOn) > 0 { 398 dependencies := make([]string, len(v.DependsOn)) 399 for i, d := range v.DependsOn { 400 ref, diags := addrs.ParseRef(d) 401 // we should not get an error here, because `terraform validate` 402 // would have complained well before this point, but if we do we'll 403 // silenty skip it. 404 if !diags.HasErrors() { 405 dependencies[i] = ref.Subject.String() 406 } 407 } 408 r.DependsOn = dependencies 409 } 410 411 rs = append(rs, r) 412 } 413 sort.Slice(rs, func(i, j int) bool { 414 return rs[i].Address < rs[j].Address 415 }) 416 return rs, nil 417 } 418 419 // opaqueProviderKey generates a unique absProviderConfig-like string from the module 420 // address and provider 421 func opaqueProviderKey(provider string, addr string) (key string) { 422 key = provider 423 if addr != "" { 424 key = fmt.Sprintf("%s:%s", addr, provider) 425 } 426 return key 427 }