Plannable import: Generate config for imported resources during the plan. (#33153)

* command: keep our promises

* remove some nil config checks

Remove some of the safety checks that ensure plan nodes have config attached at the appropriate time.

* add GeneratedConfig to plan changes objects

Add a new GeneratedConfig field alongside Importing in plan changes.

* add config generation package

The genconfig package implements HCL config generation from provider state values.

Thanks to @mildwonkey whose implementation of terraform add is the basis for this package.

* generate config during plan

If a resource is being imported and does not already have config, attempt to generate that config during planning. The config is generated from the state as an HCL string, and then parsed back into an hcl.Body to attach to the plan graph node.

The generated config string is attached to the change emitted by the plan.

* complete config generation prototype, and add tests

---------

Co-authored-by: Katy Moe <katy@katy.moe>
This commit is contained in:
Liam Cervante 2023-05-11 08:38:37 +02:00 committed by GitHub
parent 9904f62bfd
commit 79f7f59155
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1442 additions and 190 deletions

View File

@ -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.

View File

@ -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 {

View File

@ -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"),

View File

@ -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
}

View File

@ -14,6 +14,11 @@ resource "null_resource" "foo" {
}
import {
id = "directory/filename"
to = local_file.foo
}
terraform {
required_providers {
test = {

View File

@ -0,0 +1,2 @@
// Package genconfig implements config generation from provided state values.
package genconfig

View File

@ -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))
}
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 (

View File

@ -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

View File

@ -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)
}
})
}

View File

@ -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

View File

@ -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() {

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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
}