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.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
|
||||
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()) }
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return 1
|
||||
@ -135,7 +134,7 @@ func (c *ImportCommand) Run(args []string) int {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !c.Meta.allowMissingConfig && rc == nil {
|
||||
if rc == nil {
|
||||
modulePath := addr.Module.String()
|
||||
if modulePath == "" {
|
||||
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))
|
||||
|
||||
if c.Meta.allowMissingConfig && rc == nil {
|
||||
c.Ui.Output(c.Colorize().Color("[reset][yellow]\n" + importCommandAllowMissingResourceMsg))
|
||||
}
|
||||
|
||||
c.showDiagnostics(diags)
|
||||
if diags.HasErrors() {
|
||||
return 1
|
||||
@ -310,8 +305,6 @@ Options:
|
||||
If no config files are present, they must be provided
|
||||
via the input prompts or env vars.
|
||||
|
||||
-allow-missing-config Allow import when no resource configuration block exists.
|
||||
|
||||
-input=false Disable interactive input prompts.
|
||||
|
||||
-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
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
defer testChdir(t, testFixturePath("empty"))()
|
||||
|
||||
|
@ -231,9 +231,6 @@ type Meta struct {
|
||||
migrateState 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
|
||||
// state even if the remote and local Terraform versions don't match.
|
||||
ignoreRemoteVersion bool
|
||||
|
@ -1,7 +1,6 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -37,7 +36,7 @@ func TestBackendMigrate_promptMultiStatePattern(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
fmt.Println("Test: ", name)
|
||||
t.Log("Test: ", name)
|
||||
m := testMetaBackend(t, nil)
|
||||
input := map[string]string{}
|
||||
cleanup := testInputMap(t, input)
|
||||
|
@ -56,11 +56,13 @@ func (c *Context) Import(config *configs.Config, prevRunState *states.State, opt
|
||||
variables := opts.SetVariables
|
||||
|
||||
// Initialize our graph builder
|
||||
builder := &ImportGraphBuilder{
|
||||
builder := &PlanGraphBuilder{
|
||||
ImportTargets: opts.Targets,
|
||||
Config: config,
|
||||
State: state,
|
||||
RootVariableValues: variables,
|
||||
Plugins: c.plugins,
|
||||
Operation: walkImport,
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
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")
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
@ -797,6 +808,9 @@ module "b" {
|
||||
source = "./b"
|
||||
y = module.a[each.key].y
|
||||
}
|
||||
|
||||
resource "test_resource" "test" {
|
||||
}
|
||||
`,
|
||||
"a/main.tf": `
|
||||
output "y" {
|
||||
@ -810,6 +824,7 @@ variable "y" {
|
||||
|
||||
resource "test_resource" "unused" {
|
||||
value = var.y
|
||||
// missing required, but should not error
|
||||
}
|
||||
`,
|
||||
})
|
||||
@ -823,7 +838,8 @@ resource "test_resource" "unused" {
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"test_resource": {
|
||||
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",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("test"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "test_resource",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("test"),
|
||||
"id": cty.StringVal("test"),
|
||||
"required": cty.StringVal("value"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
@ -871,7 +878,7 @@ resource "test_resource" "unused" {
|
||||
}
|
||||
|
||||
ri := state.ResourceInstance(mustResourceInstanceAddr("test_resource.test"))
|
||||
expected := `{"id":"test"}`
|
||||
expected := `{"id":"test","required":"value"}`
|
||||
if ri == nil || ri.Current == nil {
|
||||
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,
|
||||
ForceReplace: opts.ForceReplace,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
Operation: walkPlan,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
return graph, walkPlan, diags
|
||||
case plans.RefreshOnlyMode:
|
||||
@ -571,16 +572,18 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
||||
Targets: opts.Targets,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
skipPlanChanges: true, // this activates "refresh only" mode.
|
||||
Operation: walkPlan,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
return graph, walkPlan, diags
|
||||
case plans.DestroyMode:
|
||||
graph, diags := DestroyPlanGraphBuilder(&PlanGraphBuilder{
|
||||
graph, diags := (&PlanGraphBuilder{
|
||||
Config: config,
|
||||
State: prevRunState,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
Operation: walkPlanDestroy,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
return graph, walkPlanDestroy, diags
|
||||
default:
|
||||
|
@ -55,11 +55,12 @@ func (c *Context) Validate(config *configs.Config) tfdiags.Diagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
graph, moreDiags := ValidateGraphBuilder(&PlanGraphBuilder{
|
||||
graph, moreDiags := (&PlanGraphBuilder{
|
||||
Config: config,
|
||||
Plugins: c.plugins,
|
||||
State: states.NewState(),
|
||||
RootVariableValues: varValues,
|
||||
Operation: walkValidate,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
|
@ -73,7 +73,7 @@ func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *Con
|
||||
refreshState = states.NewState().SyncWrapper()
|
||||
prevRunState = states.NewState().SyncWrapper()
|
||||
|
||||
case walkPlan, walkPlanDestroy:
|
||||
case walkPlan, walkPlanDestroy, walkImport:
|
||||
state = inputState.DeepCopy().SyncWrapper()
|
||||
refreshState = 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
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/dag"
|
||||
@ -52,10 +54,6 @@ type PlanGraphBuilder struct {
|
||||
// where we _only_ do the refresh step.)
|
||||
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
|
||||
ConcreteResource ConcreteResourceNodeFunc
|
||||
ConcreteResourceInstance ConcreteResourceInstanceNodeFunc
|
||||
@ -63,12 +61,16 @@ type PlanGraphBuilder struct {
|
||||
ConcreteResourceInstanceDeposed ConcreteResourceInstanceDeposedNodeFunc
|
||||
ConcreteModule ConcreteModuleNodeFunc
|
||||
|
||||
// destroy is set to true when create a full destroy plan.
|
||||
destroy bool
|
||||
// Plan Operation this graph will be used for.
|
||||
Operation walkOperation
|
||||
|
||||
// ImportTargets are the list of resources to import.
|
||||
ImportTargets []*ImportTarget
|
||||
}
|
||||
|
||||
// See GraphBuilder
|
||||
func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
|
||||
log.Printf("[TRACE] building graph for %s", b.Operation)
|
||||
return (&BasicGraphBuilder{
|
||||
Steps: b.Steps(),
|
||||
Name: "PlanGraphBuilder",
|
||||
@ -77,14 +79,29 @@ func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Dia
|
||||
|
||||
// See GraphBuilder
|
||||
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{
|
||||
// Creates all the resources represented in the config
|
||||
&ConfigTransformer{
|
||||
Concrete: b.ConcreteResource,
|
||||
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
|
||||
@ -94,7 +111,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||
&OutputTransformer{
|
||||
Config: b.Config,
|
||||
RefreshOnly: b.skipPlanChanges,
|
||||
removeRootOutputs: b.destroy,
|
||||
removeRootOutputs: b.Operation == walkPlanDestroy,
|
||||
},
|
||||
|
||||
// Add orphan resources
|
||||
@ -102,13 +119,14 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||
Concrete: b.ConcreteResourceOrphan,
|
||||
State: b.State,
|
||||
Config: b.Config,
|
||||
skip: b.destroy,
|
||||
skip: b.Operation == walkPlanDestroy,
|
||||
},
|
||||
|
||||
// We also need nodes for any deposed instance objects present in the
|
||||
// state, so we can plan to destroy them. (This intentionally
|
||||
// skips creating nodes for _current_ objects, since ConfigTransformer
|
||||
// created nodes that will do that during DynamicExpand.)
|
||||
// state, so we can plan to destroy them. (During plan this will
|
||||
// intentionally skip creating nodes for _current_ objects, since
|
||||
// ConfigTransformer created nodes that will do that during
|
||||
// DynamicExpand.)
|
||||
&StateTransformer{
|
||||
ConcreteCurrent: b.ConcreteResourceInstance,
|
||||
ConcreteDeposed: b.ConcreteResourceInstanceDeposed,
|
||||
@ -172,12 +190,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||
return steps
|
||||
}
|
||||
|
||||
func (b *PlanGraphBuilder) init() {
|
||||
// Do nothing if the user requests customizing the fields
|
||||
if b.CustomConcrete {
|
||||
return
|
||||
}
|
||||
|
||||
func (b *PlanGraphBuilder) initPlan() {
|
||||
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
|
||||
return &NodeApplyableProvider{
|
||||
NodeAbstractProvider: a,
|
||||
@ -210,5 +223,61 @@ func (b *PlanGraphBuilder) init() {
|
||||
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)
|
||||
|
||||
b := &PlanGraphBuilder{
|
||||
Config: testModule(t, "graph-builder-plan-basic"),
|
||||
Plugins: plugins,
|
||||
Config: testModule(t, "graph-builder-plan-basic"),
|
||||
Plugins: plugins,
|
||||
Operation: walkPlan,
|
||||
}
|
||||
|
||||
g, err := b.Build(addrs.RootModuleInstance)
|
||||
@ -76,8 +77,9 @@ func TestPlanGraphBuilder_dynamicBlock(t *testing.T) {
|
||||
}, nil)
|
||||
|
||||
b := &PlanGraphBuilder{
|
||||
Config: testModule(t, "graph-builder-plan-dynblock"),
|
||||
Plugins: plugins,
|
||||
Config: testModule(t, "graph-builder-plan-dynblock"),
|
||||
Plugins: plugins,
|
||||
Operation: walkPlan,
|
||||
}
|
||||
|
||||
g, err := b.Build(addrs.RootModuleInstance)
|
||||
@ -131,8 +133,9 @@ func TestPlanGraphBuilder_attrAsBlocks(t *testing.T) {
|
||||
}, nil)
|
||||
|
||||
b := &PlanGraphBuilder{
|
||||
Config: testModule(t, "graph-builder-plan-attr-as-blocks"),
|
||||
Plugins: plugins,
|
||||
Config: testModule(t, "graph-builder-plan-attr-as-blocks"),
|
||||
Plugins: plugins,
|
||||
Operation: walkPlan,
|
||||
}
|
||||
|
||||
g, err := b.Build(addrs.RootModuleInstance)
|
||||
@ -173,6 +176,7 @@ func TestPlanGraphBuilder_targetModule(t *testing.T) {
|
||||
Targets: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
|
||||
},
|
||||
Operation: walkPlan,
|
||||
}
|
||||
|
||||
g, err := b.Build(addrs.RootModuleInstance)
|
||||
@ -194,8 +198,9 @@ func TestPlanGraphBuilder_forEach(t *testing.T) {
|
||||
}, nil)
|
||||
|
||||
b := &PlanGraphBuilder{
|
||||
Config: testModule(t, "plan-for-each"),
|
||||
Plugins: plugins,
|
||||
Config: testModule(t, "plan-for-each"),
|
||||
Plugins: plugins,
|
||||
Operation: walkPlan,
|
||||
}
|
||||
|
||||
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
|
||||
ResolvedProvider addrs.AbsProviderConfig
|
||||
|
||||
// This resource may expand into instances which need to be imported.
|
||||
importTargets []*ImportTarget
|
||||
}
|
||||
|
||||
var (
|
||||
@ -124,6 +127,10 @@ func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable {
|
||||
return []addrs.Referenceable{n.Addr.Resource}
|
||||
}
|
||||
|
||||
func (n *NodeAbstractResource) Import(addr *ImportTarget) {
|
||||
|
||||
}
|
||||
|
||||
// GraphNodeReferencer
|
||||
func (n *NodeAbstractResource) References() []*addrs.Reference {
|
||||
// If we have a config then we prefer to use that.
|
||||
|
@ -5,62 +5,11 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"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 {
|
||||
Addr addrs.AbsResourceInstance // Addr is the resource address to import into
|
||||
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
|
||||
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
|
||||
a.Config = n.Config
|
||||
a.ResolvedProvider = n.ResolvedProvider
|
||||
|
@ -3,5 +3,4 @@ provider "aws" {
|
||||
}
|
||||
|
||||
resource "aws_instance" "foo" {
|
||||
id = "bar"
|
||||
}
|
||||
|
@ -31,6 +31,9 @@ type ConfigTransformer struct {
|
||||
|
||||
// Do not apply this transformer.
|
||||
skip bool
|
||||
|
||||
// configuration resources that are to be imported
|
||||
importTargets []*ImportTarget
|
||||
}
|
||||
|
||||
func (t *ConfigTransformer) Transform(g *Graph) error {
|
||||
@ -89,11 +92,22 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er
|
||||
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{
|
||||
Addr: addrs.ConfigResource{
|
||||
Resource: relAddr,
|
||||
Module: path,
|
||||
},
|
||||
importTargets: imports,
|
||||
}
|
||||
|
||||
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
|
||||
func TestProviderTransformer_fqns(t *testing.T) {
|
||||
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
|
||||
`
|
||||
|
||||
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