github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/command/views/add.go (about)

     1  package views
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/hcl/v2/hclwrite"
    10  	"github.com/hashicorp/terraform/internal/addrs"
    11  	"github.com/hashicorp/terraform/internal/command/arguments"
    12  	"github.com/hashicorp/terraform/internal/configs/configschema"
    13  	"github.com/hashicorp/terraform/internal/lang/marks"
    14  	"github.com/hashicorp/terraform/internal/tfdiags"
    15  	"github.com/zclconf/go-cty/cty"
    16  )
    17  
    18  // Add is the view interface for the "terraform add" command.
    19  type Add interface {
    20  	Resource(addrs.AbsResourceInstance, *configschema.Block, addrs.LocalProviderConfig, cty.Value) error
    21  	Diagnostics(tfdiags.Diagnostics)
    22  }
    23  
    24  // NewAdd returns an initialized Validate implementation. At this time,
    25  // ViewHuman is the only implemented view type.
    26  func NewAdd(vt arguments.ViewType, view *View, args *arguments.Add) Add {
    27  	return &addHuman{
    28  		view:     view,
    29  		optional: args.Optional,
    30  		outPath:  args.OutPath,
    31  	}
    32  }
    33  
    34  type addHuman struct {
    35  	view     *View
    36  	optional bool
    37  	outPath  string
    38  }
    39  
    40  func (v *addHuman) Resource(addr addrs.AbsResourceInstance, schema *configschema.Block, pc addrs.LocalProviderConfig, stateVal cty.Value) error {
    41  	var buf strings.Builder
    42  
    43  	buf.WriteString(`# NOTE: The "terraform add" command is currently experimental and offers only a
    44  # starting point for your resource configuration, with some limitations.
    45  #
    46  # The behavior of this command may change in future based on feedback, possibly
    47  # in incompatible ways. We don't recommend building automation around this
    48  # command at this time. If you have feedback about this command, please open
    49  # a feature request issue in the Terraform GitHub repository.
    50  `)
    51  
    52  	buf.WriteString(fmt.Sprintf("resource %q %q {\n", addr.Resource.Resource.Type, addr.Resource.Resource.Name))
    53  
    54  	if pc.LocalName != addr.Resource.Resource.ImpliedProvider() || pc.Alias != "" {
    55  		buf.WriteString(strings.Repeat(" ", 2))
    56  		buf.WriteString(fmt.Sprintf("provider = %s\n\n", pc.StringCompact()))
    57  	}
    58  
    59  	if stateVal.RawEquals(cty.NilVal) {
    60  		if err := v.writeConfigAttributes(&buf, schema.Attributes, 2); err != nil {
    61  			return err
    62  		}
    63  		if err := v.writeConfigBlocks(&buf, schema.BlockTypes, 2); err != nil {
    64  			return err
    65  		}
    66  	} else {
    67  		if err := v.writeConfigAttributesFromExisting(&buf, stateVal, schema.Attributes, 2); err != nil {
    68  			return err
    69  		}
    70  		if err := v.writeConfigBlocksFromExisting(&buf, stateVal, schema.BlockTypes, 2); err != nil {
    71  			return err
    72  		}
    73  	}
    74  
    75  	buf.WriteString("}")
    76  
    77  	// The output better be valid HCL which can be parsed and formatted.
    78  	formatted := hclwrite.Format([]byte(buf.String()))
    79  
    80  	var err error
    81  	if v.outPath == "" {
    82  		_, err = v.view.streams.Println(string(formatted))
    83  		return err
    84  	} else {
    85  		// The Println call above adds this final newline automatically; we add it manually here.
    86  		formatted = append(formatted, '\n')
    87  		return os.WriteFile(v.outPath, formatted, 0600)
    88  	}
    89  }
    90  
    91  func (v *addHuman) Diagnostics(diags tfdiags.Diagnostics) {
    92  	v.view.Diagnostics(diags)
    93  }
    94  
    95  func (v *addHuman) writeConfigAttributes(buf *strings.Builder, attrs map[string]*configschema.Attribute, indent int) error {
    96  	if len(attrs) == 0 {
    97  		return nil
    98  	}
    99  
   100  	// Get a list of sorted attribute names so the output will be consistent between runs.
   101  	keys := make([]string, 0, len(attrs))
   102  	for k := range attrs {
   103  		keys = append(keys, k)
   104  	}
   105  	sort.Strings(keys)
   106  
   107  	for i := range keys {
   108  		name := keys[i]
   109  		attrS := attrs[name]
   110  		if attrS.NestedType != nil {
   111  			if err := v.writeConfigNestedTypeAttribute(buf, name, attrS, indent); err != nil {
   112  				return err
   113  			}
   114  			continue
   115  		}
   116  		if attrS.Required {
   117  			buf.WriteString(strings.Repeat(" ", indent))
   118  			buf.WriteString(fmt.Sprintf("%s = ", name))
   119  			tok := hclwrite.TokensForValue(attrS.EmptyValue())
   120  			if _, err := tok.WriteTo(buf); err != nil {
   121  				return err
   122  			}
   123  			writeAttrTypeConstraint(buf, attrS)
   124  		} else if attrS.Optional && v.optional {
   125  			buf.WriteString(strings.Repeat(" ", indent))
   126  			buf.WriteString(fmt.Sprintf("%s = ", name))
   127  			tok := hclwrite.TokensForValue(attrS.EmptyValue())
   128  			if _, err := tok.WriteTo(buf); err != nil {
   129  				return err
   130  			}
   131  			writeAttrTypeConstraint(buf, attrS)
   132  		}
   133  	}
   134  	return nil
   135  }
   136  
   137  func (v *addHuman) writeConfigAttributesFromExisting(buf *strings.Builder, stateVal cty.Value, attrs map[string]*configschema.Attribute, indent int) error {
   138  	if len(attrs) == 0 {
   139  		return nil
   140  	}
   141  
   142  	// Get a list of sorted attribute names so the output will be consistent between runs.
   143  	keys := make([]string, 0, len(attrs))
   144  	for k := range attrs {
   145  		keys = append(keys, k)
   146  	}
   147  	sort.Strings(keys)
   148  
   149  	for i := range keys {
   150  		name := keys[i]
   151  		attrS := attrs[name]
   152  		if attrS.NestedType != nil {
   153  			if err := v.writeConfigNestedTypeAttributeFromExisting(buf, name, attrS, stateVal, indent); err != nil {
   154  				return err
   155  			}
   156  			continue
   157  		}
   158  
   159  		// Exclude computed-only attributes
   160  		if attrS.Required || attrS.Optional {
   161  			buf.WriteString(strings.Repeat(" ", indent))
   162  			buf.WriteString(fmt.Sprintf("%s = ", name))
   163  
   164  			var val cty.Value
   165  			if stateVal.Type().HasAttribute(name) {
   166  				val = stateVal.GetAttr(name)
   167  			} else {
   168  				val = attrS.EmptyValue()
   169  			}
   170  			if attrS.Sensitive || val.HasMark(marks.Sensitive) {
   171  				buf.WriteString("null # sensitive")
   172  			} else {
   173  				val, _ = val.Unmark()
   174  				tok := hclwrite.TokensForValue(val)
   175  				if _, err := tok.WriteTo(buf); err != nil {
   176  					return err
   177  				}
   178  			}
   179  
   180  			buf.WriteString("\n")
   181  		}
   182  	}
   183  	return nil
   184  }
   185  
   186  func (v *addHuman) writeConfigBlocks(buf *strings.Builder, blocks map[string]*configschema.NestedBlock, indent int) error {
   187  	if len(blocks) == 0 {
   188  		return nil
   189  	}
   190  
   191  	// Get a list of sorted block names so the output will be consistent between runs.
   192  	names := make([]string, 0, len(blocks))
   193  	for k := range blocks {
   194  		names = append(names, k)
   195  	}
   196  	sort.Strings(names)
   197  
   198  	for i := range names {
   199  		name := names[i]
   200  		blockS := blocks[name]
   201  		if err := v.writeConfigNestedBlock(buf, name, blockS, indent); err != nil {
   202  			return err
   203  		}
   204  	}
   205  	return nil
   206  }
   207  
   208  func (v *addHuman) writeConfigNestedBlock(buf *strings.Builder, name string, schema *configschema.NestedBlock, indent int) error {
   209  	if !v.optional && schema.MinItems == 0 {
   210  		return nil
   211  	}
   212  
   213  	switch schema.Nesting {
   214  	case configschema.NestingSingle, configschema.NestingGroup:
   215  		buf.WriteString(strings.Repeat(" ", indent))
   216  		buf.WriteString(fmt.Sprintf("%s {", name))
   217  		writeBlockTypeConstraint(buf, schema)
   218  		if err := v.writeConfigAttributes(buf, schema.Attributes, indent+2); err != nil {
   219  			return err
   220  		}
   221  		if err := v.writeConfigBlocks(buf, schema.BlockTypes, indent+2); err != nil {
   222  			return err
   223  		}
   224  		buf.WriteString("}\n")
   225  		return nil
   226  	case configschema.NestingList, configschema.NestingSet:
   227  		buf.WriteString(strings.Repeat(" ", indent))
   228  		buf.WriteString(fmt.Sprintf("%s {", name))
   229  		writeBlockTypeConstraint(buf, schema)
   230  		if err := v.writeConfigAttributes(buf, schema.Attributes, indent+2); err != nil {
   231  			return err
   232  		}
   233  		if err := v.writeConfigBlocks(buf, schema.BlockTypes, indent+2); err != nil {
   234  			return err
   235  		}
   236  		buf.WriteString("}\n")
   237  		return nil
   238  	case configschema.NestingMap:
   239  		buf.WriteString(strings.Repeat(" ", indent))
   240  		// we use an arbitrary placeholder key (block label) "key"
   241  		buf.WriteString(fmt.Sprintf("%s \"key\" {", name))
   242  		writeBlockTypeConstraint(buf, schema)
   243  		if err := v.writeConfigAttributes(buf, schema.Attributes, indent+2); err != nil {
   244  			return err
   245  		}
   246  		if err := v.writeConfigBlocks(buf, schema.BlockTypes, indent+2); err != nil {
   247  			return err
   248  		}
   249  		buf.WriteString(strings.Repeat(" ", indent))
   250  		buf.WriteString("}\n")
   251  		return nil
   252  	default:
   253  		// This should not happen, the above should be exhaustive.
   254  		return fmt.Errorf("unsupported NestingMode %s", schema.Nesting.String())
   255  	}
   256  }
   257  
   258  func (v *addHuman) writeConfigNestedTypeAttribute(buf *strings.Builder, name string, schema *configschema.Attribute, indent int) error {
   259  	if !schema.Required && !v.optional {
   260  		return nil
   261  	}
   262  
   263  	buf.WriteString(strings.Repeat(" ", indent))
   264  	buf.WriteString(fmt.Sprintf("%s = ", name))
   265  
   266  	switch schema.NestedType.Nesting {
   267  	case configschema.NestingSingle:
   268  		buf.WriteString("{")
   269  		writeAttrTypeConstraint(buf, schema)
   270  		if err := v.writeConfigAttributes(buf, schema.NestedType.Attributes, indent+2); err != nil {
   271  			return err
   272  		}
   273  		buf.WriteString(strings.Repeat(" ", indent))
   274  		buf.WriteString("}\n")
   275  		return nil
   276  	case configschema.NestingList, configschema.NestingSet:
   277  		buf.WriteString("[{")
   278  		writeAttrTypeConstraint(buf, schema)
   279  		if err := v.writeConfigAttributes(buf, schema.NestedType.Attributes, indent+2); err != nil {
   280  			return err
   281  		}
   282  		buf.WriteString(strings.Repeat(" ", indent))
   283  		buf.WriteString("}]\n")
   284  		return nil
   285  	case configschema.NestingMap:
   286  		buf.WriteString("{")
   287  		writeAttrTypeConstraint(buf, schema)
   288  		buf.WriteString(strings.Repeat(" ", indent+2))
   289  		// we use an arbitrary placeholder key "key"
   290  		buf.WriteString("key = {\n")
   291  		if err := v.writeConfigAttributes(buf, schema.NestedType.Attributes, indent+4); err != nil {
   292  			return err
   293  		}
   294  		buf.WriteString(strings.Repeat(" ", indent+2))
   295  		buf.WriteString("}\n")
   296  		buf.WriteString(strings.Repeat(" ", indent))
   297  		buf.WriteString("}\n")
   298  		return nil
   299  	default:
   300  		// This should not happen, the above should be exhaustive.
   301  		return fmt.Errorf("unsupported NestingMode %s", schema.NestedType.Nesting.String())
   302  	}
   303  }
   304  
   305  func (v *addHuman) writeConfigBlocksFromExisting(buf *strings.Builder, stateVal cty.Value, blocks map[string]*configschema.NestedBlock, indent int) error {
   306  	if len(blocks) == 0 {
   307  		return nil
   308  	}
   309  
   310  	// Get a list of sorted block names so the output will be consistent between runs.
   311  	names := make([]string, 0, len(blocks))
   312  	for k := range blocks {
   313  		names = append(names, k)
   314  	}
   315  	sort.Strings(names)
   316  
   317  	for _, name := range names {
   318  		blockS := blocks[name]
   319  		// This shouldn't happen in real usage; state always has all values (set
   320  		// to null as needed), but it protects against panics in tests (and any
   321  		// really weird and unlikely cases).
   322  		if !stateVal.Type().HasAttribute(name) {
   323  			continue
   324  		}
   325  		blockVal := stateVal.GetAttr(name)
   326  		if err := v.writeConfigNestedBlockFromExisting(buf, name, blockS, blockVal, indent); err != nil {
   327  			return err
   328  		}
   329  	}
   330  
   331  	return nil
   332  }
   333  
   334  func (v *addHuman) writeConfigNestedTypeAttributeFromExisting(buf *strings.Builder, name string, schema *configschema.Attribute, stateVal cty.Value, indent int) error {
   335  	switch schema.NestedType.Nesting {
   336  	case configschema.NestingSingle:
   337  		if schema.Sensitive || stateVal.HasMark(marks.Sensitive) {
   338  			buf.WriteString(strings.Repeat(" ", indent))
   339  			buf.WriteString(fmt.Sprintf("%s = {} # sensitive\n", name))
   340  			return nil
   341  		}
   342  		buf.WriteString(strings.Repeat(" ", indent))
   343  		buf.WriteString(fmt.Sprintf("%s = {\n", name))
   344  
   345  		// This shouldn't happen in real usage; state always has all values (set
   346  		// to null as needed), but it protects against panics in tests (and any
   347  		// really weird and unlikely cases).
   348  		if !stateVal.Type().HasAttribute(name) {
   349  			return nil
   350  		}
   351  		nestedVal := stateVal.GetAttr(name)
   352  		if err := v.writeConfigAttributesFromExisting(buf, nestedVal, schema.NestedType.Attributes, indent+2); err != nil {
   353  			return err
   354  		}
   355  		buf.WriteString("}\n")
   356  		return nil
   357  
   358  	case configschema.NestingList, configschema.NestingSet:
   359  		buf.WriteString(strings.Repeat(" ", indent))
   360  		buf.WriteString(fmt.Sprintf("%s = [", name))
   361  
   362  		if schema.Sensitive || stateVal.HasMark(marks.Sensitive) {
   363  			buf.WriteString("] # sensitive\n")
   364  			return nil
   365  		}
   366  
   367  		buf.WriteString("\n")
   368  
   369  		listVals := ctyCollectionValues(stateVal.GetAttr(name))
   370  		for i := range listVals {
   371  			buf.WriteString(strings.Repeat(" ", indent+2))
   372  
   373  			// The entire element is marked.
   374  			if listVals[i].HasMark(marks.Sensitive) {
   375  				buf.WriteString("{}, # sensitive\n")
   376  				continue
   377  			}
   378  
   379  			buf.WriteString("{\n")
   380  			if err := v.writeConfigAttributesFromExisting(buf, listVals[i], schema.NestedType.Attributes, indent+4); err != nil {
   381  				return err
   382  			}
   383  			buf.WriteString(strings.Repeat(" ", indent+2))
   384  			buf.WriteString("},\n")
   385  		}
   386  		buf.WriteString(strings.Repeat(" ", indent))
   387  		buf.WriteString("]\n")
   388  		return nil
   389  
   390  	case configschema.NestingMap:
   391  		buf.WriteString(strings.Repeat(" ", indent))
   392  		buf.WriteString(fmt.Sprintf("%s = {", name))
   393  
   394  		if schema.Sensitive || stateVal.HasMark(marks.Sensitive) {
   395  			buf.WriteString(" } # sensitive\n")
   396  			return nil
   397  		}
   398  
   399  		buf.WriteString("\n")
   400  
   401  		vals := stateVal.GetAttr(name).AsValueMap()
   402  		keys := make([]string, 0, len(vals))
   403  		for key := range vals {
   404  			keys = append(keys, key)
   405  		}
   406  		sort.Strings(keys)
   407  		for _, key := range keys {
   408  			buf.WriteString(strings.Repeat(" ", indent+2))
   409  			buf.WriteString(fmt.Sprintf("%s = {", key))
   410  
   411  			// This entire value is marked
   412  			if vals[key].HasMark(marks.Sensitive) {
   413  				buf.WriteString("} # sensitive\n")
   414  				continue
   415  			}
   416  
   417  			buf.WriteString("\n")
   418  			if err := v.writeConfigAttributesFromExisting(buf, vals[key], schema.NestedType.Attributes, indent+4); err != nil {
   419  				return err
   420  			}
   421  			buf.WriteString(strings.Repeat(" ", indent+2))
   422  			buf.WriteString("}\n")
   423  		}
   424  		buf.WriteString(strings.Repeat(" ", indent))
   425  		buf.WriteString("}\n")
   426  		return nil
   427  
   428  	default:
   429  		// This should not happen, the above should be exhaustive.
   430  		return fmt.Errorf("unsupported NestingMode %s", schema.NestedType.Nesting.String())
   431  	}
   432  }
   433  
   434  func (v *addHuman) writeConfigNestedBlockFromExisting(buf *strings.Builder, name string, schema *configschema.NestedBlock, stateVal cty.Value, indent int) error {
   435  	switch schema.Nesting {
   436  	case configschema.NestingSingle, configschema.NestingGroup:
   437  		buf.WriteString(strings.Repeat(" ", indent))
   438  		buf.WriteString(fmt.Sprintf("%s {", name))
   439  
   440  		// If the entire value is marked, don't print any nested attributes
   441  		if stateVal.HasMark(marks.Sensitive) {
   442  			buf.WriteString("} # sensitive\n")
   443  			return nil
   444  		}
   445  		buf.WriteString("\n")
   446  		if err := v.writeConfigAttributesFromExisting(buf, stateVal, schema.Attributes, indent+2); err != nil {
   447  			return err
   448  		}
   449  		if err := v.writeConfigBlocksFromExisting(buf, stateVal, schema.BlockTypes, indent+2); err != nil {
   450  			return err
   451  		}
   452  		buf.WriteString("}\n")
   453  		return nil
   454  	case configschema.NestingList, configschema.NestingSet:
   455  		if stateVal.HasMark(marks.Sensitive) {
   456  			buf.WriteString(strings.Repeat(" ", indent))
   457  			buf.WriteString(fmt.Sprintf("%s {} # sensitive\n", name))
   458  			return nil
   459  		}
   460  		listVals := ctyCollectionValues(stateVal)
   461  		for i := range listVals {
   462  			buf.WriteString(strings.Repeat(" ", indent))
   463  			buf.WriteString(fmt.Sprintf("%s {\n", name))
   464  			if err := v.writeConfigAttributesFromExisting(buf, listVals[i], schema.Attributes, indent+2); err != nil {
   465  				return err
   466  			}
   467  			if err := v.writeConfigBlocksFromExisting(buf, listVals[i], schema.BlockTypes, indent+2); err != nil {
   468  				return err
   469  			}
   470  			buf.WriteString("}\n")
   471  		}
   472  		return nil
   473  	case configschema.NestingMap:
   474  		// If the entire value is marked, don't print any nested attributes
   475  		if stateVal.HasMark(marks.Sensitive) {
   476  			buf.WriteString(fmt.Sprintf("%s {} # sensitive\n", name))
   477  			return nil
   478  		}
   479  
   480  		vals := stateVal.AsValueMap()
   481  		keys := make([]string, 0, len(vals))
   482  		for key := range vals {
   483  			keys = append(keys, key)
   484  		}
   485  		sort.Strings(keys)
   486  		for _, key := range keys {
   487  			buf.WriteString(strings.Repeat(" ", indent))
   488  			buf.WriteString(fmt.Sprintf("%s %q {", name, key))
   489  			// This entire map element is marked
   490  			if vals[key].HasMark(marks.Sensitive) {
   491  				buf.WriteString("} # sensitive\n")
   492  				return nil
   493  			}
   494  			buf.WriteString("\n")
   495  
   496  			if err := v.writeConfigAttributesFromExisting(buf, vals[key], schema.Attributes, indent+2); err != nil {
   497  				return err
   498  			}
   499  			if err := v.writeConfigBlocksFromExisting(buf, vals[key], schema.BlockTypes, indent+2); err != nil {
   500  				return err
   501  			}
   502  			buf.WriteString(strings.Repeat(" ", indent))
   503  			buf.WriteString("}\n")
   504  		}
   505  		return nil
   506  	default:
   507  		// This should not happen, the above should be exhaustive.
   508  		return fmt.Errorf("unsupported NestingMode %s", schema.Nesting.String())
   509  	}
   510  }
   511  
   512  func writeAttrTypeConstraint(buf *strings.Builder, schema *configschema.Attribute) {
   513  	if schema.Required {
   514  		buf.WriteString(" # REQUIRED ")
   515  	} else {
   516  		buf.WriteString(" # OPTIONAL ")
   517  	}
   518  
   519  	if schema.NestedType != nil {
   520  		buf.WriteString(fmt.Sprintf("%s\n", schema.NestedType.ImpliedType().FriendlyName()))
   521  	} else {
   522  		buf.WriteString(fmt.Sprintf("%s\n", schema.Type.FriendlyName()))
   523  	}
   524  }
   525  
   526  func writeBlockTypeConstraint(buf *strings.Builder, schema *configschema.NestedBlock) {
   527  	if schema.MinItems > 0 {
   528  		buf.WriteString(" # REQUIRED block\n")
   529  	} else {
   530  		buf.WriteString(" # OPTIONAL block\n")
   531  	}
   532  }
   533  
   534  // copied from command/format/diff
   535  func ctyCollectionValues(val cty.Value) []cty.Value {
   536  	if !val.IsKnown() || val.IsNull() {
   537  		return nil
   538  	}
   539  
   540  	var len int
   541  	if val.IsMarked() {
   542  		val, _ = val.Unmark()
   543  		len = val.LengthInt()
   544  	} else {
   545  		len = val.LengthInt()
   546  	}
   547  
   548  	ret := make([]cty.Value, 0, len)
   549  	for it := val.ElementIterator(); it.Next(); {
   550  		_, value := it.Element()
   551  		ret = append(ret, value)
   552  	}
   553  
   554  	return ret
   555  }