mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #31283 from hashicorp/jbardin/plan-import
Use plan graph for importing resources
This commit is contained in:
commit
77e6b622f8
@ -45,7 +45,6 @@ func (c *ImportCommand) Run(args []string) int {
|
|||||||
cmdFlags.StringVar(&configPath, "config", pwd, "path")
|
cmdFlags.StringVar(&configPath, "config", pwd, "path")
|
||||||
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
|
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
|
||||||
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
|
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
|
||||||
cmdFlags.BoolVar(&c.Meta.allowMissingConfig, "allow-missing-config", false, "allow missing config")
|
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
return 1
|
return 1
|
||||||
@ -135,7 +134,7 @@ func (c *ImportCommand) Run(args []string) int {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !c.Meta.allowMissingConfig && rc == nil {
|
if rc == nil {
|
||||||
modulePath := addr.Module.String()
|
modulePath := addr.Module.String()
|
||||||
if modulePath == "" {
|
if modulePath == "" {
|
||||||
modulePath = "the root module"
|
modulePath = "the root module"
|
||||||
@ -262,10 +261,6 @@ func (c *ImportCommand) Run(args []string) int {
|
|||||||
|
|
||||||
c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg))
|
c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg))
|
||||||
|
|
||||||
if c.Meta.allowMissingConfig && rc == nil {
|
|
||||||
c.Ui.Output(c.Colorize().Color("[reset][yellow]\n" + importCommandAllowMissingResourceMsg))
|
|
||||||
}
|
|
||||||
|
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return 1
|
return 1
|
||||||
@ -310,8 +305,6 @@ Options:
|
|||||||
If no config files are present, they must be provided
|
If no config files are present, they must be provided
|
||||||
via the input prompts or env vars.
|
via the input prompts or env vars.
|
||||||
|
|
||||||
-allow-missing-config Allow import when no resource configuration block exists.
|
|
||||||
|
|
||||||
-input=false Disable interactive input prompts.
|
-input=false Disable interactive input prompts.
|
||||||
|
|
||||||
-lock=false Don't hold a state lock during the operation. This is
|
-lock=false Don't hold a state lock during the operation. This is
|
||||||
@ -361,12 +354,3 @@ const importCommandSuccessMsg = `Import successful!
|
|||||||
The resources that were imported are shown above. These resources are now in
|
The resources that were imported are shown above. These resources are now in
|
||||||
your Terraform state and will henceforth be managed by Terraform.
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
`
|
`
|
||||||
|
|
||||||
const importCommandAllowMissingResourceMsg = `Import does not generate resource configuration, you must create a resource
|
|
||||||
configuration block that matches the current or desired state manually.
|
|
||||||
|
|
||||||
If there is no matching resource configuration block for the imported
|
|
||||||
resource, Terraform will delete the resource on the next "terraform apply".
|
|
||||||
It is recommended that you run "terraform plan" to verify that the
|
|
||||||
configuration is correct and complete.
|
|
||||||
`
|
|
||||||
|
@ -644,63 +644,6 @@ func TestImport_providerConfigWithVarFile(t *testing.T) {
|
|||||||
testStateOutput(t, statePath, testImportStr)
|
testStateOutput(t, statePath, testImportStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImport_allowMissingResourceConfig(t *testing.T) {
|
|
||||||
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
|
|
||||||
|
|
||||||
statePath := testTempFile(t)
|
|
||||||
|
|
||||||
p := testProvider()
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
view, _ := testView(t)
|
|
||||||
c := &ImportCommand{
|
|
||||||
Meta: Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ImportResourceStateFn = nil
|
|
||||||
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
|
|
||||||
ImportedResources: []providers.ImportedResource{
|
|
||||||
{
|
|
||||||
TypeName: "test_instance",
|
|
||||||
State: cty.ObjectVal(map[string]cty.Value{
|
|
||||||
"id": cty.StringVal("yay"),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
|
||||||
ResourceTypes: map[string]providers.Schema{
|
|
||||||
"test_instance": {
|
|
||||||
Block: &configschema.Block{
|
|
||||||
Attributes: map[string]*configschema.Attribute{
|
|
||||||
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-state", statePath,
|
|
||||||
"-allow-missing-config",
|
|
||||||
"test_instance.foo",
|
|
||||||
"bar",
|
|
||||||
}
|
|
||||||
|
|
||||||
if code := c.Run(args); code != 0 {
|
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.ImportResourceStateCalled {
|
|
||||||
t.Fatal("ImportResourceState should be called")
|
|
||||||
}
|
|
||||||
|
|
||||||
testStateOutput(t, statePath, testImportStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImport_emptyConfig(t *testing.T) {
|
func TestImport_emptyConfig(t *testing.T) {
|
||||||
defer testChdir(t, testFixturePath("empty"))()
|
defer testChdir(t, testFixturePath("empty"))()
|
||||||
|
|
||||||
|
@ -231,9 +231,6 @@ type Meta struct {
|
|||||||
migrateState bool
|
migrateState bool
|
||||||
compactWarnings bool
|
compactWarnings bool
|
||||||
|
|
||||||
// Used with the import command to allow import of state when no matching config exists.
|
|
||||||
allowMissingConfig bool
|
|
||||||
|
|
||||||
// Used with commands which write state to allow users to write remote
|
// Used with commands which write state to allow users to write remote
|
||||||
// state even if the remote and local Terraform versions don't match.
|
// state even if the remote and local Terraform versions don't match.
|
||||||
ignoreRemoteVersion bool
|
ignoreRemoteVersion bool
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ func TestBackendMigrate_promptMultiStatePattern(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for name, tc := range cases {
|
for name, tc := range cases {
|
||||||
fmt.Println("Test: ", name)
|
t.Log("Test: ", name)
|
||||||
m := testMetaBackend(t, nil)
|
m := testMetaBackend(t, nil)
|
||||||
input := map[string]string{}
|
input := map[string]string{}
|
||||||
cleanup := testInputMap(t, input)
|
cleanup := testInputMap(t, input)
|
||||||
|
@ -56,11 +56,13 @@ func (c *Context) Import(config *configs.Config, prevRunState *states.State, opt
|
|||||||
variables := opts.SetVariables
|
variables := opts.SetVariables
|
||||||
|
|
||||||
// Initialize our graph builder
|
// Initialize our graph builder
|
||||||
builder := &ImportGraphBuilder{
|
builder := &PlanGraphBuilder{
|
||||||
ImportTargets: opts.Targets,
|
ImportTargets: opts.Targets,
|
||||||
Config: config,
|
Config: config,
|
||||||
|
State: state,
|
||||||
RootVariableValues: variables,
|
RootVariableValues: variables,
|
||||||
Plugins: c.plugins,
|
Plugins: c.plugins,
|
||||||
|
Operation: walkImport,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the graph
|
// Build the graph
|
||||||
|
@ -52,9 +52,20 @@ func TestContextImport_basic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// import 1 of count instances in the configuration
|
||||||
func TestContextImport_countIndex(t *testing.T) {
|
func TestContextImport_countIndex(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
m := testModule(t, "import-provider")
|
m := testModuleInline(t, map[string]string{
|
||||||
|
"main.tf": `
|
||||||
|
provider "aws" {
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "foo" {
|
||||||
|
count = 2
|
||||||
|
}
|
||||||
|
`})
|
||||||
|
|
||||||
ctx := testContext2(t, &ContextOpts{
|
ctx := testContext2(t, &ContextOpts{
|
||||||
Providers: map[addrs.Provider]providers.Factory{
|
Providers: map[addrs.Provider]providers.Factory{
|
||||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||||
@ -779,7 +790,7 @@ func TestContextImport_multiStateSame(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextImport_noConfigModuleImport(t *testing.T) {
|
func TestContextImport_nestedModuleImport(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
m := testModuleInline(t, map[string]string{
|
m := testModuleInline(t, map[string]string{
|
||||||
"main.tf": `
|
"main.tf": `
|
||||||
@ -797,6 +808,9 @@ module "b" {
|
|||||||
source = "./b"
|
source = "./b"
|
||||||
y = module.a[each.key].y
|
y = module.a[each.key].y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "test_resource" "test" {
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
"a/main.tf": `
|
"a/main.tf": `
|
||||||
output "y" {
|
output "y" {
|
||||||
@ -810,6 +824,7 @@ variable "y" {
|
|||||||
|
|
||||||
resource "test_resource" "unused" {
|
resource "test_resource" "unused" {
|
||||||
value = var.y
|
value = var.y
|
||||||
|
// missing required, but should not error
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
@ -823,7 +838,8 @@ resource "test_resource" "unused" {
|
|||||||
ResourceTypes: map[string]*configschema.Block{
|
ResourceTypes: map[string]*configschema.Block{
|
||||||
"test_resource": {
|
"test_resource": {
|
||||||
Attributes: map[string]*configschema.Attribute{
|
Attributes: map[string]*configschema.Attribute{
|
||||||
"id": {Type: cty.String, Computed: true},
|
"id": {Type: cty.String, Computed: true},
|
||||||
|
"required": {Type: cty.String, Required: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -834,17 +850,8 @@ resource "test_resource" "unused" {
|
|||||||
{
|
{
|
||||||
TypeName: "test_resource",
|
TypeName: "test_resource",
|
||||||
State: cty.ObjectVal(map[string]cty.Value{
|
State: cty.ObjectVal(map[string]cty.Value{
|
||||||
"id": cty.StringVal("test"),
|
"id": cty.StringVal("test"),
|
||||||
}),
|
"required": cty.StringVal("value"),
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
|
|
||||||
ImportedResources: []providers.ImportedResource{
|
|
||||||
{
|
|
||||||
TypeName: "test_resource",
|
|
||||||
State: cty.ObjectVal(map[string]cty.Value{
|
|
||||||
"id": cty.StringVal("test"),
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -871,7 +878,7 @@ resource "test_resource" "unused" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ri := state.ResourceInstance(mustResourceInstanceAddr("test_resource.test"))
|
ri := state.ResourceInstance(mustResourceInstanceAddr("test_resource.test"))
|
||||||
expected := `{"id":"test"}`
|
expected := `{"id":"test","required":"value"}`
|
||||||
if ri == nil || ri.Current == nil {
|
if ri == nil || ri.Current == nil {
|
||||||
t.Fatal("no state is recorded for resource instance test_resource.test")
|
t.Fatal("no state is recorded for resource instance test_resource.test")
|
||||||
}
|
}
|
||||||
|
@ -560,6 +560,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
|||||||
Targets: opts.Targets,
|
Targets: opts.Targets,
|
||||||
ForceReplace: opts.ForceReplace,
|
ForceReplace: opts.ForceReplace,
|
||||||
skipRefresh: opts.SkipRefresh,
|
skipRefresh: opts.SkipRefresh,
|
||||||
|
Operation: walkPlan,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
return graph, walkPlan, diags
|
return graph, walkPlan, diags
|
||||||
case plans.RefreshOnlyMode:
|
case plans.RefreshOnlyMode:
|
||||||
@ -571,16 +572,18 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
|||||||
Targets: opts.Targets,
|
Targets: opts.Targets,
|
||||||
skipRefresh: opts.SkipRefresh,
|
skipRefresh: opts.SkipRefresh,
|
||||||
skipPlanChanges: true, // this activates "refresh only" mode.
|
skipPlanChanges: true, // this activates "refresh only" mode.
|
||||||
|
Operation: walkPlan,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
return graph, walkPlan, diags
|
return graph, walkPlan, diags
|
||||||
case plans.DestroyMode:
|
case plans.DestroyMode:
|
||||||
graph, diags := DestroyPlanGraphBuilder(&PlanGraphBuilder{
|
graph, diags := (&PlanGraphBuilder{
|
||||||
Config: config,
|
Config: config,
|
||||||
State: prevRunState,
|
State: prevRunState,
|
||||||
RootVariableValues: opts.SetVariables,
|
RootVariableValues: opts.SetVariables,
|
||||||
Plugins: c.plugins,
|
Plugins: c.plugins,
|
||||||
Targets: opts.Targets,
|
Targets: opts.Targets,
|
||||||
skipRefresh: opts.SkipRefresh,
|
skipRefresh: opts.SkipRefresh,
|
||||||
|
Operation: walkPlanDestroy,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
return graph, walkPlanDestroy, diags
|
return graph, walkPlanDestroy, diags
|
||||||
default:
|
default:
|
||||||
|
@ -55,11 +55,12 @@ func (c *Context) Validate(config *configs.Config) tfdiags.Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
graph, moreDiags := ValidateGraphBuilder(&PlanGraphBuilder{
|
graph, moreDiags := (&PlanGraphBuilder{
|
||||||
Config: config,
|
Config: config,
|
||||||
Plugins: c.plugins,
|
Plugins: c.plugins,
|
||||||
State: states.NewState(),
|
State: states.NewState(),
|
||||||
RootVariableValues: varValues,
|
RootVariableValues: varValues,
|
||||||
|
Operation: walkValidate,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
diags = diags.Append(moreDiags)
|
diags = diags.Append(moreDiags)
|
||||||
if moreDiags.HasErrors() {
|
if moreDiags.HasErrors() {
|
||||||
|
@ -73,7 +73,7 @@ func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *Con
|
|||||||
refreshState = states.NewState().SyncWrapper()
|
refreshState = states.NewState().SyncWrapper()
|
||||||
prevRunState = states.NewState().SyncWrapper()
|
prevRunState = states.NewState().SyncWrapper()
|
||||||
|
|
||||||
case walkPlan, walkPlanDestroy:
|
case walkPlan, walkPlanDestroy, walkImport:
|
||||||
state = inputState.DeepCopy().SyncWrapper()
|
state = inputState.DeepCopy().SyncWrapper()
|
||||||
refreshState = inputState.DeepCopy().SyncWrapper()
|
refreshState = inputState.DeepCopy().SyncWrapper()
|
||||||
prevRunState = inputState.DeepCopy().SyncWrapper()
|
prevRunState = inputState.DeepCopy().SyncWrapper()
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/terraform/internal/dag"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DestroyPlanGraphBuilder(p *PlanGraphBuilder) GraphBuilder {
|
|
||||||
p.ConcreteResourceInstance = func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
||||||
return &NodePlanDestroyableResourceInstance{
|
|
||||||
NodeAbstractResourceInstance: a,
|
|
||||||
skipRefresh: p.skipRefresh,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.destroy = true
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/terraform/internal/addrs"
|
|
||||||
"github.com/hashicorp/terraform/internal/configs"
|
|
||||||
"github.com/hashicorp/terraform/internal/dag"
|
|
||||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ImportGraphBuilder implements GraphBuilder and is responsible for building
|
|
||||||
// a graph for importing resources into Terraform. This is a much, much
|
|
||||||
// simpler graph than a normal configuration graph.
|
|
||||||
type ImportGraphBuilder struct {
|
|
||||||
// ImportTargets are the list of resources to import.
|
|
||||||
ImportTargets []*ImportTarget
|
|
||||||
|
|
||||||
// Module is a configuration to build the graph from. See ImportOpts.Config.
|
|
||||||
Config *configs.Config
|
|
||||||
|
|
||||||
// RootVariableValues are the raw input values for root input variables
|
|
||||||
// given by the caller, which we'll resolve into final values as part
|
|
||||||
// of the plan walk.
|
|
||||||
RootVariableValues InputValues
|
|
||||||
|
|
||||||
// Plugins is a library of plug-in components (providers and
|
|
||||||
// provisioners) available for use.
|
|
||||||
Plugins *contextPlugins
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build builds the graph according to the steps returned by Steps.
|
|
||||||
func (b *ImportGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
|
|
||||||
return (&BasicGraphBuilder{
|
|
||||||
Steps: b.Steps(),
|
|
||||||
Name: "ImportGraphBuilder",
|
|
||||||
}).Build(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Steps returns the ordered list of GraphTransformers that must be executed
|
|
||||||
// to build a complete graph.
|
|
||||||
func (b *ImportGraphBuilder) Steps() []GraphTransformer {
|
|
||||||
// Get the module. If we don't have one, we just use an empty tree
|
|
||||||
// so that the transform still works but does nothing.
|
|
||||||
config := b.Config
|
|
||||||
if config == nil {
|
|
||||||
config = configs.NewEmptyConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom factory for creating providers.
|
|
||||||
concreteProvider := func(a *NodeAbstractProvider) dag.Vertex {
|
|
||||||
return &NodeApplyableProvider{
|
|
||||||
NodeAbstractProvider: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
steps := []GraphTransformer{
|
|
||||||
// Create all our resources from the configuration and state
|
|
||||||
&ConfigTransformer{Config: config},
|
|
||||||
|
|
||||||
// Add dynamic values
|
|
||||||
&RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues},
|
|
||||||
&ModuleVariableTransformer{Config: b.Config},
|
|
||||||
&LocalTransformer{Config: b.Config},
|
|
||||||
&OutputTransformer{Config: b.Config},
|
|
||||||
|
|
||||||
// Attach the configuration to any resources
|
|
||||||
&AttachResourceConfigTransformer{Config: b.Config},
|
|
||||||
|
|
||||||
// Add the import steps
|
|
||||||
&ImportStateTransformer{Targets: b.ImportTargets, Config: b.Config},
|
|
||||||
|
|
||||||
transformProviders(concreteProvider, config),
|
|
||||||
|
|
||||||
// Must attach schemas before ReferenceTransformer so that we can
|
|
||||||
// analyze the configuration to find references.
|
|
||||||
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
|
||||||
|
|
||||||
// Create expansion nodes for all of the module calls. This must
|
|
||||||
// come after all other transformers that create nodes representing
|
|
||||||
// objects that can belong to modules.
|
|
||||||
&ModuleExpansionTransformer{Config: b.Config},
|
|
||||||
|
|
||||||
// Connect so that the references are ready for targeting. We'll
|
|
||||||
// have to connect again later for providers and so on.
|
|
||||||
&ReferenceTransformer{},
|
|
||||||
|
|
||||||
// Make sure data sources are aware of any depends_on from the
|
|
||||||
// configuration
|
|
||||||
&attachDataResourceDependsOnTransformer{},
|
|
||||||
|
|
||||||
// Close opened plugin connections
|
|
||||||
&CloseProviderTransformer{},
|
|
||||||
|
|
||||||
// Close root module
|
|
||||||
&CloseRootModuleTransformer{},
|
|
||||||
|
|
||||||
// Optimize
|
|
||||||
&TransitiveReductionTransformer{},
|
|
||||||
}
|
|
||||||
|
|
||||||
return steps
|
|
||||||
}
|
|
@ -1,6 +1,8 @@
|
|||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/addrs"
|
"github.com/hashicorp/terraform/internal/addrs"
|
||||||
"github.com/hashicorp/terraform/internal/configs"
|
"github.com/hashicorp/terraform/internal/configs"
|
||||||
"github.com/hashicorp/terraform/internal/dag"
|
"github.com/hashicorp/terraform/internal/dag"
|
||||||
@ -52,10 +54,6 @@ type PlanGraphBuilder struct {
|
|||||||
// where we _only_ do the refresh step.)
|
// where we _only_ do the refresh step.)
|
||||||
skipPlanChanges bool
|
skipPlanChanges bool
|
||||||
|
|
||||||
// CustomConcrete can be set to customize the node types created
|
|
||||||
// for various parts of the plan. This is useful in order to customize
|
|
||||||
// the plan behavior.
|
|
||||||
CustomConcrete bool
|
|
||||||
ConcreteProvider ConcreteProviderNodeFunc
|
ConcreteProvider ConcreteProviderNodeFunc
|
||||||
ConcreteResource ConcreteResourceNodeFunc
|
ConcreteResource ConcreteResourceNodeFunc
|
||||||
ConcreteResourceInstance ConcreteResourceInstanceNodeFunc
|
ConcreteResourceInstance ConcreteResourceInstanceNodeFunc
|
||||||
@ -63,12 +61,16 @@ type PlanGraphBuilder struct {
|
|||||||
ConcreteResourceInstanceDeposed ConcreteResourceInstanceDeposedNodeFunc
|
ConcreteResourceInstanceDeposed ConcreteResourceInstanceDeposedNodeFunc
|
||||||
ConcreteModule ConcreteModuleNodeFunc
|
ConcreteModule ConcreteModuleNodeFunc
|
||||||
|
|
||||||
// destroy is set to true when create a full destroy plan.
|
// Plan Operation this graph will be used for.
|
||||||
destroy bool
|
Operation walkOperation
|
||||||
|
|
||||||
|
// ImportTargets are the list of resources to import.
|
||||||
|
ImportTargets []*ImportTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
// See GraphBuilder
|
// See GraphBuilder
|
||||||
func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
|
func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
|
||||||
|
log.Printf("[TRACE] building graph for %s", b.Operation)
|
||||||
return (&BasicGraphBuilder{
|
return (&BasicGraphBuilder{
|
||||||
Steps: b.Steps(),
|
Steps: b.Steps(),
|
||||||
Name: "PlanGraphBuilder",
|
Name: "PlanGraphBuilder",
|
||||||
@ -77,14 +79,29 @@ func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Dia
|
|||||||
|
|
||||||
// See GraphBuilder
|
// See GraphBuilder
|
||||||
func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||||
b.init()
|
switch b.Operation {
|
||||||
|
case walkPlan:
|
||||||
|
b.initPlan()
|
||||||
|
case walkPlanDestroy:
|
||||||
|
b.initDestroy()
|
||||||
|
case walkValidate:
|
||||||
|
b.initValidate()
|
||||||
|
case walkImport:
|
||||||
|
b.initImport()
|
||||||
|
default:
|
||||||
|
panic("invalid plan operation: " + b.Operation.String())
|
||||||
|
}
|
||||||
|
|
||||||
steps := []GraphTransformer{
|
steps := []GraphTransformer{
|
||||||
// Creates all the resources represented in the config
|
// Creates all the resources represented in the config
|
||||||
&ConfigTransformer{
|
&ConfigTransformer{
|
||||||
Concrete: b.ConcreteResource,
|
Concrete: b.ConcreteResource,
|
||||||
Config: b.Config,
|
Config: b.Config,
|
||||||
skip: b.destroy,
|
|
||||||
|
// Resources are not added from the config on destroy.
|
||||||
|
skip: b.Operation == walkPlanDestroy,
|
||||||
|
|
||||||
|
importTargets: b.ImportTargets,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Add dynamic values
|
// Add dynamic values
|
||||||
@ -94,7 +111,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||||||
&OutputTransformer{
|
&OutputTransformer{
|
||||||
Config: b.Config,
|
Config: b.Config,
|
||||||
RefreshOnly: b.skipPlanChanges,
|
RefreshOnly: b.skipPlanChanges,
|
||||||
removeRootOutputs: b.destroy,
|
removeRootOutputs: b.Operation == walkPlanDestroy,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Add orphan resources
|
// Add orphan resources
|
||||||
@ -102,13 +119,14 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||||||
Concrete: b.ConcreteResourceOrphan,
|
Concrete: b.ConcreteResourceOrphan,
|
||||||
State: b.State,
|
State: b.State,
|
||||||
Config: b.Config,
|
Config: b.Config,
|
||||||
skip: b.destroy,
|
skip: b.Operation == walkPlanDestroy,
|
||||||
},
|
},
|
||||||
|
|
||||||
// We also need nodes for any deposed instance objects present in the
|
// We also need nodes for any deposed instance objects present in the
|
||||||
// state, so we can plan to destroy them. (This intentionally
|
// state, so we can plan to destroy them. (During plan this will
|
||||||
// skips creating nodes for _current_ objects, since ConfigTransformer
|
// intentionally skip creating nodes for _current_ objects, since
|
||||||
// created nodes that will do that during DynamicExpand.)
|
// ConfigTransformer created nodes that will do that during
|
||||||
|
// DynamicExpand.)
|
||||||
&StateTransformer{
|
&StateTransformer{
|
||||||
ConcreteCurrent: b.ConcreteResourceInstance,
|
ConcreteCurrent: b.ConcreteResourceInstance,
|
||||||
ConcreteDeposed: b.ConcreteResourceInstanceDeposed,
|
ConcreteDeposed: b.ConcreteResourceInstanceDeposed,
|
||||||
@ -172,12 +190,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||||||
return steps
|
return steps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *PlanGraphBuilder) init() {
|
func (b *PlanGraphBuilder) initPlan() {
|
||||||
// Do nothing if the user requests customizing the fields
|
|
||||||
if b.CustomConcrete {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
|
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
|
||||||
return &NodeApplyableProvider{
|
return &NodeApplyableProvider{
|
||||||
NodeAbstractProvider: a,
|
NodeAbstractProvider: a,
|
||||||
@ -210,5 +223,61 @@ func (b *PlanGraphBuilder) init() {
|
|||||||
skipPlanChanges: b.skipPlanChanges,
|
skipPlanChanges: b.skipPlanChanges,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PlanGraphBuilder) initDestroy() {
|
||||||
|
b.initPlan()
|
||||||
|
|
||||||
|
b.ConcreteResourceInstance = func(a *NodeAbstractResourceInstance) dag.Vertex {
|
||||||
|
return &NodePlanDestroyableResourceInstance{
|
||||||
|
NodeAbstractResourceInstance: a,
|
||||||
|
skipRefresh: b.skipRefresh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PlanGraphBuilder) initValidate() {
|
||||||
|
// Set the provider to the normal provider. This will ask for input.
|
||||||
|
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
|
||||||
|
return &NodeApplyableProvider{
|
||||||
|
NodeAbstractProvider: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
return &NodeValidatableResource{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ConcreteModule = func(n *nodeExpandModule) dag.Vertex {
|
||||||
|
return &nodeValidateModule{
|
||||||
|
nodeExpandModule: *n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PlanGraphBuilder) initImport() {
|
||||||
|
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
|
||||||
|
return &NodeApplyableProvider{
|
||||||
|
NodeAbstractProvider: a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
|
||||||
|
return &nodeExpandPlannableResource{
|
||||||
|
NodeAbstractResource: a,
|
||||||
|
|
||||||
|
// For now we always skip planning changes for import, since we are
|
||||||
|
// not going to combine importing with other changes. This is
|
||||||
|
// temporary to try and maintain existing import behaviors, but
|
||||||
|
// planning will need to be allowed for more complex configurations.
|
||||||
|
skipPlanChanges: true,
|
||||||
|
|
||||||
|
// We also skip refresh for now, since the plan output is written
|
||||||
|
// as the new state, and users are not expecting the import process
|
||||||
|
// to update any other instances in state.
|
||||||
|
skipRefresh: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,9 @@ func TestPlanGraphBuilder(t *testing.T) {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
b := &PlanGraphBuilder{
|
b := &PlanGraphBuilder{
|
||||||
Config: testModule(t, "graph-builder-plan-basic"),
|
Config: testModule(t, "graph-builder-plan-basic"),
|
||||||
Plugins: plugins,
|
Plugins: plugins,
|
||||||
|
Operation: walkPlan,
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := b.Build(addrs.RootModuleInstance)
|
g, err := b.Build(addrs.RootModuleInstance)
|
||||||
@ -76,8 +77,9 @@ func TestPlanGraphBuilder_dynamicBlock(t *testing.T) {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
b := &PlanGraphBuilder{
|
b := &PlanGraphBuilder{
|
||||||
Config: testModule(t, "graph-builder-plan-dynblock"),
|
Config: testModule(t, "graph-builder-plan-dynblock"),
|
||||||
Plugins: plugins,
|
Plugins: plugins,
|
||||||
|
Operation: walkPlan,
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := b.Build(addrs.RootModuleInstance)
|
g, err := b.Build(addrs.RootModuleInstance)
|
||||||
@ -131,8 +133,9 @@ func TestPlanGraphBuilder_attrAsBlocks(t *testing.T) {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
b := &PlanGraphBuilder{
|
b := &PlanGraphBuilder{
|
||||||
Config: testModule(t, "graph-builder-plan-attr-as-blocks"),
|
Config: testModule(t, "graph-builder-plan-attr-as-blocks"),
|
||||||
Plugins: plugins,
|
Plugins: plugins,
|
||||||
|
Operation: walkPlan,
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := b.Build(addrs.RootModuleInstance)
|
g, err := b.Build(addrs.RootModuleInstance)
|
||||||
@ -173,6 +176,7 @@ func TestPlanGraphBuilder_targetModule(t *testing.T) {
|
|||||||
Targets: []addrs.Targetable{
|
Targets: []addrs.Targetable{
|
||||||
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
|
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
|
||||||
},
|
},
|
||||||
|
Operation: walkPlan,
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := b.Build(addrs.RootModuleInstance)
|
g, err := b.Build(addrs.RootModuleInstance)
|
||||||
@ -194,8 +198,9 @@ func TestPlanGraphBuilder_forEach(t *testing.T) {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
b := &PlanGraphBuilder{
|
b := &PlanGraphBuilder{
|
||||||
Config: testModule(t, "plan-for-each"),
|
Config: testModule(t, "plan-for-each"),
|
||||||
Plugins: plugins,
|
Plugins: plugins,
|
||||||
|
Operation: walkPlan,
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := b.Build(addrs.RootModuleInstance)
|
g, err := b.Build(addrs.RootModuleInstance)
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/terraform/internal/dag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValidateGraphBuilder creates the graph for the validate operation.
|
|
||||||
//
|
|
||||||
// ValidateGraphBuilder is based on the PlanGraphBuilder. We do this so that
|
|
||||||
// we only have to validate what we'd normally plan anyways. The
|
|
||||||
// PlanGraphBuilder given will be modified so it shouldn't be used for anything
|
|
||||||
// else after calling this function.
|
|
||||||
func ValidateGraphBuilder(p *PlanGraphBuilder) GraphBuilder {
|
|
||||||
// We're going to customize the concrete functions
|
|
||||||
p.CustomConcrete = true
|
|
||||||
|
|
||||||
// Set the provider to the normal provider. This will ask for input.
|
|
||||||
p.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
|
|
||||||
return &NodeApplyableProvider{
|
|
||||||
NodeAbstractProvider: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
|
|
||||||
return &NodeValidatableResource{
|
|
||||||
NodeAbstractResource: a,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ConcreteModule = func(n *nodeExpandModule) dag.Vertex {
|
|
||||||
return &nodeValidateModule{
|
|
||||||
nodeExpandModule: *n,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We purposely don't set any other concrete types since they don't
|
|
||||||
// require validation.
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
@ -68,6 +68,9 @@ type NodeAbstractResource struct {
|
|||||||
|
|
||||||
// The address of the provider this resource will use
|
// The address of the provider this resource will use
|
||||||
ResolvedProvider addrs.AbsProviderConfig
|
ResolvedProvider addrs.AbsProviderConfig
|
||||||
|
|
||||||
|
// This resource may expand into instances which need to be imported.
|
||||||
|
importTargets []*ImportTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -124,6 +127,10 @@ func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable {
|
|||||||
return []addrs.Referenceable{n.Addr.Resource}
|
return []addrs.Referenceable{n.Addr.Resource}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NodeAbstractResource) Import(addr *ImportTarget) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// GraphNodeReferencer
|
// GraphNodeReferencer
|
||||||
func (n *NodeAbstractResource) References() []*addrs.Reference {
|
func (n *NodeAbstractResource) References() []*addrs.Reference {
|
||||||
// If we have a config then we prefer to use that.
|
// If we have a config then we prefer to use that.
|
||||||
|
@ -5,62 +5,11 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/addrs"
|
"github.com/hashicorp/terraform/internal/addrs"
|
||||||
"github.com/hashicorp/terraform/internal/configs"
|
|
||||||
"github.com/hashicorp/terraform/internal/providers"
|
"github.com/hashicorp/terraform/internal/providers"
|
||||||
"github.com/hashicorp/terraform/internal/states"
|
"github.com/hashicorp/terraform/internal/states"
|
||||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImportStateTransformer is a GraphTransformer that adds nodes to the
|
|
||||||
// graph to represent the imports we want to do for resources.
|
|
||||||
type ImportStateTransformer struct {
|
|
||||||
Targets []*ImportTarget
|
|
||||||
Config *configs.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ImportStateTransformer) Transform(g *Graph) error {
|
|
||||||
for _, target := range t.Targets {
|
|
||||||
|
|
||||||
// This is only likely to happen in misconfigured tests
|
|
||||||
if t.Config == nil {
|
|
||||||
return fmt.Errorf("cannot import into an empty configuration")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the module config
|
|
||||||
modCfg := t.Config.Descendent(target.Addr.Module.Module())
|
|
||||||
if modCfg == nil {
|
|
||||||
return fmt.Errorf("module %s not found", target.Addr.Module.Module())
|
|
||||||
}
|
|
||||||
|
|
||||||
providerAddr := addrs.AbsProviderConfig{
|
|
||||||
Module: target.Addr.Module.Module(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find the resource config
|
|
||||||
rsCfg := modCfg.Module.ResourceByAddr(target.Addr.Resource.Resource)
|
|
||||||
if rsCfg != nil {
|
|
||||||
// Get the provider FQN for the resource from the resource configuration
|
|
||||||
providerAddr.Provider = rsCfg.Provider
|
|
||||||
|
|
||||||
// Get the alias from the resource's provider local config
|
|
||||||
providerAddr.Alias = rsCfg.ProviderConfigAddr().Alias
|
|
||||||
} else {
|
|
||||||
// Resource has no matching config, so use an implied provider
|
|
||||||
// based on the resource type
|
|
||||||
rsProviderType := target.Addr.Resource.Resource.ImpliedProvider()
|
|
||||||
providerAddr.Provider = modCfg.Module.ImpliedProviderForUnqualifiedType(rsProviderType)
|
|
||||||
}
|
|
||||||
|
|
||||||
node := &graphNodeImportState{
|
|
||||||
Addr: target.Addr,
|
|
||||||
ID: target.ID,
|
|
||||||
ProviderAddr: providerAddr,
|
|
||||||
}
|
|
||||||
g.Add(node)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type graphNodeImportState struct {
|
type graphNodeImportState struct {
|
||||||
Addr addrs.AbsResourceInstance // Addr is the resource address to import into
|
Addr addrs.AbsResourceInstance // Addr is the resource address to import into
|
||||||
ID string // ID is the ID to import as
|
ID string // ID is the ID to import as
|
@ -322,6 +322,17 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|||||||
|
|
||||||
// The concrete resource factory we'll use
|
// The concrete resource factory we'll use
|
||||||
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
||||||
|
// check if this node is being imported first
|
||||||
|
for _, importTarget := range n.importTargets {
|
||||||
|
if importTarget.Addr.Equal(a.Addr) {
|
||||||
|
return &graphNodeImportState{
|
||||||
|
Addr: importTarget.Addr,
|
||||||
|
ID: importTarget.ID,
|
||||||
|
ResolvedProvider: n.ResolvedProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the config and state since we don't do that via transforms
|
// Add the config and state since we don't do that via transforms
|
||||||
a.Config = n.Config
|
a.Config = n.Config
|
||||||
a.ResolvedProvider = n.ResolvedProvider
|
a.ResolvedProvider = n.ResolvedProvider
|
||||||
|
@ -3,5 +3,4 @@ provider "aws" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_instance" "foo" {
|
resource "aws_instance" "foo" {
|
||||||
id = "bar"
|
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,9 @@ type ConfigTransformer struct {
|
|||||||
|
|
||||||
// Do not apply this transformer.
|
// Do not apply this transformer.
|
||||||
skip bool
|
skip bool
|
||||||
|
|
||||||
|
// configuration resources that are to be imported
|
||||||
|
importTargets []*ImportTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ConfigTransformer) Transform(g *Graph) error {
|
func (t *ConfigTransformer) Transform(g *Graph) error {
|
||||||
@ -89,11 +92,22 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If any of the import targets can apply to this node's instances,
|
||||||
|
// filter them down to the applicable addresses.
|
||||||
|
var imports []*ImportTarget
|
||||||
|
configAddr := relAddr.InModule(path)
|
||||||
|
for _, i := range t.importTargets {
|
||||||
|
if target := i.Addr.ContainingResource().Config(); target.Equal(configAddr) {
|
||||||
|
imports = append(imports, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract := &NodeAbstractResource{
|
abstract := &NodeAbstractResource{
|
||||||
Addr: addrs.ConfigResource{
|
Addr: addrs.ConfigResource{
|
||||||
Resource: relAddr,
|
Resource: relAddr,
|
||||||
Module: path,
|
Module: path,
|
||||||
},
|
},
|
||||||
|
importTargets: imports,
|
||||||
}
|
}
|
||||||
|
|
||||||
var node dag.Vertex = abstract
|
var node dag.Vertex = abstract
|
||||||
|
@ -49,58 +49,6 @@ func TestProviderTransformer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProviderTransformer_ImportModuleChild(t *testing.T) {
|
|
||||||
mod := testModule(t, "import-module")
|
|
||||||
|
|
||||||
g := testProviderTransformerGraph(t, mod)
|
|
||||||
|
|
||||||
{
|
|
||||||
tf := &ImportStateTransformer{
|
|
||||||
Config: mod,
|
|
||||||
Targets: []*ImportTarget{
|
|
||||||
&ImportTarget{
|
|
||||||
Addr: addrs.RootModuleInstance.
|
|
||||||
Child("child", addrs.NoKey).
|
|
||||||
ResourceInstance(
|
|
||||||
addrs.ManagedResourceMode,
|
|
||||||
"aws_instance",
|
|
||||||
"foo",
|
|
||||||
addrs.NoKey,
|
|
||||||
),
|
|
||||||
ID: "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tf.Transform(g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
t.Logf("graph after ImportStateTransformer:\n%s", g.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
tf := &MissingProviderTransformer{}
|
|
||||||
if err := tf.Transform(g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
t.Logf("graph after MissingProviderTransformer:\n%s", g.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
tf := &ProviderTransformer{}
|
|
||||||
if err := tf.Transform(g); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
t.Logf("graph after ProviderTransformer:\n%s", g.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := strings.TrimSpace(g.String())
|
|
||||||
expected := strings.TrimSpace(testTransformImportModuleChildStr)
|
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test providers with FQNs that do not match the typeName
|
// Test providers with FQNs that do not match the typeName
|
||||||
func TestProviderTransformer_fqns(t *testing.T) {
|
func TestProviderTransformer_fqns(t *testing.T) {
|
||||||
for _, mod := range []string{"fqns", "fqns-module"} {
|
for _, mod := range []string{"fqns", "fqns-module"} {
|
||||||
@ -541,12 +489,3 @@ module.child.module.grandchild.aws_instance.baz
|
|||||||
provider["registry.terraform.io/hashicorp/aws"].foo
|
provider["registry.terraform.io/hashicorp/aws"].foo
|
||||||
provider["registry.terraform.io/hashicorp/aws"].foo
|
provider["registry.terraform.io/hashicorp/aws"].foo
|
||||||
`
|
`
|
||||||
|
|
||||||
const testTransformImportModuleChildStr = `
|
|
||||||
module.child.aws_instance.foo
|
|
||||||
provider["registry.terraform.io/hashicorp/aws"]
|
|
||||||
module.child.aws_instance.foo (import id "bar")
|
|
||||||
provider["registry.terraform.io/hashicorp/aws"]
|
|
||||||
module.child.module.nested.aws_instance.foo
|
|
||||||
provider["registry.terraform.io/hashicorp/aws"]
|
|
||||||
provider["registry.terraform.io/hashicorp/aws"]`
|
|
||||||
|
Loading…
Reference in New Issue
Block a user