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  }