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  }