diff --git a/internal/command/import.go b/internal/command/import.go index 6e0764e60b..e79054bba7 100644 --- a/internal/command/import.go +++ b/internal/command/import.go @@ -297,14 +297,6 @@ Usage: terraform [global options] import [options] ADDR ID determine the ID syntax to use. It typically matches directly to the ID that the provider uses. - The current implementation of Terraform import can only import resources - into the state. It does not generate configuration. A future version of - Terraform will also generate configuration. - - Because of this, prior to running terraform import it is necessary to write - a resource configuration block for the resource manually, to which the - imported object will be attached. - This command will not modify your infrastructure, but it will make network requests to inspect parts of your infrastructure relevant to the resource being imported. diff --git a/internal/configs/config.go b/internal/configs/config.go index ac1ff3ce24..f0e18005f6 100644 --- a/internal/configs/config.go +++ b/internal/configs/config.go @@ -394,6 +394,19 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse } reqs[fqn] = nil } + for _, i := range c.Module.Import { + implied, err := addrs.ParseProviderPart(i.To.Resource.Resource.ImpliedProvider()) + if err == nil { + provider := c.Module.ImpliedProviderForUnqualifiedType(implied) + if _, exists := reqs[provider]; exists { + // Explicit dependency already present + continue + } + reqs[provider] = nil + } + // We don't return a diagnostic here, because the invalid address will + // have been caught elsewhere. + } // "provider" block can also contain version constraints for _, provider := range c.Module.ProviderConfigs { diff --git a/internal/configs/config_test.go b/internal/configs/config_test.go index 29f3ac906b..6bf01a357d 100644 --- a/internal/configs/config_test.go +++ b/internal/configs/config_test.go @@ -34,6 +34,7 @@ func TestConfigProviderTypes(t *testing.T) { got = cfg.ProviderTypes() want := []addrs.Provider{ addrs.NewDefaultProvider("aws"), + addrs.NewDefaultProvider("local"), addrs.NewDefaultProvider("null"), addrs.NewDefaultProvider("template"), addrs.NewDefaultProvider("test"), diff --git a/internal/configs/configschema/filter.go b/internal/configs/configschema/filter.go new file mode 100644 index 0000000000..40496ca8c3 --- /dev/null +++ b/internal/configs/configschema/filter.go @@ -0,0 +1,57 @@ +package configschema + +type FilterT[T any] func(string, T) bool + +var ( + FilterReadOnlyAttributes = func(name string, attribute *Attribute) bool { + return attribute.Computed && !attribute.Optional + } + + FilterDeprecatedAttribute = func(name string, attribute *Attribute) bool { + return attribute.Deprecated + } + + FilterDeprecatedBlock = func(name string, block *NestedBlock) bool { + return block.Deprecated + } +) + +func FilterOr[T any](one, two FilterT[T]) FilterT[T] { + return func(name string, value T) bool { + return one(name, value) || two(name, value) + } +} + +func (b *Block) Filter(filterAttribute FilterT[*Attribute], filterBlock FilterT[*NestedBlock]) *Block { + ret := &Block{ + Description: b.Description, + DescriptionKind: b.DescriptionKind, + Deprecated: b.Deprecated, + } + + if b.Attributes != nil { + ret.Attributes = make(map[string]*Attribute, len(b.Attributes)) + } + for name, attrS := range b.Attributes { + if filterAttribute == nil || !filterAttribute(name, attrS) { + ret.Attributes[name] = attrS + } + } + + if b.BlockTypes != nil { + ret.BlockTypes = make(map[string]*NestedBlock, len(b.BlockTypes)) + } + for name, blockS := range b.BlockTypes { + if filterBlock == nil || !filterBlock(name, blockS) { + block := blockS.Filter(filterAttribute, filterBlock) + ret.BlockTypes[name] = &NestedBlock{ + Block: *block, + Nesting: blockS.Nesting, + MinItems: blockS.MinItems, + MaxItems: blockS.MaxItems, + } + } + } + + return ret +} diff --git a/internal/configs/testdata/valid-files/providers-explicit-implied.tf b/internal/configs/testdata/valid-files/providers-explicit-implied.tf index 778b0aa539..45d7105a54 100644 --- a/internal/configs/testdata/valid-files/providers-explicit-implied.tf +++ b/internal/configs/testdata/valid-files/providers-explicit-implied.tf @@ -14,6 +14,11 @@ resource "null_resource" "foo" { } +import { + id = "directory/filename" + to = local_file.foo +} + terraform { required_providers { test = { diff --git a/internal/genconfig/doc.go b/internal/genconfig/doc.go new file mode 100644 index 0000000000..c1d7fd46d9 --- /dev/null +++ b/internal/genconfig/doc.go @@ -0,0 +1,2 @@ +// Package genconfig implements config generation from provided state values. +package genconfig diff --git a/internal/genconfig/generate_config.go b/internal/genconfig/generate_config.go new file mode 100644 index 0000000000..571ea21daf --- /dev/null +++ b/internal/genconfig/generate_config.go @@ -0,0 +1,564 @@ +package genconfig + +import ( + "fmt" + "sort" + "strings" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/tfdiags" +) + +// GenerateResourceContents generates HCL configuration code for the provided +// resource and state value. +// +// If you want tot generate actual valid Terraform code you should follow this +// call up with a call to WrapResourceContents, which will place a Terraform +// resource header around the attributes and blocks returned by this function. +func GenerateResourceContents(addr addrs.AbsResourceInstance, + schema *configschema.Block, + pc addrs.LocalProviderConfig, + stateVal cty.Value) (string, tfdiags.Diagnostics) { + var buf strings.Builder + + var diags tfdiags.Diagnostics + + if pc.LocalName != addr.Resource.Resource.ImpliedProvider() || pc.Alias != "" { + buf.WriteString(strings.Repeat(" ", 2)) + buf.WriteString(fmt.Sprintf("provider = %s\n", pc.StringCompact())) + } + + stateVal = omitUnknowns(stateVal) + if stateVal.RawEquals(cty.NilVal) { + diags = diags.Append(writeConfigAttributes(addr, &buf, schema.Attributes, 2)) + diags = diags.Append(writeConfigBlocks(addr, &buf, schema.BlockTypes, 2)) + } else { + diags = diags.Append(writeConfigAttributesFromExisting(addr, &buf, stateVal, schema.Attributes, 2)) + diags = diags.Append(writeConfigBlocksFromExisting(addr, &buf, stateVal, schema.BlockTypes, 2)) + } + + // The output better be valid HCL which can be parsed and formatted. + formatted := hclwrite.Format([]byte(buf.String())) + return string(formatted), diags +} + +func WrapResourceContents(addr addrs.AbsResourceInstance, config string) string { + var buf strings.Builder + + buf.WriteString(fmt.Sprintf("resource %q %q {\n", addr.Resource.Resource.Type, addr.Resource.Resource.Name)) + buf.WriteString(config) + buf.WriteString("}") + + // The output better be valid HCL which can be parsed and formatted. + formatted := hclwrite.Format([]byte(buf.String())) + return string(formatted) +} + +func writeConfigAttributes(addr addrs.AbsResourceInstance, buf *strings.Builder, attrs map[string]*configschema.Attribute, indent int) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + if len(attrs) == 0 { + return diags + } + + // Get a list of sorted attribute names so the output will be consistent between runs. + keys := make([]string, 0, len(attrs)) + for k := range attrs { + keys = append(keys, k) + } + sort.Strings(keys) + + for i := range keys { + name := keys[i] + attrS := attrs[name] + if attrS.NestedType != nil { + diags = diags.Append(writeConfigNestedTypeAttribute(addr, buf, name, attrS, indent)) + continue + } + if attrS.Required { + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s = ", name)) + tok := hclwrite.TokensForValue(attrS.EmptyValue()) + if _, err := tok.WriteTo(buf); err != nil { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Skipped part of config generation", + Detail: fmt.Sprintf("Could not create attribute %s in %s when generating import configuration. The plan will likely report the missing attribute as being deleted.", name, addr), + Extra: err, + }) + continue + } + writeAttrTypeConstraint(buf, attrS) + } else if attrS.Optional { + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s = ", name)) + tok := hclwrite.TokensForValue(attrS.EmptyValue()) + if _, err := tok.WriteTo(buf); err != nil { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Skipped part of config generation", + Detail: fmt.Sprintf("Could not create attribute %s in %s when generating import configuration. The plan will likely report the missing attribute as being deleted.", name, addr), + Extra: err, + }) + continue + } + writeAttrTypeConstraint(buf, attrS) + } + } + return diags +} + +func writeConfigAttributesFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, stateVal cty.Value, attrs map[string]*configschema.Attribute, indent int) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + if len(attrs) == 0 { + return diags + } + + // Get a list of sorted attribute names so the output will be consistent between runs. + keys := make([]string, 0, len(attrs)) + for k := range attrs { + keys = append(keys, k) + } + sort.Strings(keys) + + for i := range keys { + name := keys[i] + attrS := attrs[name] + if attrS.NestedType != nil { + writeConfigNestedTypeAttributeFromExisting(addr, buf, name, attrS, stateVal, indent) + continue + } + + // Exclude computed-only attributes + if attrS.Required || attrS.Optional { + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s = ", name)) + + var val cty.Value + if stateVal.Type().HasAttribute(name) { + val = stateVal.GetAttr(name) + } else { + val = attrS.EmptyValue() + } + if val.Type() == cty.String { + // SHAMELESS HACK: If we have "" for an optional value, assume + // it is actually null, due to the legacy SDK. + if !val.IsNull() && attrS.Optional && len(val.AsString()) == 0 { + val = attrS.EmptyValue() + } + } + if attrS.Sensitive || val.IsMarked() { + buf.WriteString("null # sensitive") + } else { + tok := hclwrite.TokensForValue(val) + if _, err := tok.WriteTo(buf); err != nil { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Skipped part of config generation", + Detail: fmt.Sprintf("Could not create attribute %s in %s when generating import configuration. The plan will likely report the missing attribute as being deleted.", name, addr), + Extra: err, + }) + continue + } + } + + buf.WriteString("\n") + } + } + return diags +} + +func writeConfigBlocks(addr addrs.AbsResourceInstance, buf *strings.Builder, blocks map[string]*configschema.NestedBlock, indent int) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + if len(blocks) == 0 { + return diags + } + + // Get a list of sorted block names so the output will be consistent between runs. + names := make([]string, 0, len(blocks)) + for k := range blocks { + names = append(names, k) + } + sort.Strings(names) + + for i := range names { + name := names[i] + blockS := blocks[name] + diags = diags.Append(writeConfigNestedBlock(addr, buf, name, blockS, indent)) + } + return diags +} + +func writeConfigNestedBlock(addr addrs.AbsResourceInstance, buf *strings.Builder, name string, schema *configschema.NestedBlock, indent int) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + switch schema.Nesting { + case configschema.NestingSingle, configschema.NestingGroup: + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s {", name)) + writeBlockTypeConstraint(buf, schema) + diags = diags.Append(writeConfigAttributes(addr, buf, schema.Attributes, indent+2)) + diags = diags.Append(writeConfigBlocks(addr, buf, schema.BlockTypes, indent+2)) + buf.WriteString("}\n") + return diags + case configschema.NestingList, configschema.NestingSet: + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s {", name)) + writeBlockTypeConstraint(buf, schema) + diags = diags.Append(writeConfigAttributes(addr, buf, schema.Attributes, indent+2)) + diags = diags.Append(writeConfigBlocks(addr, buf, schema.BlockTypes, indent+2)) + buf.WriteString("}\n") + return diags + case configschema.NestingMap: + buf.WriteString(strings.Repeat(" ", indent)) + // we use an arbitrary placeholder key (block label) "key" + buf.WriteString(fmt.Sprintf("%s \"key\" {", name)) + writeBlockTypeConstraint(buf, schema) + diags = diags.Append(writeConfigAttributes(addr, buf, schema.Attributes, indent+2)) + diags = diags.Append(writeConfigBlocks(addr, buf, schema.BlockTypes, indent+2)) + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString("}\n") + return diags + default: + // This should not happen, the above should be exhaustive. + panic(fmt.Errorf("unsupported NestingMode %s", schema.Nesting.String())) + } +} + +func writeConfigNestedTypeAttribute(addr addrs.AbsResourceInstance, buf *strings.Builder, name string, schema *configschema.Attribute, indent int) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s = ", name)) + + switch schema.NestedType.Nesting { + case configschema.NestingSingle: + buf.WriteString("{") + writeAttrTypeConstraint(buf, schema) + diags = diags.Append(writeConfigAttributes(addr, buf, schema.NestedType.Attributes, indent+2)) + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString("}\n") + return diags + case configschema.NestingList, configschema.NestingSet: + buf.WriteString("[{") + writeAttrTypeConstraint(buf, schema) + diags = diags.Append(writeConfigAttributes(addr, buf, schema.NestedType.Attributes, indent+2)) + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString("}]\n") + return diags + case configschema.NestingMap: + buf.WriteString("{") + writeAttrTypeConstraint(buf, schema) + buf.WriteString(strings.Repeat(" ", indent+2)) + // we use an arbitrary placeholder key "key" + buf.WriteString("key = {\n") + diags = diags.Append(writeConfigAttributes(addr, buf, schema.NestedType.Attributes, indent+4)) + buf.WriteString(strings.Repeat(" ", indent+2)) + buf.WriteString("}\n") + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString("}\n") + return diags + default: + // This should not happen, the above should be exhaustive. + panic(fmt.Errorf("unsupported NestingMode %s", schema.NestedType.Nesting.String())) + } +} + +func writeConfigBlocksFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, stateVal cty.Value, blocks map[string]*configschema.NestedBlock, indent int) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + if len(blocks) == 0 { + return diags + } + + // Get a list of sorted block names so the output will be consistent between runs. + names := make([]string, 0, len(blocks)) + for k := range blocks { + names = append(names, k) + } + sort.Strings(names) + + for _, name := range names { + blockS := blocks[name] + // This shouldn't happen in real usage; state always has all values (set + // to null as needed), but it protects against panics in tests (and any + // really weird and unlikely cases). + if !stateVal.Type().HasAttribute(name) { + continue + } + blockVal := stateVal.GetAttr(name) + diags = diags.Append(writeConfigNestedBlockFromExisting(addr, buf, name, blockS, blockVal, indent)) + } + + return diags +} + +func writeConfigNestedTypeAttributeFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, name string, schema *configschema.Attribute, stateVal cty.Value, indent int) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + switch schema.NestedType.Nesting { + case configschema.NestingSingle: + if schema.Sensitive || stateVal.IsMarked() { + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s = {} # sensitive\n", name)) + return diags + } + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s = {\n", name)) + + // This shouldn't happen in real usage; state always has all values (set + // to null as needed), but it protects against panics in tests (and any + // really weird and unlikely cases). + if !stateVal.Type().HasAttribute(name) { + return diags + } + nestedVal := stateVal.GetAttr(name) + diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, nestedVal, schema.NestedType.Attributes, indent+2)) + buf.WriteString("}\n") + return diags + + case configschema.NestingList, configschema.NestingSet: + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s = [", name)) + + if schema.Sensitive || stateVal.IsMarked() { + buf.WriteString("] # sensitive\n") + return diags + } + + buf.WriteString("\n") + + listVals := ctyCollectionValues(stateVal.GetAttr(name)) + for i := range listVals { + buf.WriteString(strings.Repeat(" ", indent+2)) + + // The entire element is marked. + if listVals[i].IsMarked() { + buf.WriteString("{}, # sensitive\n") + continue + } + + buf.WriteString("{\n") + diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, listVals[i], schema.NestedType.Attributes, indent+4)) + buf.WriteString(strings.Repeat(" ", indent+2)) + buf.WriteString("},\n") + } + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString("]\n") + return diags + + case configschema.NestingMap: + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s = {", name)) + + if schema.Sensitive || stateVal.IsMarked() { + buf.WriteString(" } # sensitive\n") + return diags + } + + buf.WriteString("\n") + + vals := stateVal.GetAttr(name).AsValueMap() + keys := make([]string, 0, len(vals)) + for key := range vals { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + buf.WriteString(strings.Repeat(" ", indent+2)) + buf.WriteString(fmt.Sprintf("%s = {", key)) + + // This entire value is marked + if vals[key].IsMarked() { + buf.WriteString("} # sensitive\n") + continue + } + + buf.WriteString("\n") + diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, vals[key], schema.NestedType.Attributes, indent+4)) + buf.WriteString(strings.Repeat(" ", indent+2)) + buf.WriteString("}\n") + } + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString("}\n") + return diags + + default: + // This should not happen, the above should be exhaustive. + panic(fmt.Errorf("unsupported NestingMode %s", schema.NestedType.Nesting.String())) + } +} + +func writeConfigNestedBlockFromExisting(addr addrs.AbsResourceInstance, buf *strings.Builder, name string, schema *configschema.NestedBlock, stateVal cty.Value, indent int) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + switch schema.Nesting { + case configschema.NestingSingle, configschema.NestingGroup: + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s {", name)) + + // If the entire value is marked, don't print any nested attributes + if stateVal.IsMarked() { + buf.WriteString("} # sensitive\n") + return diags + } + buf.WriteString("\n") + diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, stateVal, schema.Attributes, indent+2)) + diags = diags.Append(writeConfigBlocksFromExisting(addr, buf, stateVal, schema.BlockTypes, indent+2)) + buf.WriteString("}\n") + return diags + case configschema.NestingList, configschema.NestingSet: + if stateVal.IsMarked() { + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s {} # sensitive\n", name)) + return diags + } + listVals := ctyCollectionValues(stateVal) + for i := range listVals { + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s {\n", name)) + diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, listVals[i], schema.Attributes, indent+2)) + diags = diags.Append(writeConfigBlocksFromExisting(addr, buf, listVals[i], schema.BlockTypes, indent+2)) + buf.WriteString("}\n") + } + return diags + case configschema.NestingMap: + // If the entire value is marked, don't print any nested attributes + if stateVal.IsMarked() { + buf.WriteString(fmt.Sprintf("%s {} # sensitive\n", name)) + return diags + } + + vals := stateVal.AsValueMap() + keys := make([]string, 0, len(vals)) + for key := range vals { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString(fmt.Sprintf("%s %q {", name, key)) + // This entire map element is marked + if vals[key].IsMarked() { + buf.WriteString("} # sensitive\n") + return diags + } + buf.WriteString("\n") + diags = diags.Append(writeConfigAttributesFromExisting(addr, buf, vals[key], schema.Attributes, indent+2)) + diags = diags.Append(writeConfigBlocksFromExisting(addr, buf, vals[key], schema.BlockTypes, indent+2)) + buf.WriteString(strings.Repeat(" ", indent)) + buf.WriteString("}\n") + } + return diags + default: + // This should not happen, the above should be exhaustive. + panic(fmt.Errorf("unsupported NestingMode %s", schema.Nesting.String())) + } +} + +func writeAttrTypeConstraint(buf *strings.Builder, schema *configschema.Attribute) { + if schema.Required { + buf.WriteString(" # REQUIRED ") + } else { + buf.WriteString(" # OPTIONAL ") + } + + if schema.NestedType != nil { + buf.WriteString(fmt.Sprintf("%s\n", schema.NestedType.ImpliedType().FriendlyName())) + } else { + buf.WriteString(fmt.Sprintf("%s\n", schema.Type.FriendlyName())) + } +} + +func writeBlockTypeConstraint(buf *strings.Builder, schema *configschema.NestedBlock) { + if schema.MinItems > 0 { + buf.WriteString(" # REQUIRED block\n") + } else { + buf.WriteString(" # OPTIONAL block\n") + } +} + +// copied from command/format/diff +func ctyCollectionValues(val cty.Value) []cty.Value { + if !val.IsKnown() || val.IsNull() { + return nil + } + + var len int + if val.IsMarked() { + val, _ = val.Unmark() + len = val.LengthInt() + } else { + len = val.LengthInt() + } + + ret := make([]cty.Value, 0, len) + for it := val.ElementIterator(); it.Next(); { + _, value := it.Element() + ret = append(ret, value) + } + + return ret +} + +// omitUnknowns recursively walks the src cty.Value and returns a new cty.Value, +// omitting any unknowns. +// +// The result also normalizes some types: all sequence types are turned into +// tuple types and all mapping types are converted to object types, since we +// assume the result of this is just going to be serialized as JSON (and thus +// lose those distinctions) anyway. +func omitUnknowns(val cty.Value) cty.Value { + ty := val.Type() + switch { + case val.IsNull(): + return val + case !val.IsKnown(): + return cty.NilVal + case ty.IsPrimitiveType(): + return val + case ty.IsListType() || ty.IsTupleType() || ty.IsSetType(): + var vals []cty.Value + it := val.ElementIterator() + for it.Next() { + _, v := it.Element() + newVal := omitUnknowns(v) + if newVal != cty.NilVal { + vals = append(vals, newVal) + } else if newVal == cty.NilVal { + // element order is how we correlate unknownness, so we must + // replace unknowns with nulls + vals = append(vals, cty.NullVal(v.Type())) + } + } + // We use tuple types always here, because the work we did above + // may have caused the individual elements to have different types, + // and we're doing this work to produce JSON anyway and JSON marshalling + // represents all of these sequence types as an array. + return cty.TupleVal(vals) + case ty.IsMapType() || ty.IsObjectType(): + vals := make(map[string]cty.Value) + it := val.ElementIterator() + for it.Next() { + k, v := it.Element() + newVal := omitUnknowns(v) + if newVal != cty.NilVal { + vals[k.AsString()] = newVal + } + } + // We use object types always here, because the work we did above + // may have caused the individual elements to have different types, + // and we're doing this work to produce JSON anyway and JSON marshalling + // represents both of these mapping types as an object. + return cty.ObjectVal(vals) + default: + // Should never happen, since the above should cover all types + panic(fmt.Sprintf("omitUnknowns cannot handle %#v", val)) + } +} diff --git a/internal/genconfig/generate_config_test.go b/internal/genconfig/generate_config_test.go new file mode 100644 index 0000000000..3c82b0f587 --- /dev/null +++ b/internal/genconfig/generate_config_test.go @@ -0,0 +1,308 @@ +package genconfig + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/configs/configschema" +) + +func TestConfigGeneration(t *testing.T) { + tcs := map[string]struct { + schema *configschema.Block + addr addrs.AbsResourceInstance + provider addrs.LocalProviderConfig + value cty.Value + expected string + }{ + "simple_resource": { + schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "list_block": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "nested_value": { + Type: cty.String, + Optional: true, + }, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + "value": { + Type: cty.String, + Optional: true, + }, + }, + }, + addr: addrs.AbsResourceInstance{ + Module: nil, + Resource: addrs.ResourceInstance{ + Resource: addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "tfcoremock_simple_resource", + Name: "empty", + }, + Key: nil, + }, + }, + provider: addrs.LocalProviderConfig{ + LocalName: "tfcoremock", + }, + value: cty.NilVal, + expected: ` +resource "tfcoremock_simple_resource" "empty" { + value = null # OPTIONAL string + list_block { # OPTIONAL block + nested_value = null # OPTIONAL string + } +}`, + }, + "simple_resource_with_state": { + schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "list_block": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "nested_value": { + Type: cty.String, + Optional: true, + }, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + "value": { + Type: cty.String, + Optional: true, + }, + }, + }, + addr: addrs.AbsResourceInstance{ + Module: nil, + Resource: addrs.ResourceInstance{ + Resource: addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "tfcoremock_simple_resource", + Name: "empty", + }, + Key: nil, + }, + }, + provider: addrs.LocalProviderConfig{ + LocalName: "tfcoremock", + }, + value: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("D2320658"), + "value": cty.StringVal("Hello, world!"), + "list_block": cty.ObjectVal(map[string]cty.Value{ + "nested_value": cty.StringVal("Hello, solar system!"), + }), + }), + expected: ` +resource "tfcoremock_simple_resource" "empty" { + value = "Hello, world!" + list_block { + nested_value = "Hello, solar system!" + } +}`, + }, + "simple_resource_with_partial_state": { + schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "list_block": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "nested_value": { + Type: cty.String, + Optional: true, + }, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + "value": { + Type: cty.String, + Optional: true, + }, + }, + }, + addr: addrs.AbsResourceInstance{ + Module: nil, + Resource: addrs.ResourceInstance{ + Resource: addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "tfcoremock_simple_resource", + Name: "empty", + }, + Key: nil, + }, + }, + provider: addrs.LocalProviderConfig{ + LocalName: "tfcoremock", + }, + value: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("D2320658"), + "list_block": cty.ObjectVal(map[string]cty.Value{ + "nested_value": cty.StringVal("Hello, solar system!"), + }), + }), + expected: ` +resource "tfcoremock_simple_resource" "empty" { + value = null + list_block { + nested_value = "Hello, solar system!" + } +}`, + }, + "simple_resource_with_alternate_provider": { + schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "list_block": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "nested_value": { + Type: cty.String, + Optional: true, + }, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + "value": { + Type: cty.String, + Optional: true, + }, + }, + }, + addr: addrs.AbsResourceInstance{ + Module: nil, + Resource: addrs.ResourceInstance{ + Resource: addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "tfcoremock_simple_resource", + Name: "empty", + }, + Key: nil, + }, + }, + provider: addrs.LocalProviderConfig{ + LocalName: "mock", + }, + value: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("D2320658"), + "value": cty.StringVal("Hello, world!"), + "list_block": cty.ObjectVal(map[string]cty.Value{ + "nested_value": cty.StringVal("Hello, solar system!"), + }), + }), + expected: ` +resource "tfcoremock_simple_resource" "empty" { + provider = mock + value = "Hello, world!" + list_block { + nested_value = "Hello, solar system!" + } +}`, + }, + "simple_resource_with_aliased_provider": { + schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "list_block": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "nested_value": { + Type: cty.String, + Optional: true, + }, + }, + }, + Nesting: configschema.NestingSingle, + }, + }, + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + "value": { + Type: cty.String, + Optional: true, + }, + }, + }, + addr: addrs.AbsResourceInstance{ + Module: nil, + Resource: addrs.ResourceInstance{ + Resource: addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "tfcoremock_simple_resource", + Name: "empty", + }, + Key: nil, + }, + }, + provider: addrs.LocalProviderConfig{ + LocalName: "tfcoremock", + Alias: "alternate", + }, + value: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("D2320658"), + "value": cty.StringVal("Hello, world!"), + "list_block": cty.ObjectVal(map[string]cty.Value{ + "nested_value": cty.StringVal("Hello, solar system!"), + }), + }), + expected: ` +resource "tfcoremock_simple_resource" "empty" { + provider = tfcoremock.alternate + value = "Hello, world!" + list_block { + nested_value = "Hello, solar system!" + } +}`, + }, + } + for name, tc := range tcs { + t.Run(name, func(t *testing.T) { + contents, diags := GenerateResourceContents(tc.addr, tc.schema, tc.provider, tc.value) + if len(diags) > 0 { + t.Errorf("expected no diagnostics but found %s", diags) + } + + got := WrapResourceContents(tc.addr, contents) + want := strings.TrimSpace(tc.expected) + if diff := cmp.Diff(got, want); len(diff) > 0 { + t.Errorf("got:\n%s\nwant:\n%s\ndiff:\n%s", got, want, diff) + } + }) + } +} diff --git a/internal/plans/changes.go b/internal/plans/changes.go index 2c1f75ee2e..a216fd1047 100644 --- a/internal/plans/changes.go +++ b/internal/plans/changes.go @@ -308,10 +308,11 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha Private: rc.Private, ProviderAddr: rc.ProviderAddr, Change: Change{ - Action: Delete, - Before: rc.Before, - After: cty.NullVal(rc.Before.Type()), - Importing: rc.Importing, + Action: Delete, + Before: rc.Before, + After: cty.NullVal(rc.Before.Type()), + Importing: rc.Importing, + GeneratedConfig: rc.GeneratedConfig, }, } default: @@ -321,10 +322,11 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha Private: rc.Private, ProviderAddr: rc.ProviderAddr, Change: Change{ - Action: NoOp, - Before: rc.Before, - After: rc.Before, - Importing: rc.Importing, + Action: NoOp, + Before: rc.Before, + After: rc.Before, + Importing: rc.Importing, + GeneratedConfig: rc.GeneratedConfig, }, } } @@ -337,10 +339,11 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha Private: rc.Private, ProviderAddr: rc.ProviderAddr, Change: Change{ - Action: NoOp, - Before: rc.Before, - After: rc.Before, - Importing: rc.Importing, + Action: NoOp, + Before: rc.Before, + After: rc.Before, + Importing: rc.Importing, + GeneratedConfig: rc.GeneratedConfig, }, } case CreateThenDelete, DeleteThenCreate: @@ -350,10 +353,11 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha Private: rc.Private, ProviderAddr: rc.ProviderAddr, Change: Change{ - Action: Create, - Before: cty.NullVal(rc.After.Type()), - After: rc.After, - Importing: rc.Importing, + Action: Create, + Before: cty.NullVal(rc.After.Type()), + After: rc.After, + Importing: rc.Importing, + GeneratedConfig: rc.GeneratedConfig, }, } } @@ -533,6 +537,12 @@ type Change struct { // Use the simple presence of this field to detect if a ChangeSrc is to be // imported, the contents of this structure may be modified going forward. Importing *Importing + + // GeneratedConfig contains any HCL config generated for this resource + // during planning, as a string. If GeneratedConfig is populated, Importing + // should be true. However, not all Importing changes contain generated + // config. + GeneratedConfig string } // Encode produces a variant of the reciever that has its change values @@ -572,11 +582,12 @@ func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) { } return &ChangeSrc{ - Action: c.Action, - Before: beforeDV, - After: afterDV, - BeforeValMarks: beforeVM, - AfterValMarks: afterVM, - Importing: importing, + Action: c.Action, + Before: beforeDV, + After: afterDV, + BeforeValMarks: beforeVM, + AfterValMarks: afterVM, + Importing: importing, + GeneratedConfig: c.GeneratedConfig, }, nil } diff --git a/internal/plans/changes_src.go b/internal/plans/changes_src.go index d5e471926a..c6e19cd01a 100644 --- a/internal/plans/changes_src.go +++ b/internal/plans/changes_src.go @@ -218,6 +218,12 @@ type ChangeSrc struct { // Use the simple presence of this field to detect if a ChangeSrc is to be // imported, the contents of this structure may be modified going forward. Importing *ImportingSrc + + // GeneratedConfig contains any HCL config generated for this resource + // during planning, as a string. If GeneratedConfig is populated, Importing + // should be true. However, not all Importing changes contain generated + // config. + GeneratedConfig string } // Decode unmarshals the raw representations of the before and after values @@ -251,9 +257,10 @@ func (cs *ChangeSrc) Decode(ty cty.Type) (*Change, error) { } return &Change{ - Action: cs.Action, - Before: before.MarkWithPaths(cs.BeforeValMarks), - After: after.MarkWithPaths(cs.AfterValMarks), - Importing: importing, + Action: cs.Action, + Before: before.MarkWithPaths(cs.BeforeValMarks), + After: after.MarkWithPaths(cs.AfterValMarks), + Importing: importing, + GeneratedConfig: cs.GeneratedConfig, }, nil } diff --git a/internal/plans/internal/planproto/planfile.pb.go b/internal/plans/internal/planproto/planfile.pb.go index 121dc24b7a..0c3e65ba32 100644 --- a/internal/plans/internal/planproto/planfile.pb.go +++ b/internal/plans/internal/planproto/planfile.pb.go @@ -621,6 +621,9 @@ type Change struct { // Importing, if true, specifies that the resource is being imported as part // of the change. Importing *Importing `protobuf:"bytes,5,opt,name=importing,proto3" json:"importing,omitempty"` + // GeneratedConfig contains any configuration that was generated as part of + // the change, as an HCL string. + GeneratedConfig string `protobuf:"bytes,6,opt,name=generated_config,json=generatedConfig,proto3" json:"generated_config,omitempty"` } func (x *Change) Reset() { @@ -690,6 +693,13 @@ func (x *Change) GetImporting() *Importing { return nil } +func (x *Change) GetGeneratedConfig() string { + if x != nil { + return x.GeneratedConfig + } + return "" +} + type ResourceInstanceChange struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1391,7 +1401,7 @@ var file_planfile_proto_rawDesc = []byte{ 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x95, 0x02, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0xc0, 0x02, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, @@ -1408,125 +1418,127 @@ var file_planfile_proto_rawDesc = []byte{ 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x2f, 0x0a, 0x09, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, - 0x6e, 0x67, 0x52, 0x09, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x22, 0xd3, 0x02, - 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, - 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x22, 0x0a, 0x0d, - 0x70, 0x72, 0x65, 0x76, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x65, 0x76, 0x52, 0x75, 0x6e, 0x41, 0x64, 0x64, 0x72, - 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x4b, 0x65, - 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x26, 0x0a, - 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, - 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, - 0x37, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, - 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x24, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x22, 0x68, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0xe8, 0x03, - 0x0a, 0x0c, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x33, - 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x74, - 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, - 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x61, 0x64, - 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x41, 0x64, 0x64, 0x72, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x66, 0x70, - 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, - 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x1a, 0x8f, 0x01, 0x0a, 0x0c, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, - 0x6e, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, - 0x10, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x08, 0x0a, 0x04, 0x50, 0x41, 0x53, 0x53, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, - 0x4c, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x22, 0x48, - 0x0a, 0x0a, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0f, 0x0a, 0x0b, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, - 0x08, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f, - 0x55, 0x54, 0x50, 0x55, 0x54, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x09, 0x0a, - 0x05, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x03, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79, 0x6e, 0x61, - 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, 0x67, 0x70, - 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61, - 0x63, 0x6b, 0x22, 0xa5, 0x01, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x27, 0x0a, 0x05, 0x73, - 0x74, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x66, 0x70, - 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52, 0x05, 0x73, - 0x74, 0x65, 0x70, 0x73, 0x1a, 0x74, 0x0a, 0x04, 0x53, 0x74, 0x65, 0x70, 0x12, 0x27, 0x0a, 0x0e, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, - 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x48, 0x00, 0x52, 0x0a, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x42, 0x0a, - 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x1b, 0x0a, 0x09, 0x49, 0x6d, - 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x2a, 0x31, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, - 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, - 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x46, 0x52, - 0x45, 0x53, 0x48, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x2a, 0x70, 0x0a, 0x06, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, 0x12, 0x0a, - 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x45, - 0x41, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, - 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, - 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x43, 0x52, 0x45, 0x41, - 0x54, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x54, - 0x48, 0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x07, 0x2a, 0xc8, 0x03, 0x0a, - 0x1c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x08, 0x0a, - 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x50, 0x4c, 0x41, - 0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x54, 0x41, 0x49, 0x4e, 0x54, - 0x45, 0x44, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, - 0x42, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, - 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, - 0x43, 0x41, 0x4e, 0x4e, 0x4f, 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, - 0x25, 0x0a, 0x21, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, - 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4f, - 0x4e, 0x46, 0x49, 0x47, 0x10, 0x04, 0x12, 0x23, 0x0a, 0x1f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, - 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x52, - 0x45, 0x50, 0x45, 0x54, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x44, - 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f, - 0x55, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x06, 0x12, 0x1b, 0x0a, 0x17, 0x44, - 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x45, 0x41, - 0x43, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x07, 0x12, 0x1c, 0x0a, 0x18, 0x44, 0x45, 0x4c, 0x45, - 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, - 0x44, 0x55, 0x4c, 0x45, 0x10, 0x08, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, - 0x45, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x52, 0x49, 0x47, 0x47, 0x45, 0x52, 0x53, 0x10, 0x09, 0x12, - 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, - 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x0a, - 0x12, 0x23, 0x0a, 0x1f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, - 0x5f, 0x44, 0x45, 0x50, 0x45, 0x4e, 0x44, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x50, 0x45, 0x4e, 0x44, - 0x49, 0x4e, 0x47, 0x10, 0x0b, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, - 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x4e, 0x45, 0x53, 0x54, - 0x45, 0x44, 0x10, 0x0d, 0x12, 0x21, 0x0a, 0x1d, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, - 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x54, - 0x41, 0x52, 0x47, 0x45, 0x54, 0x10, 0x0c, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, - 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x6e, 0x67, 0x52, 0x09, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x29, 0x0a, + 0x10, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xd3, 0x02, 0x0a, 0x16, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x5f, + 0x72, 0x75, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x70, 0x72, 0x65, 0x76, 0x52, 0x75, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x64, + 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, + 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x37, 0x0a, 0x10, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x0b, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, + 0x74, 0x68, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x74, 0x66, 0x70, + 0x6c, 0x61, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x68, + 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, + 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0xe8, 0x03, 0x0a, 0x0c, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x33, 0x0a, 0x04, 0x6b, 0x69, 0x6e, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, + 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x41, 0x64, 0x64, 0x72, 0x12, + 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x1a, 0x8f, 0x01, 0x0a, 0x0c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x41, + 0x64, 0x64, 0x72, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x66, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, + 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x41, + 0x53, 0x53, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x02, 0x12, 0x09, + 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x22, 0x48, 0x0a, 0x0a, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x4f, + 0x55, 0x52, 0x43, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54, + 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x48, 0x45, 0x43, + 0x4b, 0x10, 0x03, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6b, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6b, 0x22, 0xa5, 0x01, + 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, + 0x61, 0x74, 0x68, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x1a, + 0x74, 0x0a, 0x04, 0x53, 0x74, 0x65, 0x70, 0x12, 0x27, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x00, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x37, 0x0a, 0x0b, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, + 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x1b, 0x0a, 0x09, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, + 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x2a, 0x31, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, + 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, + 0x59, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x46, 0x52, 0x45, 0x53, 0x48, 0x5f, 0x4f, + 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x2a, 0x70, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, + 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x45, 0x41, 0x44, 0x10, 0x02, 0x12, + 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x44, + 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x4c, 0x45, 0x54, + 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x06, 0x12, + 0x16, 0x0a, 0x12, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x44, + 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x07, 0x2a, 0xc8, 0x03, 0x0a, 0x1c, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, + 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45, + 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x54, 0x41, 0x49, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x16, 0x0a, 0x12, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x59, 0x5f, 0x52, 0x45, + 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x50, 0x4c, 0x41, + 0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x4e, 0x4f, + 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x44, 0x45, + 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, + 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, + 0x04, 0x12, 0x23, 0x0a, 0x1f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, + 0x55, 0x53, 0x45, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x50, 0x45, 0x54, 0x49, + 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, + 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x49, + 0x4e, 0x44, 0x45, 0x58, 0x10, 0x06, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, + 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x45, 0x41, 0x43, 0x48, 0x5f, 0x4b, 0x45, + 0x59, 0x10, 0x07, 0x12, 0x1c, 0x0a, 0x18, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, + 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x44, 0x55, 0x4c, 0x45, 0x10, + 0x08, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x59, 0x5f, + 0x54, 0x52, 0x49, 0x47, 0x47, 0x45, 0x52, 0x53, 0x10, 0x09, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, + 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, + 0x47, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x0a, 0x12, 0x23, 0x0a, 0x1f, 0x52, + 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x44, 0x45, 0x50, 0x45, + 0x4e, 0x44, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x0b, + 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, + 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x10, 0x0d, 0x12, + 0x21, 0x0a, 0x1d, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, + 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, + 0x10, 0x0c, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, + 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, + 0x61, 0x6e, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, + 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/plans/internal/planproto/planfile.proto b/internal/plans/internal/planproto/planfile.proto index 119247abb1..47b5478353 100644 --- a/internal/plans/internal/planproto/planfile.proto +++ b/internal/plans/internal/planproto/planfile.proto @@ -147,6 +147,10 @@ message Change { // Importing, if true, specifies that the resource is being imported as part // of the change. Importing importing = 5; + + // GeneratedConfig contains any configuration that was generated as part of + // the change, as an HCL string. + string generated_config = 6; } // ResourceInstanceActionReason sometimes provides some additional user-facing diff --git a/internal/terraform/context_plan2_test.go b/internal/terraform/context_plan2_test.go index 303cf3b9fc..de2c263381 100644 --- a/internal/terraform/context_plan2_test.go +++ b/internal/terraform/context_plan2_test.go @@ -4361,3 +4361,158 @@ import { t.Error("ReadResource called multiple times for import") } } + +func TestContext2Plan_importIntoModuleWithGeneratedConfig(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` +import { + to = test_object.a + id = "123" +} + +import { + to = module.mod.test_object.a + id = "456" +} + +module "mod" { + source = "./mod" +} +`, + "./mod/main.tf": ` +resource "test_object" "a" { + test_string = "bar" +} +`, + }) + + p := simpleMockProvider() + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + p.ReadResourceResponse = &providers.ReadResourceResponse{ + NewState: cty.ObjectVal(map[string]cty.Value{ + "test_string": cty.StringVal("foo"), + }), + } + p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ + ImportedResources: []providers.ImportedResource{ + { + TypeName: "test_object", + State: cty.ObjectVal(map[string]cty.Value{ + "test_string": cty.StringVal("foo"), + }), + }, + }, + } + + p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ + ImportedResources: []providers.ImportedResource{ + { + TypeName: "test_object", + State: cty.ObjectVal(map[string]cty.Value{ + "test_string": cty.StringVal("foo"), + }), + }, + }, + } + + plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) + if diags.HasErrors() { + t.Fatalf("unexpected errors\n%s", diags.Err().Error()) + } + + one := mustResourceInstanceAddr("test_object.a") + two := mustResourceInstanceAddr("module.mod.test_object.a") + + onePlan := plan.Changes.ResourceInstance(one) + twoPlan := plan.Changes.ResourceInstance(two) + + // This test is just to make sure things work e2e with modules and generated + // config, so we're not too careful about the actual responses - we're just + // happy nothing panicked. See the other import tests for actual validation + // of responses and the like. + if twoPlan.Action != plans.Update { + t.Errorf("expected nested item to be updated but was %s", twoPlan.Action) + } + + if len(onePlan.GeneratedConfig) == 0 { + t.Errorf("expected root item to generate config but it didn't") + } +} + +func TestContext2Plan_importResourceConfigGen(t *testing.T) { + addr := mustResourceInstanceAddr("test_object.a") + m := testModuleInline(t, map[string]string{ + "main.tf": ` +import { + to = test_object.a + id = "123" +} +`, + }) + + p := simpleMockProvider() + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + p.ReadResourceResponse = &providers.ReadResourceResponse{ + NewState: cty.ObjectVal(map[string]cty.Value{ + "test_string": cty.StringVal("foo"), + }), + } + p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ + ImportedResources: []providers.ImportedResource{ + { + TypeName: "test_object", + State: cty.ObjectVal(map[string]cty.Value{ + "test_string": cty.StringVal("foo"), + }), + }, + }, + } + + plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) + if diags.HasErrors() { + t.Fatalf("unexpected errors\n%s", diags.Err().Error()) + } + + t.Run(addr.String(), func(t *testing.T) { + instPlan := plan.Changes.ResourceInstance(addr) + if instPlan == nil { + t.Fatalf("no plan for %s at all", addr) + } + + if got, want := instPlan.Addr, addr; !got.Equal(want) { + t.Errorf("wrong current address\ngot: %s\nwant: %s", got, want) + } + if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) { + t.Errorf("wrong previous run address\ngot: %s\nwant: %s", got, want) + } + if got, want := instPlan.Action, plans.NoOp; got != want { + t.Errorf("wrong planned action\ngot: %s\nwant: %s", got, want) + } + if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want { + t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want) + } + if instPlan.Importing.ID != "123" { + t.Errorf("expected import change from \"123\", got non-import change") + } + + want := `resource "test_object" "a" { + test_bool = null + test_list = null + test_map = null + test_number = null + test_string = "foo" +}` + got := instPlan.GeneratedConfig + if diff := cmp.Diff(want, got); len(diff) > 0 { + t.Errorf("got:\n%s\nwant:\n%s\ndiff:\n%s", got, want, diff) + } + }) +} diff --git a/internal/terraform/graph_builder_plan.go b/internal/terraform/graph_builder_plan.go index bf20c21596..898a2ed1ca 100644 --- a/internal/terraform/graph_builder_plan.go +++ b/internal/terraform/graph_builder_plan.go @@ -111,6 +111,10 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { skip: b.Operation == walkPlanDestroy, importTargets: b.ImportTargets, + + // We only want to generate config during a plan operation. + // TODO: add a dedicated flag for this? + generateConfigForImportTargets: b.Operation == walkPlan, }, // Add dynamic values diff --git a/internal/terraform/node_resource_abstract.go b/internal/terraform/node_resource_abstract.go index 6fe088f89a..e615e4972c 100644 --- a/internal/terraform/node_resource_abstract.go +++ b/internal/terraform/node_resource_abstract.go @@ -360,7 +360,7 @@ func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.Ab expander := ctx.InstanceExpander() switch { - case n.Config.Count != nil: + case n.Config != nil && n.Config.Count != nil: count, countDiags := evaluateCountExpression(n.Config.Count, ctx) diags = diags.Append(countDiags) if countDiags.HasErrors() { @@ -370,7 +370,7 @@ func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.Ab state.SetResourceProvider(addr, n.ResolvedProvider) expander.SetResourceCount(addr.Module, n.Addr.Resource, count) - case n.Config.ForEach != nil: + case n.Config != nil && n.Config.ForEach != nil: forEach, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx) diags = diags.Append(forEachDiags) if forEachDiags.HasErrors() { diff --git a/internal/terraform/node_resource_abstract_instance.go b/internal/terraform/node_resource_abstract_instance.go index 60a264fbe8..7412f126f2 100644 --- a/internal/terraform/node_resource_abstract_instance.go +++ b/internal/terraform/node_resource_abstract_instance.go @@ -9,12 +9,14 @@ import ( "strings" "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/checks" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/genconfig" "github.com/hashicorp/terraform/internal/instances" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/plans/objchange" @@ -648,19 +650,69 @@ func (n *NodeAbstractResourceInstance) plan( plannedChange *plans.ResourceInstanceChange, currentState *states.ResourceInstanceObject, createBeforeDestroy bool, - forceReplace []addrs.AbsResourceInstance) (*plans.ResourceInstanceChange, *states.ResourceInstanceObject, instances.RepetitionData, tfdiags.Diagnostics) { + forceReplace []addrs.AbsResourceInstance, + generateConfig bool, +) (*plans.ResourceInstanceChange, *states.ResourceInstanceObject, instances.RepetitionData, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics var state *states.ResourceInstanceObject var plan *plans.ResourceInstanceChange var keyData instances.RepetitionData + var generatedConfig *configs.Resource - config := *n.Config resource := n.Addr.Resource.Resource provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) if err != nil { return plan, state, keyData, diags.Append(err) } + if providerSchema == nil { + diags = diags.Append(fmt.Errorf("provider schema is unavailable for %s", n.Addr)) + return plan, state, keyData, diags + } + schema, _ := providerSchema.SchemaForResourceAddr(resource) + if schema == nil { + // Should be caught during validation, so we don't bother with a pretty error here + diags = diags.Append(fmt.Errorf("provider does not support resource type %q", resource.Type)) + return plan, state, keyData, diags + } + + // If we're importing and generating config, generate it now. + var generatedHCL string + if generateConfig { + var generatedDiags tfdiags.Diagnostics + + if n.Config != nil { + return plan, state, keyData, diags.Append(fmt.Errorf("tried to generate config for %s, but it already exists", n.Addr)) + } + + // Generate the HCL string first, then parse the HCL body from it. + // First we generate the contents of the resource block for use within + // the planning node. Then we wrap it in an enclosing resource block to + // pass into the plan for rendering. + generatedHCLAttributes, generatedDiags := n.generateHCLStringAttributes(n.Addr, currentState, schema) + diags = diags.Append(generatedDiags) + + generatedHCL = genconfig.WrapResourceContents(n.Addr, generatedHCLAttributes) + + // parse the "file" as HCL to get the hcl.Body + synthHCLFile, hclDiags := hclsyntax.ParseConfig([]byte(generatedHCLAttributes), "generated_resources.tf", hcl.Pos{Byte: 0, Line: 1, Column: 1}) + if hclDiags.HasErrors() { + return plan, state, keyData, diags.Append(hclDiags) + } + + generatedConfig = &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: n.Addr.Resource.Resource.Type, + Name: n.Addr.Resource.Resource.Name, + Config: synthHCLFile.Body, + Managed: &configs.ManagedResource{}, + Provider: n.ResolvedProvider.Provider, + } + n.Config = generatedConfig + } + + config := *n.Config + checkRuleSeverity := tfdiags.Error if n.preDestroyRefresh { checkRuleSeverity = tfdiags.Warning @@ -671,18 +723,7 @@ func (n *NodeAbstractResourceInstance) plan( createBeforeDestroy = plannedChange.Action == plans.CreateThenDelete } - if providerSchema == nil { - diags = diags.Append(fmt.Errorf("provider schema is unavailable for %s", n.Addr)) - return plan, state, keyData, diags - } - // Evaluate the configuration - schema, _ := providerSchema.SchemaForResourceAddr(resource) - if schema == nil { - // Should be caught during validation, so we don't bother with a pretty error here - diags = diags.Append(fmt.Errorf("provider does not support resource type %q", resource.Type)) - return plan, state, keyData, diags - } forEach, _ := evaluateForEachExpression(n.Config.ForEach, ctx) @@ -1124,7 +1165,8 @@ func (n *NodeAbstractResourceInstance) plan( // Pass the marked planned value through in our change // to propogate through evaluation. // Marks will be removed when encoding. - After: plannedNewVal, + After: plannedNewVal, + GeneratedConfig: generatedHCL, }, ActionReason: actionReason, RequiredReplace: reqRep, @@ -1146,6 +1188,21 @@ func (n *NodeAbstractResourceInstance) plan( return plan, state, keyData, diags } +// generateHCLStringAttributes produces a string in HCL format for the given +// resource state and schema without the surrounding block. +func (n *NodeAbstractResource) generateHCLStringAttributes(addr addrs.AbsResourceInstance, state *states.ResourceInstanceObject, schema *configschema.Block) (string, tfdiags.Diagnostics) { + filteredSchema := schema.Filter( + configschema.FilterOr(configschema.FilterReadOnlyAttributes, configschema.FilterDeprecatedAttribute), + configschema.FilterDeprecatedBlock) + + providerAddr := addrs.LocalProviderConfig{ + LocalName: n.ResolvedProvider.Provider.Type, + Alias: n.ResolvedProvider.Alias, + } + + return genconfig.GenerateResourceContents(addr, filteredSchema, providerAddr, state.Value) +} + func (n *NodeAbstractResource) processIgnoreChanges(prior, config cty.Value, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { // ignore_changes only applies when an object already exists, since we // can't ignore changes to a thing we've not created yet. diff --git a/internal/terraform/node_resource_apply_instance.go b/internal/terraform/node_resource_apply_instance.go index 98d77d8aa1..8cf1bbb542 100644 --- a/internal/terraform/node_resource_apply_instance.go +++ b/internal/terraform/node_resource_apply_instance.go @@ -274,7 +274,7 @@ func (n *NodeApplyableResourceInstance) managedResourceExecute(ctx EvalContext) // Make a new diff, in case we've learned new values in the state // during apply which we can now incorporate. - diffApply, _, repeatData, planDiags := n.plan(ctx, diff, state, false, n.forceReplace) + diffApply, _, repeatData, planDiags := n.plan(ctx, diff, state, false, n.forceReplace, false) diags = diags.Append(planDiags) if diags.HasErrors() { return diags diff --git a/internal/terraform/node_resource_plan.go b/internal/terraform/node_resource_plan.go index 1068de32a5..92087e5bf9 100644 --- a/internal/terraform/node_resource_plan.go +++ b/internal/terraform/node_resource_plan.go @@ -5,7 +5,6 @@ package terraform import ( "fmt" - "log" "strings" "github.com/hashicorp/terraform/internal/addrs" @@ -200,12 +199,6 @@ func (n *nodeExpandPlannableResource) DynamicExpand(ctx EvalContext) (*Graph, er func (n *nodeExpandPlannableResource) expandResourceInstances(globalCtx EvalContext, resAddr addrs.AbsResource, g *Graph, instAddrs addrs.Set[addrs.Checkable]) error { var diags tfdiags.Diagnostics - if n.Config == nil { - // Nothing to do, then. - log.Printf("[TRACE] nodeExpandPlannableResource: no configuration present for %s", n.Name()) - return diags.ErrWithWarnings() - } - // The rest of our work here needs to know which module instance it's // working in, so that it can evaluate expressions in the appropriate scope. moduleCtx := globalCtx.WithPath(resAddr.Module) diff --git a/internal/terraform/node_resource_plan_instance.go b/internal/terraform/node_resource_plan_instance.go index 9e7908c890..ce9c709d26 100644 --- a/internal/terraform/node_resource_plan_instance.go +++ b/internal/terraform/node_resource_plan_instance.go @@ -135,6 +135,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) var change *plans.ResourceInstanceChange var instanceRefreshState *states.ResourceInstanceObject + var generateConfig bool checkRuleSeverity := tfdiags.Error if n.skipPlanChanges || n.preDestroyRefresh { @@ -147,9 +148,11 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) return diags } - diags = diags.Append(validateSelfRef(addr.Resource, config.Config, providerSchema)) - if diags.HasErrors() { - return diags + if config != nil { + diags = diags.Append(validateSelfRef(addr.Resource, config.Config, providerSchema)) + if diags.HasErrors() { + return diags + } } importing := n.importTarget.ID != "" @@ -157,6 +160,9 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) // If the resource is to be imported, we now ask the provider for an Import // and a Refresh, and save the resulting state to instanceRefreshState. if importing { + if n.Config == nil || n.Config.Managed == nil { + generateConfig = true + } instanceRefreshState, diags = n.importState(ctx, addr, provider) } else { var readDiags tfdiags.Diagnostics @@ -239,7 +245,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) } change, instancePlanState, repeatData, planDiags := n.plan( - ctx, change, instanceRefreshState, n.ForceCreateBeforeDestroy, n.forceReplace, + ctx, change, instanceRefreshState, n.ForceCreateBeforeDestroy, n.forceReplace, generateConfig, ) diags = diags.Append(planDiags) if diags.HasErrors() { @@ -361,6 +367,9 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) // instance address is added to forceReplace func (n *NodePlannableResourceInstance) replaceTriggered(ctx EvalContext, repData instances.RepetitionData) tfdiags.Diagnostics { var diags tfdiags.Diagnostics + if n.Config == nil { + return diags + } for _, expr := range n.Config.TriggersReplacement { ref, replace, evalDiags := ctx.EvaluateReplaceTriggeredBy(expr, repData) diff --git a/internal/terraform/node_resource_validate.go b/internal/terraform/node_resource_validate.go index 0c82e27633..372eb197f1 100644 --- a/internal/terraform/node_resource_validate.go +++ b/internal/terraform/node_resource_validate.go @@ -44,6 +44,10 @@ func (n *NodeValidatableResource) Path() addrs.ModuleInstance { // GraphNodeEvalable func (n *NodeValidatableResource) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { + if n.Config == nil { + return diags + } + diags = diags.Append(n.validateResource(ctx)) diags = diags.Append(n.validateCheckRules(ctx, n.Config)) diff --git a/internal/terraform/transform_config.go b/internal/terraform/transform_config.go index 5f7585c99b..023dc63637 100644 --- a/internal/terraform/transform_config.go +++ b/internal/terraform/transform_config.go @@ -35,8 +35,18 @@ type ConfigTransformer struct { // Do not apply this transformer. skip bool - // configuration resources that are to be imported + // importTargets specifies a slice of addresses that will have state + // imported for them. importTargets []*ImportTarget + + // generateConfigForImportTargets tells the graph to generate config for any + // import targets that are not contained within config. + // + // If this is false and an import target has no config, the graph will + // simply import the state for the target and any follow-up operations will + // try to delete the imported resource unless the config is updated + // manually. + generateConfigForImportTargets bool } func (t *ConfigTransformer) Transform(g *Graph) error { @@ -50,23 +60,23 @@ func (t *ConfigTransformer) Transform(g *Graph) error { } // Start the transformation process - return t.transform(g, t.Config) + return t.transform(g, t.Config, t.generateConfigForImportTargets) } -func (t *ConfigTransformer) transform(g *Graph, config *configs.Config) error { +func (t *ConfigTransformer) transform(g *Graph, config *configs.Config, generateConfig bool) error { // If no config, do nothing if config == nil { return nil } // Add our resources - if err := t.transformSingle(g, config); err != nil { + if err := t.transformSingle(g, config, generateConfig); err != nil { return err } - // Transform all the children. + // Transform all the children without generating config. for _, c := range config.Children { - if err := t.transform(g, c); err != nil { + if err := t.transform(g, c, false); err != nil { return err } } @@ -74,7 +84,7 @@ func (t *ConfigTransformer) transform(g *Graph, config *configs.Config) error { return nil } -func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) error { +func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config, generateConfig bool) error { path := config.Path module := config.Module log.Printf("[TRACE] ConfigTransformer: Starting for path: %v", path) @@ -87,6 +97,10 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er allResources = append(allResources, r) } + // Take a copy of the import targets, so we can edit them as we go. + var importTargets []*ImportTarget + importTargets = append(importTargets, t.importTargets...) + for _, r := range allResources { relAddr := r.Addr() @@ -99,12 +113,28 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er // filter them down to the applicable addresses. var imports []*ImportTarget configAddr := relAddr.InModule(path) - for _, i := range t.importTargets { + + var matchedIndices []int + for ix, i := range importTargets { if target := i.Addr.ContainingResource().Config(); target.Equal(configAddr) { + // This import target has been claimed by an actual resource, + // let's make a note of this to remove it from the targets. + matchedIndices = append(matchedIndices, ix) imports = append(imports, i) } } + for ix := len(matchedIndices) - 1; ix >= 0; ix-- { + tIx := matchedIndices[ix] + + // We do this backwards, since it means we don't have to adjust the + // later indices as we change the length of import targets. + // + // We need to do this separately, as a single resource could match + // multiple import targets. + importTargets = append(importTargets[:tIx], importTargets[tIx+1:]...) + } + abstract := &NodeAbstractResource{ Addr: addrs.ConfigResource{ Resource: relAddr, @@ -121,5 +151,29 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er g.Add(node) } + if generateConfig { + // If any import targets were not claimed by resources, then we will + // generate config for them. + for _, i := range importTargets { + if !i.Addr.Module.IsRoot() { + // We only generate config for resources imported into the root + // module. + continue + } + + abstract := &NodeAbstractResource{ + Addr: i.Addr.ConfigResource(), + importTargets: []*ImportTarget{i}, + } + + var node dag.Vertex = abstract + if f := t.Concrete; f != nil { + node = f(abstract) + } + + g.Add(node) + } + } + return nil }