2017-01-19 05:47:56 +01:00
|
|
|
package local
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2017-06-24 16:54:40 +02:00
|
|
|
"reflect"
|
2017-01-30 04:51:54 +01:00
|
|
|
"strings"
|
2017-01-19 05:47:56 +01:00
|
|
|
"testing"
|
|
|
|
|
2018-10-09 00:22:59 +02:00
|
|
|
"github.com/hashicorp/terraform/addrs"
|
2017-01-19 05:47:56 +01:00
|
|
|
"github.com/hashicorp/terraform/backend"
|
2018-09-28 23:04:57 +02:00
|
|
|
"github.com/hashicorp/terraform/configs/configschema"
|
2019-01-09 03:39:14 +01:00
|
|
|
"github.com/hashicorp/terraform/internal/initwd"
|
2018-09-29 00:57:27 +02:00
|
|
|
"github.com/hashicorp/terraform/plans"
|
|
|
|
"github.com/hashicorp/terraform/plans/planfile"
|
2018-10-09 00:22:59 +02:00
|
|
|
"github.com/hashicorp/terraform/states"
|
2017-01-19 05:47:56 +01:00
|
|
|
"github.com/hashicorp/terraform/terraform"
|
2017-09-09 02:14:37 +02:00
|
|
|
"github.com/mitchellh/cli"
|
2018-05-23 04:57:04 +02:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
2017-01-19 05:47:56 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestLocal_planBasic(t *testing.T) {
|
2018-03-28 16:54:08 +02:00
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
2018-05-23 04:57:04 +02:00
|
|
|
p := TestLocalProvider(t, b, "test", planFixtureSchema())
|
2017-01-19 05:47:56 +01:00
|
|
|
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/plan")
|
2018-03-21 02:43:02 +01:00
|
|
|
defer configCleanup()
|
2017-01-19 05:47:56 +01:00
|
|
|
op.PlanRefresh = true
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
2018-03-21 02:43:02 +01:00
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
|
2018-09-28 23:04:57 +02:00
|
|
|
if !p.PlanResourceChangeCalled {
|
|
|
|
t.Fatal("PlanResourceChange should be called")
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-09 02:14:37 +02:00
|
|
|
func TestLocal_planInAutomation(t *testing.T) {
|
2018-03-28 16:54:08 +02:00
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
2018-05-23 04:57:04 +02:00
|
|
|
TestLocalProvider(t, b, "test", planFixtureSchema())
|
2017-09-09 02:14:37 +02:00
|
|
|
|
|
|
|
const msg = `You didn't specify an "-out" parameter`
|
|
|
|
|
|
|
|
// When we're "in automation" we omit certain text from the
|
|
|
|
// plan output. However, testing for the absense of text is
|
|
|
|
// unreliable in the face of future copy changes, so we'll
|
|
|
|
// mitigate that by running both with and without the flag
|
|
|
|
// set so we can ensure that the expected messages _are_
|
|
|
|
// included the first time.
|
|
|
|
b.RunningInAutomation = false
|
|
|
|
b.CLI = cli.NewMockUi()
|
|
|
|
{
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/plan")
|
2018-03-21 02:43:02 +01:00
|
|
|
defer configCleanup()
|
2017-09-09 02:14:37 +02:00
|
|
|
op.PlanRefresh = true
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
2018-03-21 02:43:02 +01:00
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
2017-09-09 02:14:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if !strings.Contains(output, msg) {
|
|
|
|
t.Fatalf("missing next-steps message when not in automation")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// On the second run, we expect the next-steps messaging to be absent
|
|
|
|
// since we're now "running in automation".
|
|
|
|
b.RunningInAutomation = true
|
|
|
|
b.CLI = cli.NewMockUi()
|
|
|
|
{
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/plan")
|
2018-03-21 02:43:02 +01:00
|
|
|
defer configCleanup()
|
2017-09-09 02:14:37 +02:00
|
|
|
op.PlanRefresh = true
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
2018-03-21 02:43:02 +01:00
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
2017-09-09 02:14:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if strings.Contains(output, msg) {
|
|
|
|
t.Fatalf("next-steps message present when in automation")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-01-30 04:51:54 +01:00
|
|
|
func TestLocal_planNoConfig(t *testing.T) {
|
2018-03-28 16:54:08 +02:00
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
2018-05-23 04:57:04 +02:00
|
|
|
TestLocalProvider(t, b, "test", &terraform.ProviderSchema{})
|
2017-01-30 04:51:54 +01:00
|
|
|
|
2018-03-21 02:43:02 +01:00
|
|
|
b.CLI = cli.NewMockUi()
|
|
|
|
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/empty")
|
2018-03-21 02:43:02 +01:00
|
|
|
defer configCleanup()
|
2017-01-30 04:51:54 +01:00
|
|
|
op.PlanRefresh = true
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
|
|
|
|
2018-03-21 02:43:02 +01:00
|
|
|
if run.Result == backend.OperationSuccess {
|
|
|
|
t.Fatal("plan operation succeeded; want failure")
|
2017-01-30 04:51:54 +01:00
|
|
|
}
|
2018-03-21 02:43:02 +01:00
|
|
|
output := b.CLI.(*cli.MockUi).ErrorWriter.String()
|
|
|
|
if !strings.Contains(output, "configuration") {
|
2017-01-30 04:51:54 +01:00
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-14 14:45:47 +01:00
|
|
|
func TestLocal_planTainted(t *testing.T) {
|
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
|
|
|
p := TestLocalProvider(t, b, "test", planFixtureSchema())
|
|
|
|
testStateFile(t, b.StatePath, testPlanState_tainted())
|
|
|
|
b.CLI = cli.NewMockUi()
|
|
|
|
outDir := testTempDir(t)
|
|
|
|
defer os.RemoveAll(outDir)
|
|
|
|
planPath := filepath.Join(outDir, "plan.tfplan")
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/plan")
|
2018-12-14 14:45:47 +01:00
|
|
|
defer configCleanup()
|
|
|
|
op.PlanRefresh = true
|
|
|
|
op.PlanOutPath = planPath
|
|
|
|
cfg := cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"path": cty.StringVal(b.StatePath),
|
|
|
|
})
|
|
|
|
cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
op.PlanOutBackend = &plans.Backend{
|
|
|
|
// Just a placeholder so that we can generate a valid plan file.
|
|
|
|
Type: "local",
|
|
|
|
Config: cfgRaw,
|
|
|
|
}
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
|
|
|
}
|
|
|
|
if !p.ReadResourceCalled {
|
|
|
|
t.Fatal("ReadResource should be called")
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatal("plan should not be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedOutput := `An execution plan has been generated and is shown below.
|
|
|
|
Resource actions are indicated with the following symbols:
|
|
|
|
-/+ destroy and then create replacement
|
|
|
|
|
|
|
|
Terraform will perform the following actions:
|
|
|
|
|
2019-03-06 01:18:55 +01:00
|
|
|
# test_instance.foo is tainted, so must be replaced
|
2018-12-14 14:45:47 +01:00
|
|
|
-/+ resource "test_instance" "foo" {
|
|
|
|
ami = "bar"
|
|
|
|
|
|
|
|
network_interface {
|
|
|
|
description = "Main network interface"
|
|
|
|
device_index = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Plan: 1 to add, 0 to change, 1 to destroy.`
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if !strings.Contains(output, expectedOutput) {
|
|
|
|
t.Fatalf("Unexpected output:\n%s", output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-03 23:50:32 +02:00
|
|
|
func TestLocal_planDeposedOnly(t *testing.T) {
|
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
|
|
|
p := TestLocalProvider(t, b, "test", planFixtureSchema())
|
|
|
|
testStateFile(t, b.StatePath, states.BuildState(func(ss *states.SyncState) {
|
|
|
|
ss.SetResourceInstanceDeposed(
|
|
|
|
addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "test_instance",
|
|
|
|
Name: "foo",
|
|
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
|
|
states.DeposedKey("00000000"),
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{
|
|
|
|
"ami": "bar",
|
|
|
|
"network_interface": [{
|
|
|
|
"device_index": 0,
|
|
|
|
"description": "Main network interface"
|
|
|
|
}]
|
|
|
|
}`),
|
|
|
|
},
|
Initial steps towards AbsProviderConfig/LocalProviderConfig separation (#23978)
* Introduce "Local" terminology for non-absolute provider config addresses
In a future change AbsProviderConfig and LocalProviderConfig are going to
become two entirely distinct types, rather than Abs embedding Local as
written here. This naming change is in preparation for that subsequent
work, which will also include introducing a new "ProviderConfig" type
that is an interface that AbsProviderConfig and LocalProviderConfig both
implement.
This is intended to be largely just a naming change to get started, so
we can deal with all of the messy renaming. However, this did also require
a slight change in modeling where the Resource.DefaultProviderConfig
method has become Resource.DefaultProvider returning a Provider address
directly, because this method doesn't have enough information to construct
a true and accurate LocalProviderConfig -- it would need to refer to the
configuration to know what this module is calling the provider it has
selected.
In order to leave a trail to follow for subsequent work, all of the
changes here are intended to ensure that remaining work will become
obvious via compile-time errors when all of the following changes happen:
- The concept of "legacy" provider addresses is removed from the addrs
package, including removing addrs.NewLegacyProvider and
addrs.Provider.LegacyString.
- addrs.AbsProviderConfig stops having addrs.LocalProviderConfig embedded
in it and has an addrs.Provider and a string alias directly instead.
- The provider-schema-handling parts of Terraform core are updated to
work with addrs.Provider to identify providers, rather than legacy
strings.
In particular, there are still several codepaths here making legacy
provider address assumptions (in order to limit the scope of this change)
but I've made sure each one is doing something that relies on at least
one of the above changes not having been made yet.
* addrs: ProviderConfig interface
In a (very) few special situations in the main "terraform" package we need
to make runtime decisions about whether a provider config is absolute
or local.
We currently do that by exploiting the fact that AbsProviderConfig has
LocalProviderConfig nested inside of it and so in the local case we can
just ignore the wrapping AbsProviderConfig and use the embedded value.
In a future change we'll be moving away from that embedding and making
these two types distinct in order to represent that mapping between them
requires consulting a lookup table in the configuration, and so here we
introduce a new interface type ProviderConfig that can represent either
AbsProviderConfig or LocalProviderConfig decided dynamically at runtime.
This also includes the Config.ResolveAbsProviderAddr method that will
eventually be responsible for that local-to-absolute translation, so
that callers with access to the configuration can normalize to an
addrs.AbsProviderConfig given a non-nil addrs.ProviderConfig. That's
currently unused because existing callers are still relying on the
simplistic structural transform, but we'll switch them over in a later
commit.
* rename LocalType to LocalName
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2020-01-31 14:23:07 +01:00
|
|
|
addrs.LocalProviderConfig{
|
|
|
|
LocalName: "test",
|
2019-06-03 23:50:32 +02:00
|
|
|
}.Absolute(addrs.RootModuleInstance),
|
|
|
|
)
|
|
|
|
}))
|
|
|
|
b.CLI = cli.NewMockUi()
|
|
|
|
outDir := testTempDir(t)
|
|
|
|
defer os.RemoveAll(outDir)
|
|
|
|
planPath := filepath.Join(outDir, "plan.tfplan")
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/plan")
|
2019-06-03 23:50:32 +02:00
|
|
|
defer configCleanup()
|
|
|
|
op.PlanRefresh = true
|
|
|
|
op.PlanOutPath = planPath
|
|
|
|
cfg := cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"path": cty.StringVal(b.StatePath),
|
|
|
|
})
|
|
|
|
cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
op.PlanOutBackend = &plans.Backend{
|
|
|
|
// Just a placeholder so that we can generate a valid plan file.
|
|
|
|
Type: "local",
|
|
|
|
Config: cfgRaw,
|
|
|
|
}
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
|
|
|
}
|
|
|
|
if !p.ReadResourceCalled {
|
|
|
|
t.Fatal("ReadResource should be called")
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatal("plan should not be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
// The deposed object and the current object are distinct, so our
|
|
|
|
// plan includes separate actions for each of them. This strange situation
|
|
|
|
// is not common: it should arise only if Terraform fails during
|
|
|
|
// a create-before-destroy when the create hasn't completed yet but
|
|
|
|
// in a severe way that prevents the previous object from being restored
|
|
|
|
// as "current".
|
|
|
|
//
|
|
|
|
// However, that situation was more common in some earlier Terraform
|
|
|
|
// versions where deposed objects were not managed properly, so this
|
|
|
|
// can arise when upgrading from an older version with deposed objects
|
|
|
|
// already in the state.
|
|
|
|
//
|
|
|
|
// This is one of the few cases where we expose the idea of "deposed" in
|
|
|
|
// the UI, including the user-unfriendly "deposed key" (00000000 in this
|
|
|
|
// case) just so that users can correlate this with what they might
|
|
|
|
// see in `terraform show` and in the subsequent apply output, because
|
|
|
|
// it's also possible for there to be _multiple_ deposed objects, in the
|
|
|
|
// unlikely event that create_before_destroy _keeps_ crashing across
|
|
|
|
// subsequent runs.
|
|
|
|
expectedOutput := `An execution plan has been generated and is shown below.
|
|
|
|
Resource actions are indicated with the following symbols:
|
|
|
|
+ create
|
|
|
|
- destroy
|
|
|
|
|
|
|
|
Terraform will perform the following actions:
|
|
|
|
|
|
|
|
# test_instance.foo will be created
|
|
|
|
+ resource "test_instance" "foo" {
|
|
|
|
+ ami = "bar"
|
|
|
|
|
|
|
|
+ network_interface {
|
|
|
|
+ description = "Main network interface"
|
|
|
|
+ device_index = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# test_instance.foo (deposed object 00000000) will be destroyed
|
|
|
|
- resource "test_instance" "foo" {
|
|
|
|
- ami = "bar" -> null
|
|
|
|
|
|
|
|
- network_interface {
|
|
|
|
- description = "Main network interface" -> null
|
|
|
|
- device_index = 0 -> null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Plan: 1 to add, 0 to change, 1 to destroy.`
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if !strings.Contains(output, expectedOutput) {
|
|
|
|
t.Fatalf("Unexpected output:\n%s\n\nwant output containing:\n%s", output, expectedOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-14 14:45:47 +01:00
|
|
|
func TestLocal_planTainted_createBeforeDestroy(t *testing.T) {
|
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
|
|
|
p := TestLocalProvider(t, b, "test", planFixtureSchema())
|
|
|
|
testStateFile(t, b.StatePath, testPlanState_tainted())
|
|
|
|
b.CLI = cli.NewMockUi()
|
|
|
|
outDir := testTempDir(t)
|
|
|
|
defer os.RemoveAll(outDir)
|
|
|
|
planPath := filepath.Join(outDir, "plan.tfplan")
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/plan-cbd")
|
2018-12-14 14:45:47 +01:00
|
|
|
defer configCleanup()
|
|
|
|
op.PlanRefresh = true
|
|
|
|
op.PlanOutPath = planPath
|
|
|
|
cfg := cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"path": cty.StringVal(b.StatePath),
|
|
|
|
})
|
|
|
|
cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
op.PlanOutBackend = &plans.Backend{
|
|
|
|
// Just a placeholder so that we can generate a valid plan file.
|
|
|
|
Type: "local",
|
|
|
|
Config: cfgRaw,
|
|
|
|
}
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
|
|
|
}
|
|
|
|
if !p.ReadResourceCalled {
|
|
|
|
t.Fatal("ReadResource should be called")
|
|
|
|
}
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatal("plan should not be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedOutput := `An execution plan has been generated and is shown below.
|
|
|
|
Resource actions are indicated with the following symbols:
|
|
|
|
+/- create replacement and then destroy
|
|
|
|
|
|
|
|
Terraform will perform the following actions:
|
|
|
|
|
2019-03-06 01:18:55 +01:00
|
|
|
# test_instance.foo is tainted, so must be replaced
|
2018-12-14 14:45:47 +01:00
|
|
|
+/- resource "test_instance" "foo" {
|
|
|
|
ami = "bar"
|
|
|
|
|
|
|
|
network_interface {
|
|
|
|
description = "Main network interface"
|
|
|
|
device_index = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Plan: 1 to add, 0 to change, 1 to destroy.`
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if !strings.Contains(output, expectedOutput) {
|
|
|
|
t.Fatalf("Unexpected output:\n%s", output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-19 05:47:56 +01:00
|
|
|
func TestLocal_planRefreshFalse(t *testing.T) {
|
2018-03-28 16:54:08 +02:00
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
2018-10-04 23:37:14 +02:00
|
|
|
|
|
|
|
p := TestLocalProvider(t, b, "test", planFixtureSchema())
|
2018-10-09 00:22:59 +02:00
|
|
|
testStateFile(t, b.StatePath, testPlanState())
|
2017-01-19 05:47:56 +01:00
|
|
|
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/plan")
|
2018-03-21 02:43:02 +01:00
|
|
|
defer configCleanup()
|
2017-01-19 05:47:56 +01:00
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
2018-03-21 02:43:02 +01:00
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
|
2018-09-28 23:04:57 +02:00
|
|
|
if p.ReadResourceCalled {
|
|
|
|
t.Fatal("ReadResource should not be called")
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if !run.PlanEmpty {
|
|
|
|
t.Fatal("plan should be empty")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLocal_planDestroy(t *testing.T) {
|
2018-03-28 16:54:08 +02:00
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
2018-10-04 23:37:14 +02:00
|
|
|
|
2018-05-23 04:57:04 +02:00
|
|
|
p := TestLocalProvider(t, b, "test", planFixtureSchema())
|
2018-10-09 00:22:59 +02:00
|
|
|
testStateFile(t, b.StatePath, testPlanState())
|
2017-01-19 05:47:56 +01:00
|
|
|
|
|
|
|
outDir := testTempDir(t)
|
|
|
|
defer os.RemoveAll(outDir)
|
|
|
|
planPath := filepath.Join(outDir, "plan.tfplan")
|
|
|
|
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/plan")
|
2018-03-21 02:43:02 +01:00
|
|
|
defer configCleanup()
|
2018-10-04 23:37:14 +02:00
|
|
|
op.Destroy = true
|
2017-01-19 05:47:56 +01:00
|
|
|
op.PlanRefresh = true
|
|
|
|
op.PlanOutPath = planPath
|
2018-10-09 21:19:24 +02:00
|
|
|
cfg := cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"path": cty.StringVal(b.StatePath),
|
|
|
|
})
|
|
|
|
cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
op.PlanOutBackend = &plans.Backend{
|
|
|
|
// Just a placeholder so that we can generate a valid plan file.
|
2018-10-10 01:32:09 +02:00
|
|
|
Type: "local",
|
2018-10-09 21:19:24 +02:00
|
|
|
Config: cfgRaw,
|
|
|
|
}
|
2017-01-19 05:47:56 +01:00
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
2018-03-21 02:43:02 +01:00
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
|
2018-09-28 23:04:57 +02:00
|
|
|
if !p.ReadResourceCalled {
|
|
|
|
t.Fatal("ReadResource should be called")
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatal("plan should not be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
plan := testReadPlan(t, planPath)
|
2018-10-04 23:37:14 +02:00
|
|
|
for _, r := range plan.Changes.Resources {
|
|
|
|
if r.Action.String() != "Delete" {
|
|
|
|
t.Fatalf("bad: %#v", r.Action.String())
|
|
|
|
}
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-12 17:01:18 +01:00
|
|
|
func TestLocal_planDestroy_withDataSources(t *testing.T) {
|
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
p := TestLocalProvider(t, b, "test", planFixtureSchema())
|
|
|
|
testStateFile(t, b.StatePath, testPlanState_withDataSource())
|
|
|
|
|
|
|
|
b.CLI = cli.NewMockUi()
|
|
|
|
|
|
|
|
outDir := testTempDir(t)
|
|
|
|
defer os.RemoveAll(outDir)
|
|
|
|
planPath := filepath.Join(outDir, "plan.tfplan")
|
|
|
|
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/destroy-with-ds")
|
2018-12-12 17:01:18 +01:00
|
|
|
defer configCleanup()
|
|
|
|
op.Destroy = true
|
|
|
|
op.PlanRefresh = true
|
|
|
|
op.PlanOutPath = planPath
|
|
|
|
cfg := cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"path": cty.StringVal(b.StatePath),
|
|
|
|
})
|
|
|
|
cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
op.PlanOutBackend = &plans.Backend{
|
|
|
|
// Just a placeholder so that we can generate a valid plan file.
|
|
|
|
Type: "local",
|
|
|
|
Config: cfgRaw,
|
|
|
|
}
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !p.ReadResourceCalled {
|
|
|
|
t.Fatal("ReadResource should be called")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !p.ReadDataSourceCalled {
|
|
|
|
t.Fatal("ReadDataSourceCalled should be called")
|
|
|
|
}
|
|
|
|
|
|
|
|
if run.PlanEmpty {
|
|
|
|
t.Fatal("plan should not be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Data source should still exist in the the plan file
|
|
|
|
plan := testReadPlan(t, planPath)
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatalf("Expected exactly 1 resource for destruction, %d given: %q",
|
|
|
|
len(plan.Changes.Resources), getAddrs(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Data source should not be rendered in the output
|
|
|
|
expectedOutput := `Terraform will perform the following actions:
|
|
|
|
|
|
|
|
# test_instance.foo will be destroyed
|
|
|
|
- resource "test_instance" "foo" {
|
|
|
|
- ami = "bar" -> null
|
|
|
|
|
|
|
|
- network_interface {
|
|
|
|
- description = "Main network interface" -> null
|
|
|
|
- device_index = 0 -> null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Plan: 0 to add, 0 to change, 1 to destroy.`
|
|
|
|
|
|
|
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
|
|
|
if !strings.Contains(output, expectedOutput) {
|
|
|
|
t.Fatalf("Unexpected output (expected no data source):\n%s", output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAddrs(resources []*plans.ResourceInstanceChangeSrc) []string {
|
|
|
|
addrs := make([]string, len(resources), len(resources))
|
|
|
|
for i, r := range resources {
|
|
|
|
addrs[i] = r.Addr.String()
|
|
|
|
}
|
|
|
|
return addrs
|
|
|
|
}
|
|
|
|
|
2017-01-19 05:47:56 +01:00
|
|
|
func TestLocal_planOutPathNoChange(t *testing.T) {
|
2018-03-28 16:54:08 +02:00
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
2018-05-23 04:57:04 +02:00
|
|
|
TestLocalProvider(t, b, "test", planFixtureSchema())
|
2018-10-09 00:22:59 +02:00
|
|
|
testStateFile(t, b.StatePath, testPlanState())
|
2017-01-19 05:47:56 +01:00
|
|
|
|
|
|
|
outDir := testTempDir(t)
|
|
|
|
defer os.RemoveAll(outDir)
|
|
|
|
planPath := filepath.Join(outDir, "plan.tfplan")
|
|
|
|
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/plan")
|
2018-03-21 02:43:02 +01:00
|
|
|
defer configCleanup()
|
2017-01-19 05:47:56 +01:00
|
|
|
op.PlanOutPath = planPath
|
2018-10-09 21:19:24 +02:00
|
|
|
cfg := cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"path": cty.StringVal(b.StatePath),
|
|
|
|
})
|
|
|
|
cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
op.PlanOutBackend = &plans.Backend{
|
|
|
|
// Just a placeholder so that we can generate a valid plan file.
|
2018-10-10 01:32:09 +02:00
|
|
|
Type: "local",
|
2018-10-09 21:19:24 +02:00
|
|
|
Config: cfgRaw,
|
|
|
|
}
|
2017-01-19 05:47:56 +01:00
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
2018-03-21 02:43:02 +01:00
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
plan := testReadPlan(t, planPath)
|
2018-10-09 00:22:59 +02:00
|
|
|
|
|
|
|
if !plan.Changes.Empty() {
|
|
|
|
t.Fatalf("expected empty plan to be written")
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-24 16:54:40 +02:00
|
|
|
// TestLocal_planScaleOutNoDupeCount tests a Refresh/Plan sequence when a
|
|
|
|
// resource count is scaled out. The scaled out node needs to exist in the
|
|
|
|
// graph and run through a plan-style sequence during the refresh phase, but
|
|
|
|
// can conflate the count if its post-diff count hooks are not skipped. This
|
|
|
|
// checks to make sure the correct resource count is ultimately given to the
|
|
|
|
// UI.
|
|
|
|
func TestLocal_planScaleOutNoDupeCount(t *testing.T) {
|
2018-03-28 16:54:08 +02:00
|
|
|
b, cleanup := TestLocal(t)
|
|
|
|
defer cleanup()
|
2018-05-23 04:57:04 +02:00
|
|
|
TestLocalProvider(t, b, "test", planFixtureSchema())
|
2018-10-09 00:22:59 +02:00
|
|
|
testStateFile(t, b.StatePath, testPlanState())
|
2017-06-24 16:54:40 +02:00
|
|
|
|
|
|
|
actual := new(CountHook)
|
|
|
|
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, actual)
|
|
|
|
|
|
|
|
outDir := testTempDir(t)
|
|
|
|
defer os.RemoveAll(outDir)
|
|
|
|
|
2019-06-30 09:38:36 +02:00
|
|
|
op, configCleanup := testOperationPlan(t, "./testdata/plan-scaleout")
|
2018-03-21 02:43:02 +01:00
|
|
|
defer configCleanup()
|
2017-06-24 16:54:40 +02:00
|
|
|
op.PlanRefresh = true
|
|
|
|
|
|
|
|
run, err := b.Operation(context.Background(), op)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
|
|
|
<-run.Done()
|
2018-03-21 02:43:02 +01:00
|
|
|
if run.Result != backend.OperationSuccess {
|
|
|
|
t.Fatalf("plan operation failed")
|
2017-06-24 16:54:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
expected := new(CountHook)
|
|
|
|
expected.ToAdd = 1
|
|
|
|
expected.ToChange = 0
|
|
|
|
expected.ToRemoveAndAdd = 0
|
|
|
|
expected.ToRemove = 0
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
|
|
t.Fatalf("Expected %#v, got %#v instead.",
|
|
|
|
expected, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-21 02:43:02 +01:00
|
|
|
func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func()) {
|
|
|
|
t.Helper()
|
|
|
|
|
2019-01-09 03:39:14 +01:00
|
|
|
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
2018-03-21 02:43:02 +01:00
|
|
|
|
2017-01-19 05:47:56 +01:00
|
|
|
return &backend.Operation{
|
2018-03-21 02:43:02 +01:00
|
|
|
Type: backend.OperationTypePlan,
|
|
|
|
ConfigDir: configDir,
|
|
|
|
ConfigLoader: configLoader,
|
|
|
|
}, configCleanup
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
|
2018-10-09 00:22:59 +02:00
|
|
|
// testPlanState is just a common state that we use for testing plan.
|
|
|
|
func testPlanState() *states.State {
|
|
|
|
state := states.NewState()
|
|
|
|
rootModule := state.RootModule()
|
|
|
|
rootModule.SetResourceInstanceCurrent(
|
|
|
|
addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "test_instance",
|
|
|
|
Name: "foo",
|
|
|
|
}.Instance(addrs.IntKey(0)),
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
2018-11-30 19:56:50 +01:00
|
|
|
Status: states.ObjectReady,
|
2018-10-09 00:22:59 +02:00
|
|
|
AttrsJSON: []byte(`{
|
|
|
|
"ami": "bar",
|
|
|
|
"network_interface": [{
|
|
|
|
"device_index": 0,
|
|
|
|
"description": "Main network interface"
|
|
|
|
}]
|
|
|
|
}`),
|
2017-01-19 05:47:56 +01:00
|
|
|
},
|
Initial steps towards AbsProviderConfig/LocalProviderConfig separation (#23978)
* Introduce "Local" terminology for non-absolute provider config addresses
In a future change AbsProviderConfig and LocalProviderConfig are going to
become two entirely distinct types, rather than Abs embedding Local as
written here. This naming change is in preparation for that subsequent
work, which will also include introducing a new "ProviderConfig" type
that is an interface that AbsProviderConfig and LocalProviderConfig both
implement.
This is intended to be largely just a naming change to get started, so
we can deal with all of the messy renaming. However, this did also require
a slight change in modeling where the Resource.DefaultProviderConfig
method has become Resource.DefaultProvider returning a Provider address
directly, because this method doesn't have enough information to construct
a true and accurate LocalProviderConfig -- it would need to refer to the
configuration to know what this module is calling the provider it has
selected.
In order to leave a trail to follow for subsequent work, all of the
changes here are intended to ensure that remaining work will become
obvious via compile-time errors when all of the following changes happen:
- The concept of "legacy" provider addresses is removed from the addrs
package, including removing addrs.NewLegacyProvider and
addrs.Provider.LegacyString.
- addrs.AbsProviderConfig stops having addrs.LocalProviderConfig embedded
in it and has an addrs.Provider and a string alias directly instead.
- The provider-schema-handling parts of Terraform core are updated to
work with addrs.Provider to identify providers, rather than legacy
strings.
In particular, there are still several codepaths here making legacy
provider address assumptions (in order to limit the scope of this change)
but I've made sure each one is doing something that relies on at least
one of the above changes not having been made yet.
* addrs: ProviderConfig interface
In a (very) few special situations in the main "terraform" package we need
to make runtime decisions about whether a provider config is absolute
or local.
We currently do that by exploiting the fact that AbsProviderConfig has
LocalProviderConfig nested inside of it and so in the local case we can
just ignore the wrapping AbsProviderConfig and use the embedded value.
In a future change we'll be moving away from that embedding and making
these two types distinct in order to represent that mapping between them
requires consulting a lookup table in the configuration, and so here we
introduce a new interface type ProviderConfig that can represent either
AbsProviderConfig or LocalProviderConfig decided dynamically at runtime.
This also includes the Config.ResolveAbsProviderAddr method that will
eventually be responsible for that local-to-absolute translation, so
that callers with access to the configuration can normalize to an
addrs.AbsProviderConfig given a non-nil addrs.ProviderConfig. That's
currently unused because existing callers are still relying on the
simplistic structural transform, but we'll switch them over in a later
commit.
* rename LocalType to LocalName
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2020-01-31 14:23:07 +01:00
|
|
|
addrs.LocalProviderConfig{
|
|
|
|
LocalName: "test",
|
2018-10-09 00:22:59 +02:00
|
|
|
}.Absolute(addrs.RootModuleInstance),
|
|
|
|
)
|
|
|
|
return state
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
|
2018-12-12 17:01:18 +01:00
|
|
|
func testPlanState_withDataSource() *states.State {
|
|
|
|
state := states.NewState()
|
|
|
|
rootModule := state.RootModule()
|
|
|
|
rootModule.SetResourceInstanceCurrent(
|
|
|
|
addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "test_instance",
|
|
|
|
Name: "foo",
|
|
|
|
}.Instance(addrs.IntKey(0)),
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{
|
|
|
|
"ami": "bar",
|
|
|
|
"network_interface": [{
|
|
|
|
"device_index": 0,
|
|
|
|
"description": "Main network interface"
|
|
|
|
}]
|
|
|
|
}`),
|
|
|
|
},
|
Initial steps towards AbsProviderConfig/LocalProviderConfig separation (#23978)
* Introduce "Local" terminology for non-absolute provider config addresses
In a future change AbsProviderConfig and LocalProviderConfig are going to
become two entirely distinct types, rather than Abs embedding Local as
written here. This naming change is in preparation for that subsequent
work, which will also include introducing a new "ProviderConfig" type
that is an interface that AbsProviderConfig and LocalProviderConfig both
implement.
This is intended to be largely just a naming change to get started, so
we can deal with all of the messy renaming. However, this did also require
a slight change in modeling where the Resource.DefaultProviderConfig
method has become Resource.DefaultProvider returning a Provider address
directly, because this method doesn't have enough information to construct
a true and accurate LocalProviderConfig -- it would need to refer to the
configuration to know what this module is calling the provider it has
selected.
In order to leave a trail to follow for subsequent work, all of the
changes here are intended to ensure that remaining work will become
obvious via compile-time errors when all of the following changes happen:
- The concept of "legacy" provider addresses is removed from the addrs
package, including removing addrs.NewLegacyProvider and
addrs.Provider.LegacyString.
- addrs.AbsProviderConfig stops having addrs.LocalProviderConfig embedded
in it and has an addrs.Provider and a string alias directly instead.
- The provider-schema-handling parts of Terraform core are updated to
work with addrs.Provider to identify providers, rather than legacy
strings.
In particular, there are still several codepaths here making legacy
provider address assumptions (in order to limit the scope of this change)
but I've made sure each one is doing something that relies on at least
one of the above changes not having been made yet.
* addrs: ProviderConfig interface
In a (very) few special situations in the main "terraform" package we need
to make runtime decisions about whether a provider config is absolute
or local.
We currently do that by exploiting the fact that AbsProviderConfig has
LocalProviderConfig nested inside of it and so in the local case we can
just ignore the wrapping AbsProviderConfig and use the embedded value.
In a future change we'll be moving away from that embedding and making
these two types distinct in order to represent that mapping between them
requires consulting a lookup table in the configuration, and so here we
introduce a new interface type ProviderConfig that can represent either
AbsProviderConfig or LocalProviderConfig decided dynamically at runtime.
This also includes the Config.ResolveAbsProviderAddr method that will
eventually be responsible for that local-to-absolute translation, so
that callers with access to the configuration can normalize to an
addrs.AbsProviderConfig given a non-nil addrs.ProviderConfig. That's
currently unused because existing callers are still relying on the
simplistic structural transform, but we'll switch them over in a later
commit.
* rename LocalType to LocalName
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2020-01-31 14:23:07 +01:00
|
|
|
addrs.LocalProviderConfig{
|
|
|
|
LocalName: "test",
|
2018-12-12 17:01:18 +01:00
|
|
|
}.Absolute(addrs.RootModuleInstance),
|
|
|
|
)
|
|
|
|
rootModule.SetResourceInstanceCurrent(
|
|
|
|
addrs.Resource{
|
|
|
|
Mode: addrs.DataResourceMode,
|
|
|
|
Type: "test_ds",
|
|
|
|
Name: "bar",
|
|
|
|
}.Instance(addrs.IntKey(0)),
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{
|
|
|
|
"filter": "foo"
|
|
|
|
}`),
|
|
|
|
},
|
Initial steps towards AbsProviderConfig/LocalProviderConfig separation (#23978)
* Introduce "Local" terminology for non-absolute provider config addresses
In a future change AbsProviderConfig and LocalProviderConfig are going to
become two entirely distinct types, rather than Abs embedding Local as
written here. This naming change is in preparation for that subsequent
work, which will also include introducing a new "ProviderConfig" type
that is an interface that AbsProviderConfig and LocalProviderConfig both
implement.
This is intended to be largely just a naming change to get started, so
we can deal with all of the messy renaming. However, this did also require
a slight change in modeling where the Resource.DefaultProviderConfig
method has become Resource.DefaultProvider returning a Provider address
directly, because this method doesn't have enough information to construct
a true and accurate LocalProviderConfig -- it would need to refer to the
configuration to know what this module is calling the provider it has
selected.
In order to leave a trail to follow for subsequent work, all of the
changes here are intended to ensure that remaining work will become
obvious via compile-time errors when all of the following changes happen:
- The concept of "legacy" provider addresses is removed from the addrs
package, including removing addrs.NewLegacyProvider and
addrs.Provider.LegacyString.
- addrs.AbsProviderConfig stops having addrs.LocalProviderConfig embedded
in it and has an addrs.Provider and a string alias directly instead.
- The provider-schema-handling parts of Terraform core are updated to
work with addrs.Provider to identify providers, rather than legacy
strings.
In particular, there are still several codepaths here making legacy
provider address assumptions (in order to limit the scope of this change)
but I've made sure each one is doing something that relies on at least
one of the above changes not having been made yet.
* addrs: ProviderConfig interface
In a (very) few special situations in the main "terraform" package we need
to make runtime decisions about whether a provider config is absolute
or local.
We currently do that by exploiting the fact that AbsProviderConfig has
LocalProviderConfig nested inside of it and so in the local case we can
just ignore the wrapping AbsProviderConfig and use the embedded value.
In a future change we'll be moving away from that embedding and making
these two types distinct in order to represent that mapping between them
requires consulting a lookup table in the configuration, and so here we
introduce a new interface type ProviderConfig that can represent either
AbsProviderConfig or LocalProviderConfig decided dynamically at runtime.
This also includes the Config.ResolveAbsProviderAddr method that will
eventually be responsible for that local-to-absolute translation, so
that callers with access to the configuration can normalize to an
addrs.AbsProviderConfig given a non-nil addrs.ProviderConfig. That's
currently unused because existing callers are still relying on the
simplistic structural transform, but we'll switch them over in a later
commit.
* rename LocalType to LocalName
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2020-01-31 14:23:07 +01:00
|
|
|
addrs.LocalProviderConfig{
|
|
|
|
LocalName: "test",
|
2018-12-12 17:01:18 +01:00
|
|
|
}.Absolute(addrs.RootModuleInstance),
|
|
|
|
)
|
|
|
|
return state
|
|
|
|
}
|
|
|
|
|
2018-12-14 14:45:47 +01:00
|
|
|
func testPlanState_tainted() *states.State {
|
|
|
|
state := states.NewState()
|
|
|
|
rootModule := state.RootModule()
|
|
|
|
rootModule.SetResourceInstanceCurrent(
|
|
|
|
addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "test_instance",
|
|
|
|
Name: "foo",
|
|
|
|
}.Instance(addrs.IntKey(0)),
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectTainted,
|
|
|
|
AttrsJSON: []byte(`{
|
|
|
|
"ami": "bar",
|
|
|
|
"network_interface": [{
|
|
|
|
"device_index": 0,
|
|
|
|
"description": "Main network interface"
|
|
|
|
}]
|
|
|
|
}`),
|
|
|
|
},
|
Initial steps towards AbsProviderConfig/LocalProviderConfig separation (#23978)
* Introduce "Local" terminology for non-absolute provider config addresses
In a future change AbsProviderConfig and LocalProviderConfig are going to
become two entirely distinct types, rather than Abs embedding Local as
written here. This naming change is in preparation for that subsequent
work, which will also include introducing a new "ProviderConfig" type
that is an interface that AbsProviderConfig and LocalProviderConfig both
implement.
This is intended to be largely just a naming change to get started, so
we can deal with all of the messy renaming. However, this did also require
a slight change in modeling where the Resource.DefaultProviderConfig
method has become Resource.DefaultProvider returning a Provider address
directly, because this method doesn't have enough information to construct
a true and accurate LocalProviderConfig -- it would need to refer to the
configuration to know what this module is calling the provider it has
selected.
In order to leave a trail to follow for subsequent work, all of the
changes here are intended to ensure that remaining work will become
obvious via compile-time errors when all of the following changes happen:
- The concept of "legacy" provider addresses is removed from the addrs
package, including removing addrs.NewLegacyProvider and
addrs.Provider.LegacyString.
- addrs.AbsProviderConfig stops having addrs.LocalProviderConfig embedded
in it and has an addrs.Provider and a string alias directly instead.
- The provider-schema-handling parts of Terraform core are updated to
work with addrs.Provider to identify providers, rather than legacy
strings.
In particular, there are still several codepaths here making legacy
provider address assumptions (in order to limit the scope of this change)
but I've made sure each one is doing something that relies on at least
one of the above changes not having been made yet.
* addrs: ProviderConfig interface
In a (very) few special situations in the main "terraform" package we need
to make runtime decisions about whether a provider config is absolute
or local.
We currently do that by exploiting the fact that AbsProviderConfig has
LocalProviderConfig nested inside of it and so in the local case we can
just ignore the wrapping AbsProviderConfig and use the embedded value.
In a future change we'll be moving away from that embedding and making
these two types distinct in order to represent that mapping between them
requires consulting a lookup table in the configuration, and so here we
introduce a new interface type ProviderConfig that can represent either
AbsProviderConfig or LocalProviderConfig decided dynamically at runtime.
This also includes the Config.ResolveAbsProviderAddr method that will
eventually be responsible for that local-to-absolute translation, so
that callers with access to the configuration can normalize to an
addrs.AbsProviderConfig given a non-nil addrs.ProviderConfig. That's
currently unused because existing callers are still relying on the
simplistic structural transform, but we'll switch them over in a later
commit.
* rename LocalType to LocalName
Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2020-01-31 14:23:07 +01:00
|
|
|
addrs.LocalProviderConfig{
|
|
|
|
LocalName: "test",
|
2018-12-14 14:45:47 +01:00
|
|
|
}.Absolute(addrs.RootModuleInstance),
|
|
|
|
)
|
|
|
|
return state
|
|
|
|
}
|
|
|
|
|
2018-09-29 00:57:27 +02:00
|
|
|
func testReadPlan(t *testing.T, path string) *plans.Plan {
|
2018-10-09 21:19:24 +02:00
|
|
|
t.Helper()
|
|
|
|
|
2018-09-29 00:57:27 +02:00
|
|
|
p, err := planfile.Open(path)
|
2017-01-19 05:47:56 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
2018-09-29 00:57:27 +02:00
|
|
|
defer p.Close()
|
2017-01-19 05:47:56 +01:00
|
|
|
|
2018-09-29 00:57:27 +02:00
|
|
|
plan, err := p.ReadPlan()
|
2018-10-04 23:37:14 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
2017-01-19 05:47:56 +01:00
|
|
|
|
2018-09-29 00:57:27 +02:00
|
|
|
return plan
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
2018-05-23 04:57:04 +02:00
|
|
|
|
|
|
|
// planFixtureSchema returns a schema suitable for processing the
|
2019-06-30 09:38:36 +02:00
|
|
|
// configuration in testdata/plan . This schema should be
|
2018-05-23 04:57:04 +02:00
|
|
|
// assigned to a mock provider named "test".
|
|
|
|
func planFixtureSchema() *terraform.ProviderSchema {
|
|
|
|
return &terraform.ProviderSchema{
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"ami": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"network_interface": {
|
|
|
|
Nesting: configschema.NestingList,
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"device_index": {Type: cty.Number, Optional: true},
|
|
|
|
"description": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-12-12 17:01:18 +01:00
|
|
|
DataSources: map[string]*configschema.Block{
|
|
|
|
"test_ds": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"filter": {Type: cty.String, Required: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-05-23 04:57:04 +02:00
|
|
|
}
|
|
|
|
}
|