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  }