Plannable import: Add generated config to JSON and human-readable plan output (#33154)

* 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

* Plannable import: Add generated config to json and human-readable plan output

---------

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

View File

@ -468,6 +468,9 @@ func resourceChangeComment(resource jsonplan.ResourceChange, action plans.Action
}
if resource.Change.Importing != nil {
buf.WriteString(fmt.Sprintf("[bold] # %s[reset] will be imported", dispAddr))
if len(resource.Change.GeneratedConfig) > 0 {
buf.WriteString("\n #[reset] (config will be written to generated_config.tf)")
}
printedImported = true
break
}

View File

@ -141,6 +141,49 @@ Terraform will perform the following actions:
value = "Hello, World!"
}
Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.
`,
},
"simple_import_with_generated_config": {
plan: Plan{
ResourceChanges: []jsonplan.ResourceChange{
{
Address: "test_resource.resource",
Mode: "managed",
Type: "test_resource",
Name: "resource",
ProviderName: "test",
Change: jsonplan.Change{
Actions: []string{"no-op"},
Before: marshalJson(t, map[string]interface{}{
"id": "1D5F5E9E-F2E5-401B-9ED5-692A215AC67E",
"value": "Hello, World!",
}),
After: marshalJson(t, map[string]interface{}{
"id": "1D5F5E9E-F2E5-401B-9ED5-692A215AC67E",
"value": "Hello, World!",
}),
Importing: &jsonplan.Importing{
ID: "1D5F5E9E-F2E5-401B-9ED5-692A215AC67E",
},
GeneratedConfig: `resource "test_resource" "resource" {
id = "1D5F5E9E-F2E5-401B-9ED5-692A215AC67E"
value = "Hello, World!"
}`,
},
},
},
},
output: `
Terraform will perform the following actions:
# test_resource.resource will be imported
# (config will be written to generated_config.tf)
resource "test_resource" "resource" {
id = "1D5F5E9E-F2E5-401B-9ED5-692A215AC67E"
value = "Hello, World!"
}
Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.
`,
},

View File

@ -132,6 +132,14 @@ type Change struct {
// of the Importing struct is subject to change, so downstream consumers
// should treat any values in here as strictly optional.
Importing *Importing `json:"importing,omitempty"`
// GeneratedConfig contains any HCL config generated for this resource
// during planning as a string.
//
// If this is populated, then Importing should also be populated but this
// might change in the future. However, nNot all Importing changes will
// contain generated config.
GeneratedConfig string `json:"generated_config,omitempty"`
}
// Importing is a nested object for the resource import metadata.
@ -465,6 +473,7 @@ func MarshalResourceChanges(resources []*plans.ResourceInstanceChangeSrc, schema
AfterSensitive: json.RawMessage(afterSensitive),
ReplacePaths: replacePaths,
Importing: importing,
GeneratedConfig: rc.GeneratedConfig,
}
if rc.DeposedKey != states.NotDeposed {

View File

@ -11,9 +11,10 @@ import (
func NewResourceInstanceChange(change *plans.ResourceInstanceChangeSrc) *ResourceInstanceChange {
c := &ResourceInstanceChange{
Resource: newResourceAddr(change.Addr),
Action: changeAction(change.Action),
Reason: changeReason(change.ActionReason),
Resource: newResourceAddr(change.Addr),
Action: changeAction(change.Action),
Reason: changeReason(change.ActionReason),
GeneratedConfig: change.GeneratedConfig,
}
// The order here matters, we want the moved action to take precedence over
@ -51,6 +52,7 @@ type ResourceInstanceChange struct {
Action ChangeAction `json:"action"`
Reason ChangeReason `json:"reason,omitempty"`
Importing *Importing `json:"importing,omitempty"`
GeneratedConfig string `json:"generated_config,omitempty"`
}
func (c *ResourceInstanceChange) String() string {