2015-07-10 22:08:49 +02:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2019-06-01 03:09:20 +02:00
|
|
|
"errors"
|
2015-07-10 22:08:49 +02:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
2018-05-05 04:24:06 +02:00
|
|
|
|
2018-05-23 02:53:56 +02:00
|
|
|
"github.com/davecgh/go-spew/spew"
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
"github.com/google/go-cmp/cmp"
|
2018-05-05 04:24:06 +02:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2021-05-17 21:00:50 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
2021-05-17 21:17:09 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
|
|
"github.com/hashicorp/terraform/internal/configs/hcl2shim"
|
2021-06-24 23:53:43 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/lang/marks"
|
2021-05-17 21:33:17 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/plans"
|
2021-05-17 19:40:40 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/providers"
|
2021-05-17 19:51:48 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/provisioners"
|
2021-05-17 21:43:35 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/states"
|
2021-05-17 19:11:06 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
2015-07-10 22:08:49 +02:00
|
|
|
)
|
|
|
|
|
2016-11-06 08:00:05 +01:00
|
|
|
func TestContext2Plan_basic(t *testing.T) {
|
2015-07-10 22:08:49 +02:00
|
|
|
m := testModule(t, "plan-good")
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if l := len(plan.Changes.Resources); l < 2 {
|
|
|
|
t.Fatalf("wrong number of resources %d; want fewer than two\n%s", l, spew.Sdump(plan.Changes.Resources))
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
for _, r := range plan.Changes.Resources {
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := r.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.bar":
|
2018-09-19 00:44:51 +02:00
|
|
|
foo := ric.After.GetAttr("foo").AsString()
|
2018-09-15 00:40:09 +02:00
|
|
|
if foo != "2" {
|
2018-09-19 00:44:51 +02:00
|
|
|
t.Fatalf("incorrect plan for 'bar': %#v", ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
}
|
|
|
|
case "aws_instance.foo":
|
2018-09-19 00:44:51 +02:00
|
|
|
num, _ := ric.After.GetAttr("num").AsBigFloat().Int64()
|
2018-09-18 01:25:11 +02:00
|
|
|
if num != 2 {
|
2018-09-19 00:44:51 +02:00
|
|
|
t.Fatalf("incorrect plan for 'foo': %#v", ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2020-11-03 00:23:00 +01:00
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
if !p.ValidateProviderConfigCalled {
|
2020-11-03 00:23:00 +01:00
|
|
|
t.Fatal("provider config was not checked before Configure")
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2016-11-28 22:35:29 +01:00
|
|
|
func TestContext2Plan_createBefore_deposed(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-cbd")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2016-11-28 22:35:29 +01:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`),
|
2016-11-28 22:35:29 +01:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceDeposed(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
states.DeposedKey("00000001"),
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"foo"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
2016-11-28 22:35:29 +01:00
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-11-28 22:35:29 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-11-28 22:35:29 +01:00
|
|
|
}
|
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
// the state should still show one deposed
|
|
|
|
expectedState := strings.TrimSpace(`
|
|
|
|
aws_instance.foo: (1 deposed)
|
|
|
|
ID = baz
|
2020-03-31 20:03:33 +02:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
2020-10-08 14:56:03 +02:00
|
|
|
type = aws_instance
|
2018-09-18 01:25:11 +02:00
|
|
|
Deposed ID 1 = foo`)
|
2016-11-28 22:35:29 +01:00
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
if plan.PriorState.String() != expectedState {
|
|
|
|
t.Fatalf("\nexpected: %q\ngot: %q\n", expectedState, plan.PriorState.String())
|
2018-09-15 00:40:09 +02:00
|
|
|
}
|
2016-11-28 22:35:29 +01:00
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
2016-11-28 22:35:29 +01:00
|
|
|
|
2018-09-21 01:09:02 +02:00
|
|
|
type InstanceGen struct {
|
2018-09-24 21:14:32 +02:00
|
|
|
Addr string
|
2018-09-21 01:09:02 +02:00
|
|
|
DeposedKey states.DeposedKey
|
2018-09-15 00:40:09 +02:00
|
|
|
}
|
2018-09-21 01:09:02 +02:00
|
|
|
want := map[InstanceGen]bool{
|
|
|
|
{
|
|
|
|
Addr: "aws_instance.foo",
|
|
|
|
}: true,
|
|
|
|
{
|
2018-09-24 21:14:32 +02:00
|
|
|
Addr: "aws_instance.foo",
|
2018-09-21 01:09:02 +02:00
|
|
|
DeposedKey: states.DeposedKey("00000001"),
|
|
|
|
}: true,
|
|
|
|
}
|
|
|
|
got := make(map[InstanceGen]bool)
|
|
|
|
changes := make(map[InstanceGen]*plans.ResourceInstanceChangeSrc)
|
2018-09-15 00:40:09 +02:00
|
|
|
|
2018-09-21 01:09:02 +02:00
|
|
|
for _, change := range plan.Changes.Resources {
|
|
|
|
k := InstanceGen{
|
|
|
|
Addr: change.Addr.String(),
|
|
|
|
DeposedKey: change.DeposedKey,
|
|
|
|
}
|
|
|
|
got[k] = true
|
|
|
|
changes[k] = change
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
|
|
t.Fatalf("wrong resource instance object changes in plan\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(want))
|
2018-09-15 00:40:09 +02:00
|
|
|
}
|
|
|
|
|
2018-09-21 01:09:02 +02:00
|
|
|
{
|
2018-09-24 21:14:32 +02:00
|
|
|
ric, err := changes[InstanceGen{Addr: "aws_instance.foo"}].Decode(ty)
|
2018-09-21 01:09:02 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if got, want := ric.Action, plans.NoOp; got != want {
|
|
|
|
t.Errorf("current object change action is %s; want %s", got, want)
|
|
|
|
}
|
|
|
|
|
|
|
|
// the existing instance should only have an unchanged id
|
2020-10-08 14:56:03 +02:00
|
|
|
expected, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("baz"),
|
|
|
|
"type": cty.StringVal("aws_instance"),
|
|
|
|
}))
|
2018-09-21 01:09:02 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
checkVals(t, expected, ric.After)
|
2016-11-28 22:35:29 +01:00
|
|
|
}
|
2018-09-15 00:40:09 +02:00
|
|
|
|
2018-09-21 01:09:02 +02:00
|
|
|
{
|
2018-09-24 21:14:32 +02:00
|
|
|
ric, err := changes[InstanceGen{Addr: "aws_instance.foo", DeposedKey: states.DeposedKey("00000001")}].Decode(ty)
|
2018-09-21 01:09:02 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if got, want := ric.Action, plans.Delete; got != want {
|
|
|
|
t.Errorf("deposed object change action is %s; want %s", got, want)
|
|
|
|
}
|
|
|
|
}
|
2016-11-28 22:35:29 +01:00
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_createBefore_maintainRoot(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-cbd-maintain-root")
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
if !plan.PriorState.Empty() {
|
|
|
|
t.Fatal("expected empty prior state, got:", plan.PriorState)
|
2018-09-15 00:40:09 +02:00
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
|
|
t.Error("expected 4 resource in plan, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
// these should all be creates
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("unexpected action %s for %s", res.Action, res.Addr.String())
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_emptyDiff(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-empty")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
2020-10-08 14:56:03 +02:00
|
|
|
resp.PlannedState = req.ProposedNewState
|
|
|
|
return resp
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
if !plan.PriorState.Empty() {
|
|
|
|
t.Fatal("expected empty state, got:", plan.PriorState)
|
2018-09-15 00:40:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Error("expected 2 resource in plan, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
actions := map[string]plans.Action{}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
actions[res.Addr.String()] = res.Action
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := map[string]plans.Action{
|
|
|
|
"aws_instance.foo": plans.Create,
|
|
|
|
"aws_instance.bar": plans.Create,
|
|
|
|
}
|
|
|
|
if !cmp.Equal(expected, actions) {
|
|
|
|
t.Fatal(cmp.Diff(expected, actions))
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-19 21:37:55 +01:00
|
|
|
func TestContext2Plan_escapedVar(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-escaped-var")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2016-01-19 21:37:55 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-01-19 21:37:55 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-01-19 21:37:55 +01:00
|
|
|
}
|
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Error("expected 1 resource in plan, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
res := plan.Changes.Resources[0]
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2016-01-19 21:37:55 +01:00
|
|
|
}
|
2018-09-15 00:40:09 +02:00
|
|
|
|
|
|
|
expected := objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("bar-${baz}"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
|
|
|
})
|
2018-09-15 00:40:09 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
checkVals(t, expected, ric.After)
|
2016-01-19 21:37:55 +01:00
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_minimal(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-empty")
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
if !plan.PriorState.Empty() {
|
|
|
|
t.Fatal("expected empty state, got:", plan.PriorState)
|
2018-09-15 00:40:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Error("expected 2 resource in plan, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
actions := map[string]plans.Action{}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
actions[res.Addr.String()] = res.Action
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := map[string]plans.Action{
|
|
|
|
"aws_instance.foo": plans.Create,
|
|
|
|
"aws_instance.bar": plans.Create,
|
|
|
|
}
|
|
|
|
if !cmp.Equal(expected, actions) {
|
|
|
|
t.Fatal(cmp.Diff(expected, actions))
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_modules(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-modules")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
|
|
t.Error("expected 3 resource in plan, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
expectFoo := objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("2"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
|
|
|
})
|
2018-09-15 00:40:09 +02:00
|
|
|
|
|
|
|
expectNum := objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"num": cty.NumberIntVal(2),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
|
|
|
})
|
2018-09-15 00:40:09 +02:00
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var expected cty.Value
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.bar":
|
|
|
|
expected = expectFoo
|
|
|
|
case "aws_instance.foo":
|
|
|
|
expected = expectNum
|
|
|
|
case "module.child.aws_instance.foo":
|
|
|
|
expected = expectNum
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
checkVals(t, expected, ric.After)
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
2020-03-25 20:29:34 +01:00
|
|
|
func TestContext2Plan_moduleExpand(t *testing.T) {
|
|
|
|
// Test a smattering of plan expansion behavior
|
|
|
|
m := testModule(t, "plan-modules-expand")
|
2020-02-24 23:42:32 +01:00
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2020-02-24 23:42:32 +01:00
|
|
|
})
|
|
|
|
|
2021-12-21 01:38:52 +01:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
2020-02-24 23:42:32 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2020-02-24 23:42:32 +01:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
2020-03-25 20:29:34 +01:00
|
|
|
expected := map[string]struct{}{
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
`aws_instance.foo["a"]`: {},
|
|
|
|
`module.count_child[1].aws_instance.foo[0]`: {},
|
|
|
|
`module.count_child[1].aws_instance.foo[1]`: {},
|
|
|
|
`module.count_child[0].aws_instance.foo[0]`: {},
|
|
|
|
`module.count_child[0].aws_instance.foo[1]`: {},
|
|
|
|
`module.for_each_child["a"].aws_instance.foo[1]`: {},
|
|
|
|
`module.for_each_child["a"].aws_instance.foo[0]`: {},
|
2020-03-25 20:29:34 +01:00
|
|
|
}
|
2020-02-24 23:42:32 +01:00
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2020-03-25 20:29:34 +01:00
|
|
|
_, ok := expected[ric.Addr.String()]
|
|
|
|
if !ok {
|
|
|
|
t.Fatal("unexpected resource:", ric.Addr.String())
|
2020-02-24 23:42:32 +01:00
|
|
|
}
|
2020-03-25 20:29:34 +01:00
|
|
|
delete(expected, ric.Addr.String())
|
|
|
|
}
|
|
|
|
for addr := range expected {
|
|
|
|
t.Error("missing resource", addr)
|
2020-02-24 23:42:32 +01:00
|
|
|
}
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
|
|
|
// GH-1475
|
|
|
|
func TestContext2Plan_moduleCycle(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-cycle")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-05-23 18:59:03 +02:00
|
|
|
"id": {Type: cty.String, Computed: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
"some_input": {Type: cty.String, Optional: true},
|
2019-02-12 02:05:24 +01:00
|
|
|
"type": {Type: cty.String, Computed: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var expected cty.Value
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.b":
|
|
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-15 00:40:09 +02:00
|
|
|
})
|
|
|
|
case "aws_instance.c":
|
|
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"some_input": cty.UnknownVal(cty.String),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-15 00:40:09 +02:00
|
|
|
})
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
checkVals(t, expected, ric.After)
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_moduleDeadlock(t *testing.T) {
|
2015-08-10 22:03:02 +02:00
|
|
|
testCheckDeadlock(t, func() {
|
|
|
|
m := testModule(t, "plan-module-deadlock")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-08-10 22:03:02 +02:00
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2015-07-10 22:08:49 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
expected := objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-15 00:40:09 +02:00
|
|
|
})
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "module.child.aws_instance.foo[0]":
|
|
|
|
case "module.child.aws_instance.foo[1]":
|
|
|
|
case "module.child.aws_instance.foo[2]":
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
checkVals(t, expected, ric.After)
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2015-08-10 22:03:02 +02:00
|
|
|
})
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_moduleInput(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-input")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var expected cty.Value
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.bar":
|
|
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("2"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-15 00:40:09 +02:00
|
|
|
})
|
|
|
|
case "module.child.aws_instance.foo":
|
|
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("42"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-15 00:40:09 +02:00
|
|
|
})
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
checkVals(t, expected, ric.After)
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_moduleInputComputed(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-input-computed")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.bar":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2019-02-12 02:05:24 +01:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.UnknownVal(cty.String),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2019-02-12 02:05:24 +01:00
|
|
|
"compute": cty.StringVal("foo"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
case "module.child.aws_instance.foo":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.UnknownVal(cty.String),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_moduleInputFromVar(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-input-var")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
SetVariables: InputValues{
|
2018-05-05 04:24:06 +02:00
|
|
|
"foo": &InputValue{
|
|
|
|
Value: cty.StringVal("52"),
|
|
|
|
SourceType: ValueFromCaller,
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.bar":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("2"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
case "module.child.aws_instance.foo":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("52"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_moduleMultiVar(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-multi-var")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Computed: true},
|
|
|
|
"foo": {Type: cty.String, Optional: true},
|
|
|
|
"baz": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 5 {
|
|
|
|
t.Fatal("expected 5 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.parent[0]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.parent[1]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
case "module.child.aws_instance.bar[0]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"baz": cty.StringVal("baz"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
case "module.child.aws_instance.bar[1]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"baz": cty.StringVal("baz"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
case "module.child.aws_instance.foo":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("baz,baz"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_moduleOrphans(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-modules-remove")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
|
|
child.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"baz"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.foo":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"num": cty.NumberIntVal(2),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
case "module.child.aws_instance.foo":
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("expected resource delete, got %s", res.Action)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedState := `<no state>
|
|
|
|
module.child:
|
|
|
|
aws_instance.foo:
|
|
|
|
ID = baz
|
2020-03-31 20:03:33 +02:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]`
|
2018-09-15 00:40:09 +02:00
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
if plan.PriorState.String() != expectedState {
|
|
|
|
t.Fatalf("\nexpected state: %q\n\ngot: %q", expectedState, plan.PriorState.String())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
// https://github.com/hashicorp/terraform/issues/3114
|
|
|
|
func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-modules-remove-provisioners")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2021-01-12 18:39:25 +01:00
|
|
|
pr := testProvisioner()
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.top").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"top","type":"aws_instance"}`),
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
child1 := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child1", addrs.NoKey))
|
|
|
|
child1.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`),
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
child2 := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey).Child("child2", addrs.NoKey))
|
|
|
|
child2.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"baz","type":"aws_instance"}`),
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2020-11-13 18:43:28 +01:00
|
|
|
Provisioners: map[string]provisioners.Factory{
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
"shell": testProvisionerFuncFixed(pr),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
|
|
t.Error("expected 3 planned resources, got", len(plan.Changes.Resources))
|
|
|
|
}
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2020-03-31 20:03:33 +02:00
|
|
|
case "module.parent.module.child1.aws_instance.foo":
|
2018-09-15 00:40:09 +02:00
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("expected resource Delete, got %s", res.Action)
|
|
|
|
}
|
2020-03-31 20:03:33 +02:00
|
|
|
case "module.parent.module.child2.aws_instance.foo":
|
2018-09-15 00:40:09 +02:00
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("expected resource Delete, got %s", res.Action)
|
|
|
|
}
|
|
|
|
case "aws_instance.top":
|
|
|
|
if res.Action != plans.NoOp {
|
2020-03-31 20:03:33 +02:00
|
|
|
t.Fatalf("expected no changes, got %s", res.Action)
|
2018-09-15 00:40:09 +02:00
|
|
|
}
|
|
|
|
default:
|
2018-09-19 00:44:51 +02:00
|
|
|
t.Fatalf("unknown instance: %s\nafter: %#v", i, hcl2shim.ConfigValueFromHCL2(ric.After))
|
2018-09-15 00:40:09 +02:00
|
|
|
}
|
|
|
|
}
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
expectedState := `aws_instance.top:
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
ID = top
|
2020-03-31 20:03:33 +02:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
2020-10-08 14:56:03 +02:00
|
|
|
type = aws_instance
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
module.parent.child1:
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
aws_instance.foo:
|
|
|
|
ID = baz
|
2020-03-31 20:03:33 +02:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
2020-10-08 14:56:03 +02:00
|
|
|
type = aws_instance
|
2020-03-31 20:03:33 +02:00
|
|
|
module.parent.child2:
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
aws_instance.foo:
|
|
|
|
ID = baz
|
2020-10-08 14:56:03 +02:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
|
|
|
type = aws_instance`
|
2018-09-15 00:40:09 +02:00
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
if expectedState != plan.PriorState.String() {
|
|
|
|
t.Fatalf("\nexpect state:\n%s\n\ngot state:\n%s\n", expectedState, plan.PriorState.String())
|
core: fix bug detecting deeply nested module orphans
Context:
As part of building up a Plan, Terraform needs to detect "orphaned"
resources--resources which are present in the state but not in the
config. This happens when config for those resources is removed by the
user, making it Terraform's responsibility to destroy them.
Both state and config are organized by Module into a logical tree, so
the process of finding orphans involves checking for orphaned Resources
in the current module and for orphaned Modules, which themselves will
have all their Resources marked as orphans.
Bug:
In #3114 a problem was exposed where, given a module tree that looked
like this:
```
root
|
+-- parent (empty, except for sub-modules)
|
+-- child1 (1 resource)
|
+-- child2 (1 resource)
```
If `parent` was removed, a bunch of error messages would occur during
the plan. The root cause of this was duplicate orphans appearing for the
resources in child1 and child2.
Fix:
This turned out to be a bug in orphaned module detection. When looking
for deeply nested orphaned modules, root.parent was getting added twice
as an orphaned module to the graph.
Here, we add an additional check to prevent a double add, which
addresses this scenario properly.
Fixes #3114 (the Provisioner side of it was fixed in #4877)
2016-02-05 19:19:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_moduleProviderInherit(t *testing.T) {
|
|
|
|
var l sync.Mutex
|
|
|
|
var calls []string
|
|
|
|
|
|
|
|
m := testModule(t, "plan-module-provider-inherit")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) {
|
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2020-03-31 20:03:33 +02:00
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"from": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
2018-05-23 02:53:56 +02:00
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"from": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2021-02-18 16:13:43 +01:00
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
2020-10-08 18:26:12 +02:00
|
|
|
from := req.Config.GetAttr("from")
|
|
|
|
if from.IsNull() || from.AsString() != "root" {
|
|
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("not root"))
|
2018-05-23 02:53:56 +02:00
|
|
|
}
|
2017-04-22 02:40:46 +02:00
|
|
|
|
2020-10-08 18:26:12 +02:00
|
|
|
return
|
2020-03-31 20:03:33 +02:00
|
|
|
}
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
2020-10-08 14:56:03 +02:00
|
|
|
from := req.Config.GetAttr("from").AsString()
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
2020-10-08 14:56:03 +02:00
|
|
|
calls = append(calls, from)
|
|
|
|
return testDiffFn(req)
|
2020-03-31 20:03:33 +02:00
|
|
|
}
|
|
|
|
return p, nil
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2015-07-10 22:08:49 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual := calls
|
|
|
|
sort.Strings(actual)
|
|
|
|
expected := []string{"child", "root"}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
|
|
t.Fatalf("bad: %#v", actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-24 21:56:13 +01:00
|
|
|
// This tests (for GH-11282) that deeply nested modules properly inherit
|
|
|
|
// configuration.
|
|
|
|
func TestContext2Plan_moduleProviderInheritDeep(t *testing.T) {
|
|
|
|
var l sync.Mutex
|
|
|
|
|
|
|
|
m := testModule(t, "plan-module-provider-inherit-deep")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) {
|
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
var from string
|
|
|
|
p := testProvider("aws")
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2020-03-31 20:03:33 +02:00
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"from": {Type: cty.String, Optional: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2017-04-22 02:40:46 +02:00
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
2020-10-08 18:26:12 +02:00
|
|
|
v := req.Config.GetAttr("from")
|
|
|
|
if v.IsNull() || v.AsString() != "root" {
|
|
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("not root"))
|
2017-01-24 21:56:13 +01:00
|
|
|
}
|
2020-10-08 18:26:12 +02:00
|
|
|
from = v.AsString()
|
2017-01-24 21:56:13 +01:00
|
|
|
|
2020-10-08 18:26:12 +02:00
|
|
|
return
|
2020-03-31 20:03:33 +02:00
|
|
|
}
|
2017-01-24 21:56:13 +01:00
|
|
|
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
2020-03-31 20:03:33 +02:00
|
|
|
if from != "root" {
|
2020-10-08 14:56:03 +02:00
|
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("bad resource"))
|
|
|
|
return
|
2017-01-24 21:56:13 +01:00
|
|
|
}
|
2020-03-31 20:03:33 +02:00
|
|
|
|
2020-10-08 14:56:03 +02:00
|
|
|
return testDiffFn(req)
|
2020-03-31 20:03:33 +02:00
|
|
|
}
|
|
|
|
return p, nil
|
2017-01-24 21:56:13 +01:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
2017-01-24 21:56:13 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2017-01-24 21:56:13 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_moduleProviderDefaultsVar(t *testing.T) {
|
|
|
|
var l sync.Mutex
|
|
|
|
var calls []string
|
|
|
|
|
|
|
|
m := testModule(t, "plan-module-provider-defaults-var")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): func() (providers.Interface, error) {
|
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2020-03-31 20:03:33 +02:00
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"to": {Type: cty.String, Optional: true},
|
|
|
|
"from": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
2018-05-23 02:53:56 +02:00
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"from": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2021-02-18 16:13:43 +01:00
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
2020-03-31 20:03:33 +02:00
|
|
|
var buf bytes.Buffer
|
2020-10-08 18:26:12 +02:00
|
|
|
from := req.Config.GetAttr("from")
|
|
|
|
if !from.IsNull() {
|
|
|
|
buf.WriteString(from.AsString() + "\n")
|
2018-05-23 02:53:56 +02:00
|
|
|
}
|
2020-10-08 18:26:12 +02:00
|
|
|
to := req.Config.GetAttr("to")
|
|
|
|
if !to.IsNull() {
|
|
|
|
buf.WriteString(to.AsString() + "\n")
|
2017-04-22 02:40:46 +02:00
|
|
|
}
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
calls = append(calls, buf.String())
|
2020-10-08 18:26:12 +02:00
|
|
|
return
|
2020-03-31 20:03:33 +02:00
|
|
|
}
|
2020-10-08 18:26:12 +02:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
return p, nil
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
_, err := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
SetVariables: InputValues{
|
2018-05-05 04:24:06 +02:00
|
|
|
"foo": &InputValue{
|
|
|
|
Value: cty.StringVal("root"),
|
|
|
|
SourceType: ValueFromCaller,
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := []string{
|
2017-10-13 16:11:10 +02:00
|
|
|
"child\nchild\n",
|
2017-11-01 23:34:18 +01:00
|
|
|
"root\n",
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2017-11-01 23:34:18 +01:00
|
|
|
sort.Strings(calls)
|
2015-07-10 22:08:49 +02:00
|
|
|
if !reflect.DeepEqual(calls, expected) {
|
2017-10-13 16:11:10 +02:00
|
|
|
t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, calls)
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-14 06:22:21 +01:00
|
|
|
func TestContext2Plan_moduleProviderVar(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-provider-var")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"value": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"value": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2016-12-14 06:22:21 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-12-14 06:22:21 +01:00
|
|
|
})
|
|
|
|
|
2021-12-21 01:38:52 +01:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-12-14 06:22:21 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "module.child.aws_instance.test":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"value": cty.StringVal("hello"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2016-12-14 06:22:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_moduleVar(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-var")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var expected cty.Value
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.bar":
|
|
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("2"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-15 00:40:09 +02:00
|
|
|
})
|
|
|
|
case "module.child.aws_instance.foo":
|
|
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"num": cty.NumberIntVal(2),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-15 00:40:09 +02:00
|
|
|
})
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
checkVals(t, expected, ric.After)
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-07 22:55:35 +01:00
|
|
|
func TestContext2Plan_moduleVarWrongTypeBasic(t *testing.T) {
|
2016-04-15 21:07:54 +02:00
|
|
|
m := testModule(t, "plan-module-wrong-var-type")
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-04-15 21:07:54 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
t.Fatalf("succeeded; want errors")
|
2016-04-15 21:07:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_moduleVarWrongTypeNested(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-wrong-var-type-nested")
|
2017-04-22 02:40:46 +02:00
|
|
|
p := testProvider("null")
|
2016-04-15 21:07:54 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-04-15 21:07:54 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
t.Fatalf("succeeded; want errors")
|
2016-04-15 21:07:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-22 06:30:34 +02:00
|
|
|
func TestContext2Plan_moduleVarWithDefaultValue(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-var-with-default-value")
|
|
|
|
p := testProvider("null")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-04-22 06:30:34 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-04-22 06:30:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_moduleVarComputed(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-var-computed")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-15 00:40:09 +02:00
|
|
|
ty := schema.ImpliedType()
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-15 00:40:09 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-15 00:40:09 +02:00
|
|
|
case "aws_instance.bar":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.UnknownVal(cty.String),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
case "module.child.aws_instance.foo":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2019-02-12 02:05:24 +01:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.UnknownVal(cty.String),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2019-02-12 02:05:24 +01:00
|
|
|
"compute": cty.StringVal("foo"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-15 00:40:09 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_preventDestroy_bad(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-prevent-destroy-bad")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, err := ctx.Plan(m, state, DefaultPlanOpts)
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-05-22 20:45:07 +02:00
|
|
|
expectedErr := "aws_instance.foo has lifecycle.prevent_destroy"
|
2015-07-10 22:08:49 +02:00
|
|
|
if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) {
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if plan != nil {
|
|
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
|
|
}
|
|
|
|
t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, err)
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_preventDestroy_good(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-prevent-destroy-good")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123","type":"aws_instance"}`),
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2018-05-21 20:32:43 +02:00
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if !plan.Changes.Empty() {
|
2018-09-15 00:40:09 +02:00
|
|
|
t.Fatalf("expected no changes, got %#v\n", plan.Changes)
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-29 03:31:47 +02:00
|
|
|
func TestContext2Plan_preventDestroy_countBad(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-prevent-destroy-count-bad")
|
|
|
|
p := testProvider("aws")
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc345"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2016-10-29 03:31:47 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-10-29 03:31:47 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, err := ctx.Plan(m, state, DefaultPlanOpts)
|
2016-10-29 03:31:47 +02:00
|
|
|
|
2018-05-22 20:45:07 +02:00
|
|
|
expectedErr := "aws_instance.foo[1] has lifecycle.prevent_destroy"
|
2016-10-29 03:31:47 +02:00
|
|
|
if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) {
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if plan != nil {
|
|
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
|
|
}
|
|
|
|
t.Fatalf("expected err would contain %q\nerr: %s", expectedErr, err)
|
2016-10-29 03:31:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_preventDestroy_countGood(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-prevent-destroy-count-good")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"current": {Type: cty.String, Optional: true},
|
2020-03-31 20:03:33 +02:00
|
|
|
"id": {Type: cty.String, Computed: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc345"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2016-10-29 03:31:47 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-10-29 03:31:47 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-10-29 03:31:47 +02:00
|
|
|
}
|
2018-05-21 20:32:43 +02:00
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if plan.Changes.Empty() {
|
|
|
|
t.Fatalf("Expected non-empty plan, got %s", legacyDiffComparisonString(plan.Changes))
|
2016-10-29 03:31:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_preventDestroy_countGoodNoChange(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-prevent-destroy-count-good")
|
|
|
|
p := testProvider("aws")
|
2021-01-12 18:39:25 +01:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"current": {Type: cty.String, Optional: true},
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": {Type: cty.String, Optional: true, Computed: true},
|
2020-03-31 20:03:33 +02:00
|
|
|
"id": {Type: cty.String, Computed: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123","current":"0","type":"aws_instance"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2016-10-29 03:31:47 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-10-29 03:31:47 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-10-29 03:31:47 +02:00
|
|
|
}
|
2018-05-21 20:32:43 +02:00
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if !plan.Changes.Empty() {
|
|
|
|
t.Fatalf("Expected empty plan, got %s", legacyDiffComparisonString(plan.Changes))
|
2016-10-29 03:31:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_preventDestroy_destroyPlan(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-prevent-destroy-good")
|
|
|
|
p := testProvider("aws")
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.DestroyMode,
|
|
|
|
})
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-05-22 20:45:07 +02:00
|
|
|
expectedErr := "aws_instance.foo has lifecycle.prevent_destroy"
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if !strings.Contains(fmt.Sprintf("%s", diags.Err()), expectedErr) {
|
|
|
|
if plan != nil {
|
|
|
|
t.Logf(legacyDiffComparisonString(plan.Changes))
|
|
|
|
}
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
t.Fatalf("expected diagnostics would contain %q\nactual diags: %s", expectedErr, diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-30 22:27:08 +01:00
|
|
|
func TestContext2Plan_provisionerCycle(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-provisioner-cycle")
|
|
|
|
p := testProvider("aws")
|
|
|
|
pr := testProvisioner()
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2020-11-13 18:43:28 +01:00
|
|
|
Provisioners: map[string]provisioners.Factory{
|
2016-10-30 22:27:08 +01:00
|
|
|
"local-exec": testProvisionerFuncFixed(pr),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
t.Fatalf("succeeded; want errors")
|
2016-10-30 22:27:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_computed(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-computed")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-18 01:25:11 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-18 01:25:11 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.bar":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.UnknownVal(cty.String),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.foo":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2019-02-12 02:05:24 +01:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.UnknownVal(cty.String),
|
|
|
|
"num": cty.NumberIntVal(2),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2019-02-12 02:05:24 +01:00
|
|
|
"compute": cty.StringVal("foo"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-09 00:32:53 +02:00
|
|
|
func TestContext2Plan_blockNestingGroup(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-block-nesting-group")
|
|
|
|
p := testProvider("test")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2019-04-09 00:32:53 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test": {
|
|
|
|
BlockTypes: map[string]*configschema.NestedBlock{
|
|
|
|
"blah": {
|
|
|
|
Nesting: configschema.NestingGroup,
|
|
|
|
Block: configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"baz": {Type: cty.String, Required: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2019-04-09 00:32:53 +02:00
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
|
|
return providers.PlanResourceChangeResponse{
|
|
|
|
PlannedState: req.ProposedNewState,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
2019-04-09 00:32:53 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2019-04-09 00:32:53 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
if got, want := 1, len(plan.Changes.Resources); got != want {
|
|
|
|
t.Fatalf("wrong number of planned resource changes %d; want %d\n%s", got, want, spew.Sdump(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
if !p.PlanResourceChangeCalled {
|
|
|
|
t.Fatalf("PlanResourceChange was not called at all")
|
|
|
|
}
|
|
|
|
|
|
|
|
got := p.PlanResourceChangeRequest
|
|
|
|
want := providers.PlanResourceChangeRequest{
|
|
|
|
TypeName: "test",
|
|
|
|
|
|
|
|
// Because block type "blah" is defined as NestingGroup, we get a non-null
|
|
|
|
// value for it with null nested attributes, rather than the "blah" object
|
|
|
|
// itself being null, when there's no "blah" block in the config at all.
|
|
|
|
//
|
|
|
|
// This represents the situation where the remote service _always_ creates
|
|
|
|
// a single "blah", regardless of whether the block is present, but when
|
|
|
|
// the block _is_ present the user can override some aspects of it. The
|
|
|
|
// absense of the block means "use the defaults", in that case.
|
|
|
|
Config: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"blah": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"baz": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
ProposedNewState: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"blah": cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"baz": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
if !cmp.Equal(got, want, valueTrans) {
|
|
|
|
t.Errorf("wrong PlanResourceChange request\n%s", cmp.Diff(got, want, valueTrans))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-21 22:00:46 +02:00
|
|
|
func TestContext2Plan_computedDataResource(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-computed-data-resource")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"num": {Type: cty.String, Optional: true},
|
|
|
|
"compute": {Type: cty.String, Optional: true},
|
|
|
|
"foo": {Type: cty.String, Computed: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
DataSources: map[string]*configschema.Block{
|
|
|
|
"aws_vpc": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"foo": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2016-05-21 22:00:46 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-05-21 22:00:46 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-05-21 22:00:46 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.DataSources["aws_vpc"].Block
|
2018-09-18 01:25:11 +02:00
|
|
|
ty := schema.ImpliedType()
|
2016-05-21 22:00:46 +02:00
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if rc := plan.Changes.ResourceInstance(addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "aws_instance", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)); rc == nil {
|
2016-05-21 22:00:46 +02:00
|
|
|
t.Fatalf("missing diff for aws_instance.foo")
|
|
|
|
}
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
rcs := plan.Changes.ResourceInstance(addrs.Resource{
|
|
|
|
Mode: addrs.DataResourceMode,
|
|
|
|
Type: "aws_vpc",
|
|
|
|
Name: "bar",
|
|
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
|
|
|
if rcs == nil {
|
2016-05-21 22:00:46 +02:00
|
|
|
t.Fatalf("missing diff for data.aws_vpc.bar")
|
|
|
|
}
|
2016-05-21 22:23:28 +02:00
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
rc, err := rcs.Decode(ty)
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2016-05-21 22:23:28 +02:00
|
|
|
}
|
2018-09-18 01:25:11 +02:00
|
|
|
|
|
|
|
checkVals(t,
|
|
|
|
cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"foo": cty.UnknownVal(cty.String),
|
|
|
|
}),
|
|
|
|
rc.After,
|
|
|
|
)
|
2016-05-21 22:00:46 +02:00
|
|
|
}
|
|
|
|
|
2018-10-02 10:45:17 +02:00
|
|
|
func TestContext2Plan_computedInFunction(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-computed-in-function")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-10-02 10:45:17 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"attr": {Type: cty.Number, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
DataSources: map[string]*configschema.Block{
|
|
|
|
"aws_data_source": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"computed": {Type: cty.List(cty.String), Computed: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2021-01-09 21:33:38 +01:00
|
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
2018-10-02 19:58:49 +02:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"computed": cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("foo"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}
|
2018-10-02 10:45:17 +02:00
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-10-02 10:45:17 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
diags := ctx.Validate(m)
|
2018-10-02 19:58:49 +02:00
|
|
|
assertNoErrors(t, diags)
|
2018-10-02 10:45:17 +02:00
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags = ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-10-02 19:58:49 +02:00
|
|
|
assertNoErrors(t, diags)
|
2018-10-02 10:45:17 +02:00
|
|
|
|
2018-10-02 19:58:49 +02:00
|
|
|
if !p.ReadDataSourceCalled {
|
2020-09-22 02:59:50 +02:00
|
|
|
t.Fatalf("ReadDataSource was not called on provider during plan; should've been called")
|
2018-10-02 10:45:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-02 22:29:25 +02:00
|
|
|
func TestContext2Plan_computedDataCountResource(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-computed-data-count")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"num": {Type: cty.String, Optional: true},
|
|
|
|
"compute": {Type: cty.String, Optional: true},
|
|
|
|
"foo": {Type: cty.String, Computed: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
DataSources: map[string]*configschema.Block{
|
|
|
|
"aws_vpc": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"foo": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2016-09-02 22:29:25 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-09-02 22:29:25 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-09-02 22:29:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// make sure we created 3 "bar"s
|
|
|
|
for i := 0; i < 3; i++ {
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
addr := addrs.Resource{
|
|
|
|
Mode: addrs.DataResourceMode,
|
|
|
|
Type: "aws_vpc",
|
|
|
|
Name: "bar",
|
|
|
|
}.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance)
|
|
|
|
|
|
|
|
if rcs := plan.Changes.ResourceInstance(addr); rcs == nil {
|
|
|
|
t.Fatalf("missing changes for %s", addr)
|
2016-09-02 22:29:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-01 18:35:49 +02:00
|
|
|
func TestContext2Plan_localValueCount(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-local-value-count")
|
|
|
|
p := testProvider("test")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
2017-09-01 18:35:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2017-09-01 18:35:49 +02:00
|
|
|
}
|
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
// make sure we created 3 "foo"s
|
2017-09-01 18:35:49 +02:00
|
|
|
for i := 0; i < 3; i++ {
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
addr := addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "test_resource",
|
|
|
|
Name: "foo",
|
|
|
|
}.Instance(addrs.IntKey(i)).Absolute(addrs.RootModuleInstance)
|
|
|
|
|
|
|
|
if rcs := plan.Changes.ResourceInstance(addr); rcs == nil {
|
|
|
|
t.Fatalf("missing changes for %s", addr)
|
2017-09-01 18:35:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-data-resource-becomes-computed")
|
2016-07-01 01:22:20 +02:00
|
|
|
p := testProvider("aws")
|
2018-09-18 01:25:11 +02:00
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-09-18 01:25:11 +02:00
|
|
|
"foo": {Type: cty.String, Optional: true},
|
|
|
|
"computed": {Type: cty.String, Computed: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
DataSources: map[string]*configschema.Block{
|
2018-09-18 01:25:11 +02:00
|
|
|
"aws_data_source": {
|
2018-05-23 02:53:56 +02:00
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2020-05-06 03:23:27 +02:00
|
|
|
"id": {Type: cty.String, Computed: true},
|
2018-09-18 01:25:11 +02:00
|
|
|
"foo": {Type: cty.String, Optional: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-09-18 01:25:11 +02:00
|
|
|
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
2019-02-08 02:33:17 +01:00
|
|
|
fooVal := req.ProposedNewState.GetAttr("foo")
|
2018-09-18 01:25:11 +02:00
|
|
|
return providers.PlanResourceChangeResponse{
|
2019-02-08 02:33:17 +01:00
|
|
|
PlannedState: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"foo": fooVal,
|
|
|
|
"computed": cty.UnknownVal(cty.String),
|
|
|
|
}),
|
2018-09-18 01:25:11 +02:00
|
|
|
PlannedPrivate: req.PriorPrivate,
|
2018-09-05 23:35:30 +02:00
|
|
|
}
|
2016-07-01 01:22:20 +02:00
|
|
|
}
|
2018-09-18 01:25:11 +02:00
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.DataSources["aws_data_source"].Block
|
2018-09-18 01:25:11 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
2021-01-09 21:33:38 +01:00
|
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
2019-02-08 02:33:17 +01:00
|
|
|
// This should not be called, because the configuration for the
|
|
|
|
// data resource contains an unknown value for "foo".
|
|
|
|
Diagnostics: tfdiags.Diagnostics(nil).Append(fmt.Errorf("ReadDataSource called, but should not have been")),
|
2016-07-01 01:22:20 +02:00
|
|
|
}
|
2018-09-18 01:25:11 +02:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("data.aws_data_source.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123","foo":"baz"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2016-07-01 01:22:20 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-07-01 01:22:20 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-09-18 01:25:11 +02:00
|
|
|
if diags.HasErrors() {
|
2018-10-03 01:50:53 +02:00
|
|
|
t.Fatalf("unexpected errors during plan: %s", diags.Err())
|
2016-07-01 01:22:20 +02:00
|
|
|
}
|
2016-05-28 21:39:36 +02:00
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
rcs := plan.Changes.ResourceInstance(addrs.Resource{
|
|
|
|
Mode: addrs.DataResourceMode,
|
|
|
|
Type: "aws_data_source",
|
|
|
|
Name: "foo",
|
|
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
|
|
|
if rcs == nil {
|
2018-10-03 01:50:53 +02:00
|
|
|
t.Logf("full changeset: %s", spew.Sdump(plan.Changes))
|
2018-09-18 01:25:11 +02:00
|
|
|
t.Fatalf("missing diff for data.aws_data_resource.foo")
|
|
|
|
}
|
2016-05-28 21:39:36 +02:00
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
rc, err := rcs.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-10-18 01:29:24 +02:00
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
// foo should now be unknown
|
|
|
|
foo := rc.After.GetAttr("foo")
|
|
|
|
if foo.IsKnown() {
|
|
|
|
t.Fatalf("foo should be unknown, got %#v", foo)
|
|
|
|
}
|
2016-05-28 21:39:36 +02:00
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_computedList(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-computed-list")
|
|
|
|
p := testProvider("aws")
|
2021-01-12 18:39:25 +01:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"compute": {Type: cty.String, Optional: true},
|
|
|
|
"foo": {Type: cty.String, Optional: true},
|
|
|
|
"num": {Type: cty.String, Optional: true},
|
|
|
|
"list": {Type: cty.List(cty.String), Computed: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-18 01:25:11 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-18 01:25:11 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.bar":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"foo": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.foo":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2019-02-12 02:05:24 +01:00
|
|
|
"list": cty.UnknownVal(cty.List(cty.String)),
|
|
|
|
"num": cty.NumberIntVal(2),
|
|
|
|
"compute": cty.StringVal("list.#"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-11 01:17:29 +01:00
|
|
|
// GH-8695. This tests that you can index into a computed list on a
|
|
|
|
// splatted resource.
|
|
|
|
func TestContext2Plan_computedMultiIndex(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-computed-multi-index")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2018-11-19 22:59:27 +01:00
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-09-18 01:25:11 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"compute": {Type: cty.String, Optional: true},
|
|
|
|
"foo": {Type: cty.List(cty.String), Optional: true},
|
|
|
|
"ip": {Type: cty.List(cty.String), Computed: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-09-18 01:25:11 +02:00
|
|
|
|
2016-12-11 01:17:29 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-12-11 01:17:29 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-12-11 01:17:29 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-18 01:25:11 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
|
|
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-18 01:25:11 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.foo[0]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2019-02-12 02:05:24 +01:00
|
|
|
"ip": cty.UnknownVal(cty.List(cty.String)),
|
|
|
|
"foo": cty.NullVal(cty.List(cty.String)),
|
|
|
|
"compute": cty.StringVal("ip.#"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.foo[1]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2019-02-12 02:05:24 +01:00
|
|
|
"ip": cty.UnknownVal(cty.List(cty.String)),
|
|
|
|
"foo": cty.NullVal(cty.List(cty.String)),
|
|
|
|
"compute": cty.StringVal("ip.#"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.bar[0]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"foo": cty.UnknownVal(cty.List(cty.String)),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2016-12-11 01:17:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_count(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 6 {
|
|
|
|
t.Fatal("expected 6 changes, got", len(plan.Changes.Resources))
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.bar":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo,foo,foo,foo,foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[0]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[1]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[2]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[3]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[4]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_countComputed(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-computed")
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2015-07-10 22:08:49 +02:00
|
|
|
if err == nil {
|
|
|
|
t.Fatal("should error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-11 20:02:49 +01:00
|
|
|
func TestContext2Plan_countComputedModule(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-computed-module")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2016-11-11 20:02:49 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-11-11 20:02:49 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2016-11-11 20:02:49 +01:00
|
|
|
|
2018-05-24 00:03:01 +02:00
|
|
|
expectedErr := `The "count" value depends on resource attributes`
|
2016-11-11 20:02:49 +01:00
|
|
|
if !strings.Contains(fmt.Sprintf("%s", err), expectedErr) {
|
|
|
|
t.Fatalf("expected err would contain %q\nerr: %s\n",
|
|
|
|
expectedErr, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-28 05:32:55 +01:00
|
|
|
func TestContext2Plan_countModuleStatic(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-module-static")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2017-01-28 05:32:55 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2017-01-28 05:32:55 +01:00
|
|
|
})
|
|
|
|
|
2021-12-21 01:38:52 +01:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2017-01-28 05:32:55 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-18 01:25:11 +02:00
|
|
|
ty := schema.ImpliedType()
|
2017-01-28 05:32:55 +01:00
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
|
|
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2017-01-28 05:32:55 +01:00
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-18 01:25:11 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-01-28 05:32:55 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-18 01:25:11 +02:00
|
|
|
case "module.child.aws_instance.foo[0]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "module.child.aws_instance.foo[1]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "module.child.aws_instance.foo[2]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2017-01-28 05:32:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-28 05:38:07 +01:00
|
|
|
func TestContext2Plan_countModuleStaticGrandchild(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-module-static-grandchild")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2017-01-28 05:38:07 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2017-01-28 05:38:07 +01:00
|
|
|
})
|
|
|
|
|
2021-12-21 01:38:52 +01:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2017-01-28 05:38:07 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-18 01:25:11 +02:00
|
|
|
ty := schema.ImpliedType()
|
2017-01-28 05:38:07 +01:00
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
|
|
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2017-01-28 05:38:07 +01:00
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-18 01:25:11 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-01-28 05:38:07 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-18 01:25:11 +02:00
|
|
|
case "module.child.module.child.aws_instance.foo[0]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "module.child.module.child.aws_instance.foo[1]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "module.child.module.child.aws_instance.foo[2]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2017-01-28 05:38:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_countIndex(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-index")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-18 01:25:11 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-18 01:25:11 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.foo[0]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("0"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.foo[1]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("1"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_countVar(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-var")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
SetVariables: InputValues{
|
2018-05-23 18:59:03 +02:00
|
|
|
"instance_count": &InputValue{
|
2018-05-05 04:24:06 +02:00
|
|
|
Value: cty.StringVal("3"),
|
|
|
|
SourceType: ValueFromCaller,
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-18 01:25:11 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
|
|
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
2018-09-18 01:25:11 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.bar":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo,foo,foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.foo[0]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.foo[1]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
case "aws_instance.foo[2]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2018-09-18 01:25:11 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_countZero(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-zero")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"foo": {Type: cty.DynamicPseudoType, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-09-19 00:44:51 +02:00
|
|
|
|
|
|
|
// This schema contains a DynamicPseudoType, and therefore can't go through any shim functions
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
|
|
resp.PlannedState = req.ProposedNewState
|
|
|
|
resp.PlannedPrivate = req.PriorPrivate
|
|
|
|
return resp
|
|
|
|
}
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-18 01:25:11 +02:00
|
|
|
ty := schema.ImpliedType()
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-18 01:25:11 +02:00
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2018-09-18 01:25:11 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
res := plan.Changes.Resources[0]
|
2018-09-18 01:25:11 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2018-09-18 01:25:11 +02:00
|
|
|
}
|
|
|
|
|
2018-09-20 03:31:59 +02:00
|
|
|
expected := cty.TupleVal(nil)
|
2018-09-18 01:25:11 +02:00
|
|
|
|
2018-09-20 03:31:59 +02:00
|
|
|
foo := ric.After.GetAttr("foo")
|
|
|
|
|
|
|
|
if !cmp.Equal(expected, foo, valueComparer) {
|
|
|
|
t.Fatal(cmp.Diff(expected, foo, valueComparer))
|
2018-09-19 00:44:51 +02:00
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_countOneIndex(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-one-index")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.bar":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[0]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_countDecreaseToOne(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-dec")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[2]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
|
|
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.bar":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("bar"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo":
|
|
|
|
if res.Action != plans.NoOp {
|
|
|
|
t.Fatalf("resource %s should be unchanged", i)
|
|
|
|
}
|
|
|
|
case "aws_instance.foo[1]":
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("expected resource delete, got %s", res.Action)
|
|
|
|
}
|
|
|
|
case "aws_instance.foo[2]":
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("expected resource delete, got %s", res.Action)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-11 17:42:05 +02:00
|
|
|
expectedState := `aws_instance.foo:
|
2018-09-19 00:44:51 +02:00
|
|
|
ID = bar
|
2020-03-31 20:03:33 +02:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
2018-09-19 00:44:51 +02:00
|
|
|
foo = foo
|
|
|
|
type = aws_instance
|
|
|
|
aws_instance.foo.1:
|
|
|
|
ID = bar
|
2020-03-31 20:03:33 +02:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]
|
2018-09-19 00:44:51 +02:00
|
|
|
aws_instance.foo.2:
|
|
|
|
ID = bar
|
2020-03-31 20:03:33 +02:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/aws"]`
|
2018-09-19 00:44:51 +02:00
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
if plan.PriorState.String() != expectedState {
|
|
|
|
t.Fatalf("epected state:\n%q\n\ngot state:\n%q\n", expectedState, plan.PriorState.String())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-inc")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar","type":"aws_instance","foo":"foo"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
|
|
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.bar":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("bar"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[0]":
|
|
|
|
if res.Action != plans.NoOp {
|
|
|
|
t.Fatalf("resource %s should be unchanged", i)
|
|
|
|
}
|
|
|
|
case "aws_instance.foo[1]":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[2]":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_countIncreaseFromOne(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-inc")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
|
|
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.bar":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("bar"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[0]":
|
|
|
|
if res.Action != plans.NoOp {
|
|
|
|
t.Fatalf("resource %s should be unchanged", i)
|
|
|
|
}
|
|
|
|
case "aws_instance.foo[1]":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[2]":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://github.com/PeoplePerHour/terraform/pull/11
|
|
|
|
//
|
|
|
|
// This tests a case where both a "resource" and "resource.0" are in
|
|
|
|
// the state file, which apparently is a reasonable backwards compatibility
|
|
|
|
// concern found in the above 3rd party repo.
|
|
|
|
func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-inc")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"foo","type":"aws_instance"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 5 {
|
|
|
|
t.Fatal("expected 5 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.bar":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("bar"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo":
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("resource %s should be removed", i)
|
|
|
|
}
|
|
|
|
case "aws_instance.foo[0]":
|
|
|
|
if res.Action != plans.NoOp {
|
|
|
|
t.Fatalf("resource %s should be unchanged", i)
|
|
|
|
}
|
|
|
|
case "aws_instance.foo[1]":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[2]":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-05 01:46:48 +02:00
|
|
|
// A common pattern in TF configs is to have a set of resources with the same
|
|
|
|
// count and to use count.index to create correspondences between them:
|
|
|
|
//
|
|
|
|
// foo_id = "${foo.bar.*.id[count.index]}"
|
|
|
|
//
|
|
|
|
// This test is for the situation where some instances already exist and the
|
|
|
|
// count is increased. In that case, we should see only the create diffs
|
|
|
|
// for the new instances and not any update diffs for the existing ones.
|
|
|
|
func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-count-splat-reference")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"name": {Type: cty.String, Optional: true},
|
|
|
|
"foo_name": {Type: cty.String, Optional: true},
|
2020-03-31 20:03:33 +02:00
|
|
|
"id": {Type: cty.String, Computed: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar","name":"foo 0"}`),
|
2017-05-05 01:46:48 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar","name":"foo 1"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.bar[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar","foo_name":"foo 0"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.bar[1]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar","foo_name":"foo 1"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2017-05-05 01:46:48 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2017-05-05 01:46:48 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2017-05-05 01:46:48 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2017-05-05 01:46:48 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 6 {
|
|
|
|
t.Fatal("expected 6 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2017-05-05 01:46:48 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-05-05 01:46:48 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.bar[0]", "aws_instance.bar[1]", "aws_instance.foo[0]", "aws_instance.foo[1]":
|
|
|
|
if res.Action != plans.NoOp {
|
|
|
|
t.Fatalf("resource %s should be unchanged", i)
|
|
|
|
}
|
|
|
|
case "aws_instance.bar[2]":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
2020-03-31 20:03:33 +02:00
|
|
|
// The instance ID changed, so just check that the name updated
|
|
|
|
if ric.After.GetAttr("foo_name") != cty.StringVal("foo 2") {
|
|
|
|
t.Fatalf("resource %s attr \"foo_name\" should be changed", i)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
case "aws_instance.foo[2]":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource create, got %s", res.Action)
|
|
|
|
}
|
2020-03-31 20:03:33 +02:00
|
|
|
// The instance ID changed, so just check that the name updated
|
|
|
|
if ric.After.GetAttr("name") != cty.StringVal("foo 2") {
|
|
|
|
t.Fatalf("resource %s attr \"name\" should be changed", i)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2017-05-05 01:46:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-12 17:07:32 +02:00
|
|
|
func TestContext2Plan_forEach(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-for-each")
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2019-06-12 17:07:32 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2019-06-12 17:07:32 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2019-06-12 17:07:32 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 8 {
|
|
|
|
t.Fatal("expected 8 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
_, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
2019-08-26 19:27:33 +02:00
|
|
|
}
|
|
|
|
|
2019-08-26 21:25:03 +02:00
|
|
|
func TestContext2Plan_forEachUnknownValue(t *testing.T) {
|
2019-10-08 00:24:16 +02:00
|
|
|
// This module has a variable defined, but it's value is unknown. We
|
|
|
|
// expect this to produce an error, but not to panic.
|
2019-08-26 21:25:03 +02:00
|
|
|
m := testModule(t, "plan-for-each-unknown-value")
|
2019-08-26 19:27:33 +02:00
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
SetVariables: InputValues{
|
2019-10-08 00:24:16 +02:00
|
|
|
"foo": {
|
|
|
|
Value: cty.UnknownVal(cty.String),
|
|
|
|
SourceType: ValueFromCLIArg,
|
|
|
|
},
|
|
|
|
},
|
2019-08-26 19:27:33 +02:00
|
|
|
})
|
2019-08-26 21:25:03 +02:00
|
|
|
if !diags.HasErrors() {
|
2019-08-26 21:30:42 +02:00
|
|
|
// Should get this error:
|
|
|
|
// Invalid for_each argument: The "for_each" value depends on resource attributes that cannot be determined until apply...
|
|
|
|
t.Fatal("succeeded; want errors")
|
|
|
|
}
|
|
|
|
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
|
|
wantErrStr := "Invalid for_each argument"
|
|
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
2019-08-26 19:27:33 +02:00
|
|
|
}
|
2019-06-12 17:07:32 +02:00
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_destroy(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-destroy")
|
|
|
|
p := testProvider("aws")
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.one").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.two").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"baz"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.DestroyMode,
|
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if len(plan.Changes.Resources) != 2 {
|
2018-09-19 00:44:51 +02:00
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.one", "aws_instance.two":
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("resource %s should be removed", i)
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_moduleDestroy(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-destroy")
|
|
|
|
p := testProvider("aws")
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
|
|
child.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.DestroyMode,
|
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.foo", "module.child.aws_instance.foo":
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("resource %s should be removed", i)
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GH-1835
|
|
|
|
func TestContext2Plan_moduleDestroyCycle(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-destroy-gh-1835")
|
|
|
|
p := testProvider("aws")
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
aModule := state.EnsureModule(addrs.RootModuleInstance.Child("a_module", addrs.NoKey))
|
|
|
|
aModule.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.a").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"a"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
bModule := state.EnsureModule(addrs.RootModuleInstance.Child("b_module", addrs.NoKey))
|
|
|
|
bModule.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.b").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"b"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.DestroyMode,
|
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "module.a_module.aws_instance.a", "module.b_module.aws_instance.b":
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("resource %s should be removed", i)
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_moduleDestroyMultivar(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-destroy-multivar")
|
|
|
|
p := testProvider("aws")
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
|
|
child.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar0"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
child.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar1"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.DestroyMode,
|
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "module.child.aws_instance.foo[0]", "module.child.aws_instance.foo[1]":
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("resource %s should be removed", i)
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_pathVar(t *testing.T) {
|
|
|
|
cwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
m := testModule(t, "plan-path-var")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"cwd": {Type: cty.String, Optional: true},
|
|
|
|
"module": {Type: cty.String, Optional: true},
|
|
|
|
"root": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-05 04:24:06 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("err: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.foo":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created", i)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"cwd": cty.StringVal(cwd + "/barpath"),
|
|
|
|
"module": cty.StringVal(m.Module.SourceDir + "/foopath"),
|
|
|
|
"root": cty.StringVal(m.Module.SourceDir + "/barpath"),
|
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_diffVar(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-diffvar")
|
|
|
|
p := testProvider("aws")
|
2021-01-12 18:39:25 +01:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"bar","num":"2","type":"aws_instance"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.bar":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created", i)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"num": cty.NumberIntVal(3),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo":
|
|
|
|
if res.Action != plans.Update {
|
|
|
|
t.Fatalf("resource %s should be updated", i)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.StringVal("bar"),
|
|
|
|
"num": cty.NumberIntVal(2),
|
|
|
|
"type": cty.StringVal("aws_instance"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.Before)
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.StringVal("bar"),
|
|
|
|
"num": cty.NumberIntVal(3),
|
|
|
|
"type": cty.StringVal("aws_instance"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_hook(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-good")
|
|
|
|
h := new(MockHook)
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
Hooks: []Hook{h},
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if !h.PreDiffCalled {
|
|
|
|
t.Fatal("should be called")
|
|
|
|
}
|
|
|
|
if !h.PostDiffCalled {
|
|
|
|
t.Fatal("should be called")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-04 21:00:09 +01:00
|
|
|
func TestContext2Plan_closeProvider(t *testing.T) {
|
|
|
|
// this fixture only has an aliased provider located in the module, to make
|
|
|
|
// sure that the provier name contains a path more complex than
|
|
|
|
// "provider.aws".
|
|
|
|
m := testModule(t, "plan-close-module-provider")
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-01-04 21:00:09 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2018-01-04 21:00:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if !p.CloseCalled {
|
|
|
|
t.Fatal("provider not closed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_orphan(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-orphan")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.baz").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.baz":
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("resource %s should be removed", i)
|
|
|
|
}
|
2021-09-23 02:58:41 +02:00
|
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want {
|
2021-04-28 21:02:34 +02:00
|
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
case "aws_instance.foo":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created", i)
|
|
|
|
}
|
2021-04-28 21:02:34 +02:00
|
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"num": cty.NumberIntVal(2),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-14 19:17:42 +01:00
|
|
|
// This tests that configurations with UUIDs don't produce errors.
|
|
|
|
// For shadows, this would produce errors since a UUID changes every time.
|
|
|
|
func TestContext2Plan_shadowUuid(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-shadow-uuid")
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-11-14 19:17:42 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-11-14 19:17:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_state(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-good")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if len(plan.Changes.Resources) < 2 {
|
|
|
|
t.Fatalf("bad: %#v", plan.Changes.Resources)
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.bar":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created", i)
|
|
|
|
}
|
2021-04-28 21:02:34 +02:00
|
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("2"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo":
|
|
|
|
if res.Action != plans.Update {
|
|
|
|
t.Fatalf("resource %s should be updated", i)
|
|
|
|
}
|
2021-04-28 21:02:34 +02:00
|
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("bar"),
|
|
|
|
"num": cty.NullVal(cty.Number),
|
|
|
|
"type": cty.NullVal(cty.String),
|
|
|
|
}), ric.Before)
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("bar"),
|
|
|
|
"num": cty.NumberIntVal(2),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-28 21:02:34 +02:00
|
|
|
func TestContext2Plan_requiresReplace(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-requires-replace")
|
|
|
|
p := testProvider("test")
|
|
|
|
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
|
|
|
Provider: providers.Schema{
|
|
|
|
Block: &configschema.Block{},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]providers.Schema{
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
"test_thing": {
|
2021-04-28 21:02:34 +02:00
|
|
|
Block: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"v": {
|
|
|
|
Type: cty.String,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
|
|
return providers.PlanResourceChangeResponse{
|
|
|
|
PlannedState: req.ProposedNewState,
|
|
|
|
RequiresReplace: []cty.Path{
|
|
|
|
cty.GetAttrPath("v"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("test_thing.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"v":"hello"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
|
|
)
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2021-04-28 21:02:34 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["test_thing"].Block
|
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if got, want := len(plan.Changes.Resources), 1; got != want {
|
|
|
|
t.Fatalf("got %d changes; want %d", got, want)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
t.Run(res.Addr.String(), func(t *testing.T) {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "test_thing.foo":
|
|
|
|
if got, want := ric.Action, plans.DeleteThenCreate; got != want {
|
|
|
|
t.Errorf("wrong action\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
|
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceReplaceBecauseCannotUpdate; got != want {
|
|
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"v": cty.StringVal("goodbye"),
|
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource instance %s", i)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_taint(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-taint")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"bar","num":"2","type":"aws_instance"}`),
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectTainted,
|
|
|
|
AttrsJSON: []byte(`{"id":"baz"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
2021-04-28 21:02:34 +02:00
|
|
|
t.Run(res.Addr.String(), func(t *testing.T) {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2018-09-19 00:44:51 +02:00
|
|
|
}
|
2021-04-28 21:02:34 +02:00
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.bar":
|
|
|
|
if got, want := res.Action, plans.DeleteThenCreate; got != want {
|
|
|
|
t.Errorf("wrong action\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
|
|
|
if got, want := res.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want {
|
|
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("2"),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo":
|
|
|
|
if got, want := res.Action, plans.NoOp; got != want {
|
|
|
|
t.Errorf("wrong action\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
|
|
|
if got, want := res.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
|
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
2018-09-19 00:44:51 +02:00
|
|
|
}
|
2021-04-28 21:02:34 +02:00
|
|
|
})
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-22 01:20:32 +02:00
|
|
|
func TestContext2Plan_taintIgnoreChanges(t *testing.T) {
|
2016-10-27 14:44:59 +02:00
|
|
|
m := testModule(t, "plan-taint-ignore-changes")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-09-19 00:44:51 +02:00
|
|
|
"id": {Type: cty.String, Computed: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
"vars": {Type: cty.String, Optional: true},
|
2018-09-19 00:44:51 +02:00
|
|
|
"type": {Type: cty.String, Computed: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectTainted,
|
|
|
|
AttrsJSON: []byte(`{"id":"foo","vars":"foo","type":"aws_instance"}`),
|
2016-10-27 14:44:59 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2016-10-27 14:44:59 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-10-27 14:44:59 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-10-27 14:44:59 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.foo":
|
2021-04-28 21:02:34 +02:00
|
|
|
if got, want := res.Action, plans.DeleteThenCreate; got != want {
|
|
|
|
t.Errorf("wrong action\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
|
|
|
if got, want := res.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want {
|
|
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
2018-09-19 00:44:51 +02:00
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("foo"),
|
|
|
|
"vars": cty.StringVal("foo"),
|
|
|
|
"type": cty.StringVal("aws_instance"),
|
|
|
|
}), ric.Before)
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"vars": cty.StringVal("foo"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2016-10-27 14:44:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-06 00:40:35 +01:00
|
|
|
// Fails about 50% of the time before the fix for GH-4982, covers the fix.
|
|
|
|
func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-taint-interpolated-count")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectTainted,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`),
|
2016-02-06 00:40:35 +01:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`),
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[2]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"bar","type":"aws_instance"}`),
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
2016-02-06 00:40:35 +01:00
|
|
|
|
|
|
|
for i := 0; i < 100; i++ {
|
2018-09-19 00:44:51 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-09-19 00:44:51 +02:00
|
|
|
})
|
|
|
|
|
2021-12-21 01:38:52 +01:00
|
|
|
plan, diags := ctx.Plan(m, state.DeepCopy(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-02-06 00:40:35 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2016-02-06 00:40:35 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 3 {
|
|
|
|
t.Fatal("expected 3 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2016-02-06 00:40:35 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-02-06 00:40:35 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.foo[0]":
|
2021-04-28 21:02:34 +02:00
|
|
|
if got, want := ric.Action, plans.DeleteThenCreate; got != want {
|
|
|
|
t.Errorf("wrong action\ngot: %s\nwant: %s", got, want)
|
|
|
|
}
|
|
|
|
if got, want := ric.ActionReason, plans.ResourceInstanceReplaceBecauseTainted; got != want {
|
|
|
|
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
2018-09-19 00:44:51 +02:00
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.StringVal("bar"),
|
|
|
|
"type": cty.StringVal("aws_instance"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.Before)
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[1]", "aws_instance.foo[2]":
|
|
|
|
if res.Action != plans.NoOp {
|
|
|
|
t.Fatalf("resource %s should not be changed", i)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2016-02-06 00:40:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
func TestContext2Plan_targeted(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-targeted")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2015-07-10 22:08:49 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
2018-05-05 04:24:06 +02:00
|
|
|
Targets: []addrs.Targetable{
|
|
|
|
addrs.RootModuleInstance.Resource(
|
|
|
|
addrs.ManagedResourceMode, "aws_instance", "foo",
|
|
|
|
),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.foo":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created", i)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"num": cty.NumberIntVal(2),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2015-11-11 18:35:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-13 05:54:46 +01:00
|
|
|
// Test that targeting a module properly plans any inputs that depend
|
|
|
|
// on another module.
|
|
|
|
func TestContext2Plan_targetedCrossModule(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-targeted-cross-module")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2016-12-13 05:54:46 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
2018-05-05 04:24:06 +02:00
|
|
|
Targets: []addrs.Targetable{
|
|
|
|
addrs.RootModuleInstance.Child("B", addrs.NoKey),
|
|
|
|
},
|
2016-12-13 05:54:46 +01:00
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-12-13 05:54:46 +01:00
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2016-12-13 05:54:46 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2016-12-13 05:54:46 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created", ric.Addr)
|
|
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "module.A.aws_instance.foo":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.StringVal("bar"),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "module.B.aws_instance.bar":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"foo": cty.UnknownVal(cty.String),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2016-12-13 05:54:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-17 18:21:29 +01:00
|
|
|
func TestContext2Plan_targetedModuleWithProvider(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-targeted-module-with-provider")
|
|
|
|
p := testProvider("null")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"key": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"null_resource": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2017-02-17 18:21:29 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
2018-05-05 04:24:06 +02:00
|
|
|
Targets: []addrs.Targetable{
|
|
|
|
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
|
|
|
|
},
|
2017-02-17 18:21:29 +01:00
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2017-02-17 18:21:29 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["null_resource"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2017-02-17 18:21:29 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2017-02-17 18:21:29 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
res := plan.Changes.Resources[0]
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-02-17 18:21:29 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if ric.Addr.String() != "module.child2.null_resource.foo" {
|
|
|
|
t.Fatalf("unexpcetd resource: %s", ric.Addr)
|
2017-02-17 18:21:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-11 18:35:35 +01:00
|
|
|
func TestContext2Plan_targetedOrphan(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-targeted-orphan")
|
|
|
|
p := testProvider("aws")
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.orphan").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-789xyz"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.nottargeted").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-11-11 18:35:35 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.DestroyMode,
|
2018-05-05 04:24:06 +02:00
|
|
|
Targets: []addrs.Targetable{
|
|
|
|
addrs.RootModuleInstance.Resource(
|
|
|
|
addrs.ManagedResourceMode, "aws_instance", "orphan",
|
|
|
|
),
|
|
|
|
},
|
2015-11-11 18:35:35 +01:00
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-11-11 18:35:35 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2015-11-11 18:35:35 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2015-11-11 18:35:35 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-11-11 18:35:35 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.orphan":
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("resource %s should be destroyed", ric.Addr)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2016-01-07 21:43:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://github.com/hashicorp/terraform/issues/2538
|
|
|
|
func TestContext2Plan_targetedModuleOrphan(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-targeted-module-orphan")
|
|
|
|
p := testProvider("aws")
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
|
|
|
child.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.orphan").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-789xyz"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
child.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.nottargeted").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2016-01-07 21:43:43 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.DestroyMode,
|
2018-05-05 04:24:06 +02:00
|
|
|
Targets: []addrs.Targetable{
|
|
|
|
addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource(
|
|
|
|
addrs.ManagedResourceMode, "aws_instance", "orphan",
|
|
|
|
),
|
|
|
|
},
|
2016-01-07 21:43:43 +01:00
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-01-07 21:43:43 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2016-01-07 21:43:43 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2016-01-07 21:43:43 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
res := plan.Changes.Resources[0]
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-01-07 21:43:43 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if ric.Addr.String() != "module.child.aws_instance.orphan" {
|
|
|
|
t.Fatalf("unexpected resource :%s", ric.Addr)
|
|
|
|
}
|
|
|
|
if res.Action != plans.Delete {
|
|
|
|
t.Fatalf("resource %s should be deleted", ric.Addr)
|
2016-01-07 21:43:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-29 17:53:13 +02:00
|
|
|
func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-targeted-module-untargeted-variable")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2016-07-29 17:53:13 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
2018-05-05 04:24:06 +02:00
|
|
|
Targets: []addrs.Targetable{
|
|
|
|
addrs.RootModuleInstance.Resource(
|
|
|
|
addrs.ManagedResourceMode, "aws_instance", "blue",
|
|
|
|
),
|
|
|
|
addrs.RootModuleInstance.Child("blue_mod", addrs.NoKey),
|
|
|
|
},
|
2016-07-29 17:53:13 +02:00
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-07-29 17:53:13 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2016-07-29 17:53:13 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
2016-07-29 17:53:13 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created", ric.Addr)
|
|
|
|
}
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.blue":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "module.blue_mod.aws_instance.mod":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"value": cty.UnknownVal(cty.String),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2016-07-29 17:53:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-20 01:32:37 +01:00
|
|
|
// ensure that outputs missing references due to targetting are removed from
|
|
|
|
// the graph.
|
|
|
|
func TestContext2Plan_outputContainsTargetedResource(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-untargeted-resource-output")
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
2018-05-05 04:24:06 +02:00
|
|
|
Targets: []addrs.Targetable{
|
|
|
|
addrs.RootModuleInstance.Child("mod", addrs.NoKey).Resource(
|
|
|
|
addrs.ManagedResourceMode, "aws_instance", "a",
|
|
|
|
),
|
|
|
|
},
|
2018-03-20 01:32:37 +01:00
|
|
|
})
|
2019-09-12 21:11:54 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("err: %s", diags)
|
|
|
|
}
|
|
|
|
if len(diags) != 1 {
|
|
|
|
t.Fatalf("got %d diagnostics; want 1", diags)
|
|
|
|
}
|
|
|
|
if got, want := diags[0].Severity(), tfdiags.Warning; got != want {
|
|
|
|
t.Errorf("wrong diagnostic severity %#v; want %#v", got, want)
|
|
|
|
}
|
|
|
|
if got, want := diags[0].Description().Summary, "Resource targeting is in effect"; got != want {
|
|
|
|
t.Errorf("wrong diagnostic summary %#v; want %#v", got, want)
|
2018-03-20 01:32:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-07 21:43:43 +01:00
|
|
|
// https://github.com/hashicorp/terraform/issues/4515
|
|
|
|
func TestContext2Plan_targetedOverTen(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-targeted-over-ten")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2016-01-07 21:43:43 +01:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
2016-01-07 21:43:43 +01:00
|
|
|
for i := 0; i < 13; i++ {
|
2020-03-31 20:03:33 +02:00
|
|
|
key := fmt.Sprintf("aws_instance.foo[%d]", i)
|
2016-01-07 21:43:43 +01:00
|
|
|
id := fmt.Sprintf("i-abc%d", i)
|
2020-10-08 14:56:03 +02:00
|
|
|
attrs := fmt.Sprintf(`{"id":"%s","type":"aws_instance"}`, id)
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr(key).Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(attrs),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
2016-01-07 21:43:43 +01:00
|
|
|
}
|
2020-03-31 20:03:33 +02:00
|
|
|
|
2016-01-07 21:43:43 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
2018-05-05 04:24:06 +02:00
|
|
|
Targets: []addrs.Targetable{
|
|
|
|
addrs.RootModuleInstance.ResourceInstance(
|
|
|
|
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1),
|
|
|
|
),
|
|
|
|
},
|
2016-01-07 21:43:43 +01:00
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-01-07 21:43:43 +01:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
2016-01-07 21:43:43 +01:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if res.Action != plans.NoOp {
|
|
|
|
t.Fatalf("unexpected action %s for %s", res.Action, ric.Addr)
|
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_provider(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-provider")
|
|
|
|
p := testProvider("aws")
|
|
|
|
|
|
|
|
var value interface{}
|
2021-02-18 16:13:43 +01:00
|
|
|
p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
|
2020-10-08 18:26:12 +02:00
|
|
|
value = req.Config.GetAttr("foo").AsString()
|
|
|
|
return
|
2015-07-10 22:08:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
opts := &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
SetVariables: InputValues{
|
2018-05-05 04:24:06 +02:00
|
|
|
"foo": &InputValue{
|
|
|
|
Value: cty.StringVal("bar"),
|
|
|
|
SourceType: ValueFromCaller,
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
}
|
2015-07-10 22:08:49 +02:00
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
if _, err := ctx.Plan(m, states.NewState(), opts); err != nil {
|
2015-07-10 22:08:49 +02:00
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if value != "bar" {
|
|
|
|
t.Fatalf("bad: %#v", value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_varListErr(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-var-list-err")
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2015-07-10 22:08:49 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, err := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2016-04-15 21:07:54 +02:00
|
|
|
|
2015-07-10 22:08:49 +02:00
|
|
|
if err == nil {
|
|
|
|
t.Fatal("should error")
|
|
|
|
}
|
|
|
|
}
|
2015-08-09 10:02:28 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_ignoreChanges(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-ignore-changes")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","type":"aws_instance"}`),
|
2015-08-09 10:02:28 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2015-08-09 10:02:28 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
SetVariables: InputValues{
|
2018-05-05 04:24:06 +02:00
|
|
|
"foo": &InputValue{
|
|
|
|
Value: cty.StringVal("ami-1234abcd"),
|
|
|
|
SourceType: ValueFromCaller,
|
|
|
|
},
|
2015-08-09 10:02:28 +02:00
|
|
|
},
|
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2015-08-09 10:02:28 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2018-09-19 00:44:51 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
res := plan.Changes.Resources[0]
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-08-09 10:02:28 +02:00
|
|
|
|
2018-09-19 00:44:51 +02:00
|
|
|
if ric.Addr.String() != "aws_instance.foo" {
|
|
|
|
t.Fatalf("unexpected resource: %s", ric.Addr)
|
2015-08-09 10:02:28 +02:00
|
|
|
}
|
2018-09-19 00:44:51 +02:00
|
|
|
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2020-10-08 14:56:03 +02:00
|
|
|
"id": cty.StringVal("bar"),
|
|
|
|
"ami": cty.StringVal("ami-abcd1234"),
|
|
|
|
"type": cty.StringVal("aws_instance"),
|
2018-09-19 00:44:51 +02:00
|
|
|
}), ric.After)
|
2015-08-09 10:02:28 +02:00
|
|
|
}
|
2016-07-06 16:11:46 +02:00
|
|
|
|
2016-09-02 15:44:35 +02:00
|
|
|
func TestContext2Plan_ignoreChangesWildcard(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-ignore-changes-wildcard")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2020-03-31 20:03:33 +02:00
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","instance":"t2.micro","type":"aws_instance"}`),
|
2016-09-02 15:44:35 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
2016-09-02 15:44:35 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
SetVariables: InputValues{
|
2018-05-05 04:24:06 +02:00
|
|
|
"foo": &InputValue{
|
|
|
|
Value: cty.StringVal("ami-1234abcd"),
|
|
|
|
SourceType: ValueFromCaller,
|
|
|
|
},
|
|
|
|
"bar": &InputValue{
|
|
|
|
Value: cty.StringVal("t2.small"),
|
|
|
|
SourceType: ValueFromCaller,
|
|
|
|
},
|
2016-09-02 15:44:35 +02:00
|
|
|
},
|
|
|
|
})
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-09-02 15:44:35 +02:00
|
|
|
}
|
|
|
|
|
2018-09-20 03:31:59 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.NoOp {
|
|
|
|
t.Fatalf("unexpected resource diffs in root module: %s", spew.Sdump(plan.Changes.Resources))
|
|
|
|
}
|
2016-09-02 15:44:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-19 01:03:33 +02:00
|
|
|
func TestContext2Plan_ignoreChangesInMap(t *testing.T) {
|
|
|
|
p := testProvider("test")
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2019-06-19 01:03:33 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_ignore_changes_map": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"tags": {Type: cty.Map(cty.String), Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2019-06-19 01:03:33 +02:00
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
|
|
return providers.PlanResourceChangeResponse{
|
|
|
|
PlannedState: req.ProposedNewState,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s := states.BuildState(func(ss *states.SyncState) {
|
|
|
|
ss.SetResourceInstanceCurrent(
|
|
|
|
addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "test_ignore_changes_map",
|
|
|
|
Name: "foo",
|
|
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"foo","tags":{"ignored":"from state","other":"from state"},"type":"aws_instance"}`),
|
2019-06-19 01:03:33 +02:00
|
|
|
},
|
2020-02-13 21:32:58 +01:00
|
|
|
addrs.AbsProviderConfig{
|
2020-03-31 20:03:33 +02:00
|
|
|
Provider: addrs.NewDefaultProvider("test"),
|
2020-03-11 02:21:19 +01:00
|
|
|
Module: addrs.RootModule,
|
2020-02-13 21:32:58 +01:00
|
|
|
},
|
2019-06-19 01:03:33 +02:00
|
|
|
)
|
|
|
|
})
|
|
|
|
m := testModule(t, "plan-ignore-changes-in-map")
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
2019-06-19 01:03:33 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, s, DefaultPlanOpts)
|
2019-06-19 01:03:33 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["test_ignore_changes_map"].Block
|
2019-06-19 01:03:33 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if got, want := len(plan.Changes.Resources), 1; got != want {
|
|
|
|
t.Fatalf("wrong number of changes %d; want %d", got, want)
|
|
|
|
}
|
|
|
|
|
|
|
|
res := plan.Changes.Resources[0]
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if res.Action != plans.Update {
|
|
|
|
t.Fatalf("resource %s should be updated, got %s", ric.Addr, res.Action)
|
|
|
|
}
|
|
|
|
|
|
|
|
if got, want := ric.Addr.String(), "test_ignore_changes_map.foo"; got != want {
|
|
|
|
t.Fatalf("unexpected resource address %s; want %s", got, want)
|
|
|
|
}
|
|
|
|
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"tags": cty.MapVal(map[string]cty.Value{
|
|
|
|
"ignored": cty.StringVal("from state"),
|
|
|
|
"other": cty.StringVal("from config"),
|
|
|
|
}),
|
|
|
|
}), ric.After)
|
|
|
|
}
|
|
|
|
|
2020-10-20 18:20:52 +02:00
|
|
|
func TestContext2Plan_ignoreChangesSensitive(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-ignore-changes-sensitive")
|
|
|
|
p := testProvider("aws")
|
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"bar","ami":"ami-abcd1234","type":"aws_instance"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
SetVariables: InputValues{
|
2020-10-20 18:20:52 +02:00
|
|
|
"foo": &InputValue{
|
|
|
|
Value: cty.StringVal("ami-1234abcd"),
|
|
|
|
SourceType: ValueFromCaller,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2020-10-20 18:20:52 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
res := plan.Changes.Resources[0]
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ric.Addr.String() != "aws_instance.foo" {
|
|
|
|
t.Fatalf("unexpected resource: %s", ric.Addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("bar"),
|
2021-07-19 21:33:38 +02:00
|
|
|
"ami": cty.StringVal("ami-abcd1234"),
|
2020-10-20 18:20:52 +02:00
|
|
|
"type": cty.StringVal("aws_instance"),
|
|
|
|
}), ric.After)
|
|
|
|
}
|
|
|
|
|
2016-07-06 16:11:46 +02:00
|
|
|
func TestContext2Plan_moduleMapLiteral(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-map-literal")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"meta": {Type: cty.Map(cty.String), Optional: true},
|
|
|
|
"tags": {Type: cty.Map(cty.String), Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
2020-10-08 14:56:03 +02:00
|
|
|
s := req.ProposedNewState.AsValueMap()
|
|
|
|
m := s["tags"].AsValueMap()
|
|
|
|
|
|
|
|
if m["foo"].AsString() != "bar" {
|
2016-07-06 16:11:46 +02:00
|
|
|
t.Fatalf("Bad value in tags attr: %#v", m)
|
|
|
|
}
|
2020-10-08 14:56:03 +02:00
|
|
|
|
|
|
|
meta := s["meta"].AsValueMap()
|
|
|
|
if len(meta) != 0 {
|
|
|
|
t.Fatalf("Meta attr not empty: %#v", meta)
|
2016-07-06 16:11:46 +02:00
|
|
|
}
|
2020-10-08 14:56:03 +02:00
|
|
|
return testDiffFn(req)
|
2016-07-06 16:11:46 +02:00
|
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-07-06 16:11:46 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-07-06 16:11:46 +02:00
|
|
|
}
|
|
|
|
}
|
2016-07-07 20:14:33 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_computedValueInMap(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-computed-value-in-map")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"looked_up": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"aws_computed_source": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"computed_read_only": {Type: cty.String, Computed: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
2020-10-08 14:56:03 +02:00
|
|
|
resp = testDiffFn(req)
|
|
|
|
|
|
|
|
if req.TypeName != "aws_computed_source" {
|
|
|
|
return
|
2016-07-07 20:14:33 +02:00
|
|
|
}
|
|
|
|
|
2020-10-08 14:56:03 +02:00
|
|
|
planned := resp.PlannedState.AsValueMap()
|
|
|
|
planned["computed_read_only"] = cty.UnknownVal(cty.String)
|
|
|
|
resp.PlannedState = cty.ObjectVal(planned)
|
|
|
|
return resp
|
2016-07-07 20:14:33 +02:00
|
|
|
}
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2016-07-07 20:14:33 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-07-07 20:14:33 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-07-07 20:14:33 +02:00
|
|
|
}
|
|
|
|
|
2018-09-20 03:31:59 +02:00
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
2016-07-07 20:14:33 +02:00
|
|
|
}
|
|
|
|
|
2018-09-20 03:31:59 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block
|
2018-09-20 03:31:59 +02:00
|
|
|
|
|
|
|
ric, err := res.Decode(schema.ImpliedType())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created", ric.Addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_computed_source.intermediates":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"computed_read_only": cty.UnknownVal(cty.String),
|
|
|
|
}), ric.After)
|
|
|
|
case "module.test_mod.aws_instance.inner2":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"looked_up": cty.UnknownVal(cty.String),
|
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2016-07-07 20:14:33 +02:00
|
|
|
}
|
|
|
|
}
|
2016-07-13 19:23:56 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_moduleVariableFromSplat(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-module-variable-from-splat")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"thing": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2016-07-13 19:23:56 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-07-13 19:23:56 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-07-13 19:23:56 +02:00
|
|
|
}
|
|
|
|
|
2018-09-20 03:31:59 +02:00
|
|
|
if len(plan.Changes.Resources) != 4 {
|
|
|
|
t.Fatal("expected 4 changes, got", len(plan.Changes.Resources))
|
2016-07-13 19:23:56 +02:00
|
|
|
}
|
|
|
|
|
2018-09-20 03:31:59 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block
|
2018-09-20 03:31:59 +02:00
|
|
|
|
|
|
|
ric, err := res.Decode(schema.ImpliedType())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created", ric.Addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "module.mod1.aws_instance.test[0]",
|
|
|
|
"module.mod1.aws_instance.test[1]",
|
|
|
|
"module.mod2.aws_instance.test[0]",
|
|
|
|
"module.mod2.aws_instance.test[1]":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"thing": cty.StringVal("doesnt"),
|
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2016-07-13 19:23:56 +02:00
|
|
|
}
|
|
|
|
}
|
2016-11-03 23:31:25 +01:00
|
|
|
|
|
|
|
func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) {
|
2018-05-25 01:07:15 +02:00
|
|
|
m := testModule(t, "plan-cbd-depends-datasource")
|
2016-11-03 23:31:25 +01:00
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-09-20 03:31:59 +02:00
|
|
|
"num": {Type: cty.String, Optional: true},
|
|
|
|
"computed": {Type: cty.String, Optional: true, Computed: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
DataSources: map[string]*configschema.Block{
|
|
|
|
"aws_vpc": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-05-25 01:23:33 +02:00
|
|
|
"id": {Type: cty.String, Computed: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
"foo": {Type: cty.Number, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-09-28 23:54:59 +02:00
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
2019-02-08 03:33:05 +01:00
|
|
|
computedVal := req.ProposedNewState.GetAttr("computed")
|
|
|
|
if computedVal.IsNull() {
|
|
|
|
computedVal = cty.UnknownVal(cty.String)
|
|
|
|
}
|
2018-09-28 03:17:06 +02:00
|
|
|
return providers.PlanResourceChangeResponse{
|
2019-02-08 03:33:05 +01:00
|
|
|
PlannedState: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"num": req.ProposedNewState.GetAttr("num"),
|
|
|
|
"computed": computedVal,
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
2020-05-05 17:45:33 +02:00
|
|
|
cfg := req.Config.AsValueMap()
|
|
|
|
cfg["id"] = cty.StringVal("data_id")
|
2019-02-08 03:33:05 +01:00
|
|
|
return providers.ReadDataSourceResponse{
|
2020-05-05 17:45:33 +02:00
|
|
|
State: cty.ObjectVal(cfg),
|
2018-09-28 03:17:06 +02:00
|
|
|
}
|
|
|
|
}
|
2018-05-23 02:53:56 +02:00
|
|
|
|
2016-11-03 23:31:25 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-11-03 23:31:25 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-11-03 23:31:25 +01:00
|
|
|
}
|
|
|
|
|
2019-02-08 03:33:05 +01:00
|
|
|
seenAddrs := make(map[string]struct{})
|
2018-09-20 03:31:59 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
var schema *configschema.Block
|
|
|
|
switch res.Addr.Resource.Resource.Mode {
|
|
|
|
case addrs.DataResourceMode:
|
2021-02-18 16:13:43 +01:00
|
|
|
schema = p.GetProviderSchemaResponse.DataSources[res.Addr.Resource.Resource.Type].Block
|
2018-09-20 03:31:59 +02:00
|
|
|
case addrs.ManagedResourceMode:
|
2021-02-18 16:13:43 +01:00
|
|
|
schema = p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
}
|
2018-09-20 03:31:59 +02:00
|
|
|
|
|
|
|
ric, err := res.Decode(schema.ImpliedType())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2019-02-08 03:33:05 +01:00
|
|
|
seenAddrs[ric.Addr.String()] = struct{}{}
|
|
|
|
|
2018-09-28 23:54:59 +02:00
|
|
|
t.Run(ric.Addr.String(), func(t *testing.T) {
|
2018-09-28 03:17:06 +02:00
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.foo[0]":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"num": cty.StringVal("2"),
|
2020-05-05 17:45:33 +02:00
|
|
|
"computed": cty.StringVal("data_id"),
|
2018-09-28 03:17:06 +02:00
|
|
|
}), ric.After)
|
|
|
|
case "aws_instance.foo[1]":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created, got %s", ric.Addr, ric.Action)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"num": cty.StringVal("2"),
|
2020-05-05 17:45:33 +02:00
|
|
|
"computed": cty.StringVal("data_id"),
|
2018-09-28 03:17:06 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
2018-09-20 03:31:59 +02:00
|
|
|
}
|
2018-09-28 03:17:06 +02:00
|
|
|
})
|
2016-11-03 23:31:25 +01:00
|
|
|
}
|
2019-02-08 03:33:05 +01:00
|
|
|
|
|
|
|
wantAddrs := map[string]struct{}{
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
"aws_instance.foo[0]": {},
|
|
|
|
"aws_instance.foo[1]": {},
|
2019-02-08 03:33:05 +01:00
|
|
|
}
|
|
|
|
if !cmp.Equal(seenAddrs, wantAddrs) {
|
|
|
|
t.Errorf("incorrect addresses in changeset:\n%s", cmp.Diff(wantAddrs, seenAddrs))
|
|
|
|
}
|
2016-11-03 23:31:25 +01:00
|
|
|
}
|
2016-12-15 19:22:53 +01:00
|
|
|
|
|
|
|
// interpolated lists need to be stored in the original order.
|
|
|
|
func TestContext2Plan_listOrder(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-list-order")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"foo": {Type: cty.List(cty.String), Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2016-12-15 19:22:53 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2016-12-15 19:22:53 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2016-12-15 19:22:53 +01:00
|
|
|
}
|
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
changes := plan.Changes
|
|
|
|
rDiffA := changes.ResourceInstance(addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "aws_instance",
|
|
|
|
Name: "a",
|
|
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
|
|
|
rDiffB := changes.ResourceInstance(addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "aws_instance",
|
|
|
|
Name: "b",
|
|
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
2016-12-15 19:22:53 +01:00
|
|
|
|
2018-09-20 03:31:59 +02:00
|
|
|
if !cmp.Equal(rDiffA.After, rDiffB.After, valueComparer) {
|
|
|
|
t.Fatal(cmp.Diff(rDiffA.After, rDiffB.After, valueComparer))
|
2016-12-15 19:22:53 +01:00
|
|
|
}
|
|
|
|
}
|
2017-03-20 22:17:14 +01:00
|
|
|
|
|
|
|
// Make sure ignore-changes doesn't interfere with set/list/map diffs.
|
|
|
|
// If a resource was being replaced by a RequiresNew attribute that gets
|
2017-03-20 16:23:31 +01:00
|
|
|
// ignored, we need to filter the diff properly to properly update rather than
|
|
|
|
// replace.
|
2017-03-20 22:17:14 +01:00
|
|
|
func TestContext2Plan_ignoreChangesWithFlatmaps(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-ignore-changes-with-flatmaps")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-05-23 02:53:56 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"user_data": {Type: cty.String, Optional: true},
|
|
|
|
"require_new": {Type: cty.String, Optional: true},
|
2018-05-24 21:40:33 +02:00
|
|
|
|
|
|
|
// This test predates the 0.12 work to integrate cty and
|
|
|
|
// HCL, and so it was ported as-is where its expected
|
|
|
|
// test output was clearly expecting a list of maps here
|
|
|
|
// even though it is named "set".
|
|
|
|
"set": {Type: cty.List(cty.Map(cty.String)), Optional: true},
|
|
|
|
"lst": {Type: cty.List(cty.String), Optional: true},
|
2018-05-23 02:53:56 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{
|
|
|
|
"user_data":"x","require_new":"",
|
|
|
|
"set":[{"a":"1"}],
|
|
|
|
"lst":["j"]
|
|
|
|
}`),
|
2017-03-20 22:17:14 +01:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
2018-09-20 03:31:59 +02:00
|
|
|
|
2017-03-20 22:17:14 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2017-03-20 22:17:14 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
2017-03-20 22:17:14 +01:00
|
|
|
}
|
|
|
|
|
2018-09-20 03:31:59 +02:00
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
res := plan.Changes.Resources[0]
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes[res.Addr.Resource.Resource.Type].Block
|
2018-09-20 03:31:59 +02:00
|
|
|
|
|
|
|
ric, err := res.Decode(schema.ImpliedType())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.Action != plans.Update {
|
|
|
|
t.Fatalf("resource %s should be updated, got %s", ric.Addr, ric.Action)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ric.Addr.String() != "aws_instance.foo" {
|
|
|
|
t.Fatalf("unknown resource: %s", ric.Addr)
|
2017-03-20 22:17:14 +01:00
|
|
|
}
|
2018-09-20 03:31:59 +02:00
|
|
|
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"lst": cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("j"),
|
|
|
|
cty.StringVal("k"),
|
|
|
|
}),
|
|
|
|
"require_new": cty.StringVal(""),
|
|
|
|
"user_data": cty.StringVal("x"),
|
|
|
|
"set": cty.ListVal([]cty.Value{cty.MapVal(map[string]cty.Value{
|
|
|
|
"a": cty.StringVal("1"),
|
|
|
|
"b": cty.StringVal("2"),
|
|
|
|
})}),
|
|
|
|
}), ric.After)
|
2017-03-20 22:17:14 +01:00
|
|
|
}
|
2017-04-20 07:23:52 +02:00
|
|
|
|
|
|
|
// TestContext2Plan_resourceNestedCount ensures resource sets that depend on
|
|
|
|
// the count of another resource set (ie: count of a data source that depends
|
|
|
|
// on another data source's instance count - data.x.foo.*.id) get properly
|
|
|
|
// normalized to the indexes they should be. This case comes up when there is
|
|
|
|
// an existing state (after an initial apply).
|
|
|
|
func TestContext2Plan_resourceNestedCount(t *testing.T) {
|
|
|
|
m := testModule(t, "nested-resource-count-plan")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2018-09-05 23:35:30 +02:00
|
|
|
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
|
|
|
return providers.ReadResourceResponse{
|
|
|
|
NewState: req.PriorState,
|
|
|
|
}
|
2017-04-20 07:23:52 +02:00
|
|
|
}
|
2020-03-31 20:03:33 +02:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"foo0","type":"aws_instance"}`),
|
2017-04-20 07:23:52 +02:00
|
|
|
},
|
2020-03-31 20:03:33 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"foo1","type":"aws_instance"}`),
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.bar[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"bar0","type":"aws_instance"}`),
|
2020-09-24 17:57:43 +02:00
|
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.bar[1]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"bar1","type":"aws_instance"}`),
|
2020-09-24 17:57:43 +02:00
|
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.foo")},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.baz[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"baz0","type":"aws_instance"}`),
|
2020-09-24 17:57:43 +02:00
|
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.baz[1]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"baz1","type":"aws_instance"}`),
|
2020-09-24 17:57:43 +02:00
|
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("aws_instance.bar")},
|
2020-03-31 20:03:33 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
)
|
2017-04-20 07:23:52 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2017-04-20 07:23:52 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
diags := ctx.Validate(m)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("validate errors: %s", diags.Err())
|
2017-04-20 07:23:52 +02:00
|
|
|
}
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2018-05-21 20:32:43 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("plan errors: %s", diags.Err())
|
2017-04-20 07:23:52 +02:00
|
|
|
}
|
|
|
|
|
2018-09-20 03:31:59 +02:00
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.NoOp {
|
2020-10-08 14:56:03 +02:00
|
|
|
t.Fatalf("resource %s should not change, plan returned %s", res.Addr, res.Action)
|
2018-09-20 03:31:59 +02:00
|
|
|
}
|
2017-04-20 07:23:52 +02:00
|
|
|
}
|
|
|
|
}
|
2018-05-25 22:12:07 +02:00
|
|
|
|
|
|
|
// Higher level test at TestResource_dataSourceListApplyPanic
|
|
|
|
func TestContext2Plan_computedAttrRefTypeMismatch(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-computed-attr-ref-type-mismatch")
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
|
2018-09-05 23:35:30 +02:00
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
if req.TypeName == "aws_instance" {
|
|
|
|
amiVal := req.Config.GetAttr("ami")
|
|
|
|
if amiVal.Type() != cty.String {
|
|
|
|
diags = diags.Append(fmt.Errorf("Expected ami to be cty.String, got %#v", amiVal))
|
2018-05-25 22:12:07 +02:00
|
|
|
}
|
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
return providers.ValidateResourceConfigResponse{
|
2018-09-05 23:35:30 +02:00
|
|
|
Diagnostics: diags,
|
|
|
|
}
|
2018-05-25 22:12:07 +02:00
|
|
|
}
|
2020-10-08 19:13:13 +02:00
|
|
|
p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
|
2020-10-08 14:56:03 +02:00
|
|
|
if req.TypeName != "aws_ami_list" {
|
|
|
|
t.Fatalf("Reached apply for unexpected resource type! %s", req.TypeName)
|
2018-05-25 22:12:07 +02:00
|
|
|
}
|
|
|
|
// Pretend like we make a thing and the computed list "ids" is populated
|
2020-10-08 14:56:03 +02:00
|
|
|
s := req.PlannedState.AsValueMap()
|
|
|
|
s["id"] = cty.StringVal("someid")
|
|
|
|
s["ids"] = cty.ListVal([]cty.Value{
|
|
|
|
cty.StringVal("ami-abc123"),
|
|
|
|
cty.StringVal("ami-bcd345"),
|
|
|
|
})
|
|
|
|
|
|
|
|
resp.NewState = cty.ObjectVal(s)
|
|
|
|
return
|
2018-05-25 22:12:07 +02:00
|
|
|
}
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-05-25 22:12:07 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-05-25 22:12:07 +02:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
t.Fatalf("Succeeded; want type mismatch error for 'ami' argument")
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := `Inappropriate value for attribute "ami"`
|
|
|
|
if errStr := diags.Err().Error(); !strings.Contains(errStr, expected) {
|
|
|
|
t.Fatalf("expected:\n\n%s\n\nto contain:\n\n%s", errStr, expected)
|
|
|
|
}
|
|
|
|
}
|
2018-06-23 01:54:27 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_selfRef(t *testing.T) {
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-06-23 01:54:27 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"foo": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-06-23 01:54:27 +02:00
|
|
|
|
|
|
|
m := testModule(t, "plan-self-ref")
|
|
|
|
c := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-06-23 01:54:27 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
diags := c.Validate(m)
|
2018-06-23 01:54:27 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags = c.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-06-23 01:54:27 +02:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
t.Fatalf("plan succeeded; want error")
|
|
|
|
}
|
|
|
|
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
|
|
wantErrStr := "Self-referential block"
|
|
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_selfRefMulti(t *testing.T) {
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-06-23 01:54:27 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"foo": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-06-23 01:54:27 +02:00
|
|
|
|
|
|
|
m := testModule(t, "plan-self-ref-multi")
|
|
|
|
c := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-06-23 01:54:27 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
diags := c.Validate(m)
|
2018-06-23 01:54:27 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags = c.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-06-23 01:54:27 +02:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
t.Fatalf("plan succeeded; want error")
|
|
|
|
}
|
|
|
|
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
|
|
wantErrStr := "Self-referential block"
|
|
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_selfRefMultiAll(t *testing.T) {
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-06-23 01:54:27 +02:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"aws_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"foo": {Type: cty.List(cty.String), Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-06-23 01:54:27 +02:00
|
|
|
|
|
|
|
m := testModule(t, "plan-self-ref-multi-all")
|
|
|
|
c := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-06-23 01:54:27 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
diags := c.Validate(m)
|
2018-06-23 01:54:27 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected validation failure: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags = c.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-06-23 01:54:27 +02:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
t.Fatalf("plan succeeded; want error")
|
|
|
|
}
|
|
|
|
|
|
|
|
gotErrStr := diags.Err().Error()
|
2018-09-20 03:31:59 +02:00
|
|
|
|
|
|
|
// The graph is checked for cycles before we can walk it, so we don't
|
|
|
|
// encounter the self-reference check.
|
|
|
|
//wantErrStr := "Self-referential block"
|
|
|
|
wantErrStr := "Cycle"
|
2018-06-23 01:54:27 +02:00
|
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
|
|
}
|
|
|
|
}
|
2018-09-15 00:40:09 +02:00
|
|
|
|
2018-09-24 21:14:32 +02:00
|
|
|
func TestContext2Plan_invalidOutput(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
data "aws_data_source" "name" {}
|
|
|
|
|
|
|
|
output "out" {
|
2020-05-06 03:34:31 +02:00
|
|
|
value = data.aws_data_source.name.missing
|
2018-09-24 21:14:32 +02:00
|
|
|
}`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
2021-01-09 21:33:38 +01:00
|
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
2020-05-06 03:34:31 +02:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("data_id"),
|
|
|
|
"foo": cty.StringVal("foo"),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2018-09-24 21:14:32 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-09-24 21:14:32 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-09-24 21:14:32 +02:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
// Should get this error:
|
|
|
|
// Unsupported attribute: This object does not have an attribute named "missing"
|
|
|
|
t.Fatal("succeeded; want errors")
|
|
|
|
}
|
|
|
|
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
|
|
wantErrStr := "Unsupported attribute"
|
|
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_invalidModuleOutput(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"child/main.tf": `
|
|
|
|
data "aws_data_source" "name" {}
|
|
|
|
|
|
|
|
output "out" {
|
|
|
|
value = "${data.aws_data_source.name.missing}"
|
|
|
|
}`,
|
|
|
|
"main.tf": `
|
|
|
|
module "child" {
|
|
|
|
source = "./child"
|
|
|
|
}
|
|
|
|
|
|
|
|
resource "aws_instance" "foo" {
|
|
|
|
foo = "${module.child.out}"
|
|
|
|
}`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
2021-01-09 21:33:38 +01:00
|
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
2020-05-06 03:34:31 +02:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("data_id"),
|
|
|
|
"foo": cty.StringVal("foo"),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2018-09-24 21:14:32 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-09-24 21:14:32 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-09-24 21:14:32 +02:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
// Should get this error:
|
|
|
|
// Unsupported attribute: This object does not have an attribute named "missing"
|
|
|
|
t.Fatal("succeeded; want errors")
|
|
|
|
}
|
|
|
|
|
|
|
|
gotErrStr := diags.Err().Error()
|
|
|
|
wantErrStr := "Unsupported attribute"
|
|
|
|
if !strings.Contains(gotErrStr, wantErrStr) {
|
|
|
|
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErrStr, wantErrStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-01 03:09:20 +02:00
|
|
|
func TestContext2Plan_variableValidation(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
variable "x" {
|
|
|
|
default = "bar"
|
|
|
|
}
|
|
|
|
|
|
|
|
resource "aws_instance" "foo" {
|
|
|
|
foo = var.x
|
|
|
|
}`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) {
|
2019-06-01 03:09:20 +02:00
|
|
|
foo := req.Config.GetAttr("foo").AsString()
|
|
|
|
if foo == "bar" {
|
|
|
|
resp.Diagnostics = resp.Diagnostics.Append(errors.New("foo cannot be bar"))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
|
|
resp.PlannedState = req.ProposedNewState
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2019-06-01 03:09:20 +02:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2019-06-01 03:09:20 +02:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
// Should get this error:
|
|
|
|
// Unsupported attribute: This object does not have an attribute named "missing"
|
|
|
|
t.Fatal("succeeded; want errors")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-10 22:06:37 +02:00
|
|
|
func TestContext2Plan_variableSensitivity(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-variable-sensitivity")
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
|
|
resp.PlannedState = req.ProposedNewState
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2021-12-21 01:38:52 +01:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
2020-09-10 22:06:37 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2020-09-10 22:06:37 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "aws_instance.foo":
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2021-06-24 23:53:43 +02:00
|
|
|
"foo": cty.StringVal("foo").Mark(marks.Sensitive),
|
2020-09-10 22:06:37 +02:00
|
|
|
}), ric.After)
|
2020-09-16 22:48:28 +02:00
|
|
|
if len(res.ChangeSrc.BeforeValMarks) != 0 {
|
|
|
|
t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks)
|
|
|
|
}
|
|
|
|
if len(res.ChangeSrc.AfterValMarks) != 1 {
|
|
|
|
t.Errorf("unexpected AfterValMarks: %#v", res.ChangeSrc.AfterValMarks)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
pvm := res.ChangeSrc.AfterValMarks[0]
|
|
|
|
if got, want := pvm.Path, cty.GetAttrPath("foo"); !got.Equals(want) {
|
2020-09-16 22:54:04 +02:00
|
|
|
t.Errorf("unexpected path for mark\n got: %#v\nwant: %#v", got, want)
|
|
|
|
}
|
2021-06-24 23:53:43 +02:00
|
|
|
if got, want := pvm.Marks, cty.NewValueMarks(marks.Sensitive); !got.Equal(want) {
|
2020-09-16 22:54:04 +02:00
|
|
|
t.Errorf("unexpected value for mark\n got: %#v\nwant: %#v", got, want)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_variableSensitivityModule(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-variable-sensitivity-module")
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
2020-09-10 22:06:37 +02:00
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
|
|
resp.PlannedState = req.ProposedNewState
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
SetVariables: InputValues{
|
2021-12-21 01:38:52 +01:00
|
|
|
"sensitive_var": {Value: cty.NilVal},
|
2020-12-03 23:10:30 +01:00
|
|
|
"another_var": &InputValue{
|
|
|
|
Value: cty.StringVal("boop"),
|
|
|
|
SourceType: ValueFromCaller,
|
|
|
|
},
|
|
|
|
},
|
2020-09-10 22:06:37 +02:00
|
|
|
})
|
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2020-09-10 22:06:37 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
2020-09-16 22:54:04 +02:00
|
|
|
case "module.child.aws_instance.foo":
|
2020-09-10 22:06:37 +02:00
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
2021-06-24 23:53:43 +02:00
|
|
|
"foo": cty.StringVal("foo").Mark(marks.Sensitive),
|
|
|
|
"value": cty.StringVal("boop").Mark(marks.Sensitive),
|
2020-09-10 22:06:37 +02:00
|
|
|
}), ric.After)
|
2020-09-16 22:54:04 +02:00
|
|
|
if len(res.ChangeSrc.BeforeValMarks) != 0 {
|
|
|
|
t.Errorf("unexpected BeforeValMarks: %#v", res.ChangeSrc.BeforeValMarks)
|
|
|
|
}
|
2020-12-03 23:10:30 +01:00
|
|
|
if len(res.ChangeSrc.AfterValMarks) != 2 {
|
|
|
|
t.Errorf("expected AfterValMarks to contain two elements: %#v", res.ChangeSrc.AfterValMarks)
|
2020-09-16 22:54:04 +02:00
|
|
|
continue
|
|
|
|
}
|
2020-12-03 23:10:30 +01:00
|
|
|
// validate that the after marks have "foo" and "value"
|
|
|
|
contains := func(pvmSlice []cty.PathValueMarks, stepName string) bool {
|
|
|
|
for _, pvm := range pvmSlice {
|
|
|
|
if pvm.Path.Equals(cty.GetAttrPath(stepName)) {
|
2021-06-24 23:53:43 +02:00
|
|
|
if pvm.Marks.Equal(cty.NewValueMarks(marks.Sensitive)) {
|
2020-12-03 23:10:30 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2020-09-16 22:48:28 +02:00
|
|
|
}
|
2020-12-03 23:10:30 +01:00
|
|
|
if !contains(res.ChangeSrc.AfterValMarks, "foo") {
|
|
|
|
t.Error("unexpected AfterValMarks to contain \"foo\" with sensitive mark")
|
|
|
|
}
|
|
|
|
if !contains(res.ChangeSrc.AfterValMarks, "value") {
|
|
|
|
t.Error("unexpected AfterValMarks to contain \"value\" with sensitive mark")
|
2020-09-16 22:48:28 +02:00
|
|
|
}
|
2020-09-10 22:06:37 +02:00
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-15 00:40:09 +02:00
|
|
|
func checkVals(t *testing.T, expected, got cty.Value) {
|
|
|
|
t.Helper()
|
2021-05-12 14:55:10 +02:00
|
|
|
// The GoStringer format seems to result in the closest thing to a useful
|
|
|
|
// diff for values with marks.
|
|
|
|
// TODO: if we want to continue using cmp.Diff on cty.Values, we should
|
|
|
|
// make a transformer that creates a more comparable structure.
|
|
|
|
valueTrans := cmp.Transformer("gostring", func(v cty.Value) string {
|
|
|
|
return fmt.Sprintf("%#v\n", v)
|
|
|
|
})
|
2018-09-15 00:40:09 +02:00
|
|
|
if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) {
|
|
|
|
t.Fatal(cmp.Diff(expected, got, valueTrans, equateEmpty))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func objectVal(t *testing.T, schema *configschema.Block, m map[string]cty.Value) cty.Value {
|
|
|
|
t.Helper()
|
|
|
|
v, err := schema.CoerceValue(
|
|
|
|
cty.ObjectVal(m),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
2018-10-31 21:41:36 +01:00
|
|
|
|
|
|
|
func TestContext2Plan_requiredModuleOutput(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-required-output")
|
|
|
|
p := testProvider("test")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-10-31 21:41:36 +01:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_resource": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Computed: true},
|
|
|
|
"required": {Type: cty.String, Required: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-10-31 21:41:36 +01:00
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-10-31 21:41:36 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-10-31 21:41:36 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block
|
2018-11-02 01:41:35 +01:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
t.Run(fmt.Sprintf("%s %s", res.Action, res.Addr), func(t *testing.T) {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var expected cty.Value
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "test_resource.root":
|
|
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"required": cty.UnknownVal(cty.String),
|
|
|
|
})
|
|
|
|
case "module.mod.test_resource.for_output":
|
|
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"required": cty.StringVal("val"),
|
|
|
|
})
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
|
|
|
|
|
|
|
checkVals(t, expected, ric.After)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestContext2Plan_requiredModuleObject(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-required-whole-mod")
|
|
|
|
p := testProvider("test")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2018-11-02 01:41:35 +01:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_resource": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Computed: true},
|
|
|
|
"required": {Type: cty.String, Required: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2018-11-02 01:41:35 +01:00
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-03-31 20:03:33 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
2018-11-02 01:41:35 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2018-11-02 01:41:35 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Block
|
2018-10-31 21:41:36 +01:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 2 {
|
|
|
|
t.Fatal("expected 2 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
2018-11-02 01:32:30 +01:00
|
|
|
t.Run(fmt.Sprintf("%s %s", res.Action, res.Addr), func(t *testing.T) {
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("expected resource creation, got %s", res.Action)
|
|
|
|
}
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-10-31 21:41:36 +01:00
|
|
|
|
2018-11-02 01:32:30 +01:00
|
|
|
var expected cty.Value
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "test_resource.root":
|
|
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"required": cty.UnknownVal(cty.String),
|
|
|
|
})
|
|
|
|
case "module.mod.test_resource.for_output":
|
|
|
|
expected = objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"required": cty.StringVal("val"),
|
|
|
|
})
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
2018-10-31 21:41:36 +01:00
|
|
|
|
2018-11-02 01:32:30 +01:00
|
|
|
checkVals(t, expected, ric.After)
|
|
|
|
})
|
2018-10-31 21:41:36 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-27 17:42:00 +01:00
|
|
|
|
|
|
|
func TestContext2Plan_expandOrphan(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
module "mod" {
|
|
|
|
count = 1
|
|
|
|
source = "./mod"
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
"mod/main.tf": `
|
|
|
|
resource "aws_instance" "foo" {
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.IntKey(0))).SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"child","type":"aws_instance"}`),
|
2020-03-27 17:42:00 +01:00
|
|
|
},
|
2020-04-06 18:50:37 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
2020-03-27 17:42:00 +01:00
|
|
|
)
|
|
|
|
state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.IntKey(1))).SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("aws_instance.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"child","type":"aws_instance"}`),
|
2020-03-27 17:42:00 +01:00
|
|
|
},
|
2020-04-06 18:50:37 +02:00
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
2020-03-27 17:42:00 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-03-27 17:42:00 +01:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
2020-04-06 18:50:37 +02:00
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
2020-03-27 17:42:00 +01:00
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2020-03-27 17:42:00 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
|
|
}
|
|
|
|
|
2020-03-27 22:16:08 +01:00
|
|
|
expected := map[string]plans.Action{
|
|
|
|
`module.mod[1].aws_instance.foo`: plans.Delete,
|
|
|
|
`module.mod[0].aws_instance.foo`: plans.NoOp,
|
2020-03-27 17:42:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
2020-03-27 22:16:08 +01:00
|
|
|
want := expected[res.Addr.String()]
|
|
|
|
if res.Action != want {
|
|
|
|
t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action)
|
2020-03-27 17:42:00 +01:00
|
|
|
}
|
2020-04-03 17:28:31 +02:00
|
|
|
delete(expected, res.Addr.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
for res, action := range expected {
|
|
|
|
t.Errorf("missing %s change for %s", action, res)
|
2020-03-27 17:42:00 +01:00
|
|
|
}
|
|
|
|
}
|
2020-04-10 22:52:47 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_indexInVar(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
module "a" {
|
|
|
|
count = 1
|
|
|
|
source = "./mod"
|
|
|
|
in = "test"
|
|
|
|
}
|
|
|
|
|
|
|
|
module "b" {
|
|
|
|
count = 1
|
|
|
|
source = "./mod"
|
|
|
|
in = length(module.a)
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
"mod/main.tf": `
|
|
|
|
resource "aws_instance" "foo" {
|
|
|
|
foo = var.in
|
|
|
|
}
|
|
|
|
|
|
|
|
variable "in" {
|
|
|
|
}
|
|
|
|
|
|
|
|
output"out" {
|
|
|
|
value = aws_instance.foo.id
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2020-04-10 22:52:47 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
|
|
}
|
|
|
|
}
|
2020-06-10 22:11:05 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_targetExpandedAddress(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
module "mod" {
|
|
|
|
count = 3
|
|
|
|
source = "./mod"
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
"mod/main.tf": `
|
|
|
|
resource "aws_instance" "foo" {
|
|
|
|
count = 2
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
|
|
|
|
|
|
|
targets := []addrs.Targetable{}
|
|
|
|
target, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo[0]")
|
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
|
|
}
|
|
|
|
targets = append(targets, target.Subject)
|
|
|
|
|
|
|
|
target, diags = addrs.ParseTargetStr("module.mod[2]")
|
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
|
|
}
|
|
|
|
targets = append(targets, target.Subject)
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
Targets: targets,
|
|
|
|
})
|
2020-06-10 22:11:05 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := map[string]plans.Action{
|
|
|
|
// the single targeted mod[1] instances
|
|
|
|
`module.mod[1].aws_instance.foo[0]`: plans.Create,
|
|
|
|
// the whole mode[2]
|
|
|
|
`module.mod[2].aws_instance.foo[0]`: plans.Create,
|
|
|
|
`module.mod[2].aws_instance.foo[1]`: plans.Create,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
want := expected[res.Addr.String()]
|
|
|
|
if res.Action != want {
|
|
|
|
t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action)
|
|
|
|
}
|
|
|
|
delete(expected, res.Addr.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
for res, action := range expected {
|
|
|
|
t.Errorf("missing %s change for %s", action, res)
|
|
|
|
}
|
|
|
|
}
|
2020-06-16 02:45:23 +02:00
|
|
|
|
2020-08-05 23:50:23 +02:00
|
|
|
func TestContext2Plan_targetResourceInModuleInstance(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
module "mod" {
|
|
|
|
count = 3
|
|
|
|
source = "./mod"
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
"mod/main.tf": `
|
|
|
|
resource "aws_instance" "foo" {
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
|
|
|
|
|
|
|
target, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo")
|
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
|
|
}
|
|
|
|
|
|
|
|
targets := []addrs.Targetable{target.Subject}
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
Targets: targets,
|
|
|
|
})
|
2020-08-05 23:50:23 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := map[string]plans.Action{
|
|
|
|
// the single targeted mod[1] instance
|
|
|
|
`module.mod[1].aws_instance.foo`: plans.Create,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
want := expected[res.Addr.String()]
|
|
|
|
if res.Action != want {
|
|
|
|
t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action)
|
|
|
|
}
|
|
|
|
delete(expected, res.Addr.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
for res, action := range expected {
|
|
|
|
t.Errorf("missing %s change for %s", action, res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-16 02:45:23 +02:00
|
|
|
func TestContext2Plan_moduleRefIndex(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
module "mod" {
|
|
|
|
for_each = {
|
|
|
|
a = "thing"
|
|
|
|
}
|
|
|
|
in = null
|
|
|
|
source = "./mod"
|
|
|
|
}
|
|
|
|
|
|
|
|
module "single" {
|
|
|
|
source = "./mod"
|
|
|
|
in = module.mod["a"]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
"mod/main.tf": `
|
|
|
|
variable "in" {
|
|
|
|
}
|
|
|
|
|
|
|
|
output "out" {
|
|
|
|
value = "foo"
|
|
|
|
}
|
|
|
|
|
|
|
|
resource "aws_instance" "foo" {
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("aws")
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2020-06-16 02:45:23 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
|
|
}
|
|
|
|
}
|
2020-06-19 01:05:45 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_noChangeDataPlan(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
data "test_data_source" "foo" {}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := new(MockProvider)
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2020-06-19 01:05:45 +02:00
|
|
|
DataSources: map[string]*configschema.Block{
|
|
|
|
"test_data_source": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {
|
|
|
|
Type: cty.String,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
"foo": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2020-06-19 01:05:45 +02:00
|
|
|
|
2021-01-09 21:33:38 +01:00
|
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
2020-06-19 01:05:45 +02:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("data_id"),
|
|
|
|
"foo": cty.StringVal("foo"),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("data.test_data_source.foo").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"data_id", "foo":"foo"}`),
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
|
|
)
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2020-06-19 01:05:45 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
if res.Action != plans.NoOp {
|
|
|
|
t.Fatalf("expected NoOp, got: %q %s", res.Addr, res.Action)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-23 23:17:06 +02:00
|
|
|
|
|
|
|
// for_each can reference a resource with 0 instances
|
|
|
|
func TestContext2Plan_scaleInForEach(t *testing.T) {
|
|
|
|
p := testProvider("test")
|
|
|
|
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
locals {
|
|
|
|
m = {}
|
|
|
|
}
|
|
|
|
|
|
|
|
resource "test_instance" "a" {
|
|
|
|
for_each = local.m
|
|
|
|
}
|
|
|
|
|
|
|
|
resource "test_instance" "b" {
|
|
|
|
for_each = test_instance.a
|
|
|
|
}
|
|
|
|
`})
|
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("test_instance.a[0]").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"a0"}`),
|
|
|
|
Dependencies: []addrs.ConfigResource{},
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
|
|
)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("test_instance.b").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"b"}`),
|
2020-09-24 17:57:43 +02:00
|
|
|
Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_instance.a")},
|
2020-07-23 23:17:06 +02:00
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
|
|
)
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2021-09-23 02:58:41 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2020-07-23 23:17:06 +02:00
|
|
|
assertNoErrors(t, diags)
|
2021-09-23 02:58:41 +02:00
|
|
|
|
|
|
|
t.Run("test_instance.a[0]", func(t *testing.T) {
|
|
|
|
instAddr := mustResourceInstanceAddr("test_instance.a[0]")
|
|
|
|
change := plan.Changes.ResourceInstance(instAddr)
|
|
|
|
if change == nil {
|
|
|
|
t.Fatalf("no planned change for %s", instAddr)
|
|
|
|
}
|
|
|
|
if got, want := change.PrevRunAddr, instAddr; !want.Equal(got) {
|
|
|
|
t.Errorf("wrong previous run address for %s %s; want %s", instAddr, got, want)
|
|
|
|
}
|
|
|
|
if got, want := change.Action, plans.Delete; got != want {
|
|
|
|
t.Errorf("wrong action for %s %s; want %s", instAddr, got, want)
|
|
|
|
}
|
|
|
|
if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want {
|
|
|
|
t.Errorf("wrong action reason for %s %s; want %s", instAddr, got, want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
t.Run("test_instance.b", func(t *testing.T) {
|
|
|
|
instAddr := mustResourceInstanceAddr("test_instance.b")
|
|
|
|
change := plan.Changes.ResourceInstance(instAddr)
|
|
|
|
if change == nil {
|
|
|
|
t.Fatalf("no planned change for %s", instAddr)
|
|
|
|
}
|
|
|
|
if got, want := change.PrevRunAddr, instAddr; !want.Equal(got) {
|
|
|
|
t.Errorf("wrong previous run address for %s %s; want %s", instAddr, got, want)
|
|
|
|
}
|
|
|
|
if got, want := change.Action, plans.Delete; got != want {
|
|
|
|
t.Errorf("wrong action for %s %s; want %s", instAddr, got, want)
|
|
|
|
}
|
|
|
|
if got, want := change.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want {
|
|
|
|
t.Errorf("wrong action reason for %s %s; want %s", instAddr, got, want)
|
|
|
|
}
|
|
|
|
})
|
2020-07-23 23:17:06 +02:00
|
|
|
}
|
2020-08-10 18:16:20 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_targetedModuleInstance(t *testing.T) {
|
|
|
|
m := testModule(t, "plan-targeted")
|
|
|
|
p := testProvider("aws")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-08-10 18:16:20 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
2020-08-10 18:16:20 +02:00
|
|
|
Targets: []addrs.Targetable{
|
|
|
|
addrs.RootModuleInstance.Child("mod", addrs.IntKey(0)),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatalf("unexpected errors: %s", diags.Err())
|
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
2020-08-10 18:16:20 +02:00
|
|
|
ty := schema.ImpliedType()
|
|
|
|
|
|
|
|
if len(plan.Changes.Resources) != 1 {
|
|
|
|
t.Fatal("expected 1 changes, got", len(plan.Changes.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range plan.Changes.Resources {
|
|
|
|
ric, err := res.Decode(ty)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch i := ric.Addr.String(); i {
|
|
|
|
case "module.mod[0].aws_instance.foo":
|
|
|
|
if res.Action != plans.Create {
|
|
|
|
t.Fatalf("resource %s should be created", i)
|
|
|
|
}
|
|
|
|
checkVals(t, objectVal(t, schema, map[string]cty.Value{
|
|
|
|
"id": cty.UnknownVal(cty.String),
|
|
|
|
"num": cty.NumberIntVal(2),
|
2020-10-08 14:56:03 +02:00
|
|
|
"type": cty.UnknownVal(cty.String),
|
2020-08-10 18:16:20 +02:00
|
|
|
}), ric.After)
|
|
|
|
default:
|
|
|
|
t.Fatal("unknown instance:", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-17 23:12:10 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_dataRefreshedInPlan(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
data "test_data_source" "d" {
|
|
|
|
}
|
|
|
|
`})
|
|
|
|
|
|
|
|
p := testProvider("test")
|
2021-01-09 21:33:38 +01:00
|
|
|
p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
|
2020-09-17 23:12:10 +02:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("this"),
|
|
|
|
"foo": cty.NullVal(cty.String),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2020-09-17 23:12:10 +02:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.ErrWithWarnings())
|
|
|
|
}
|
|
|
|
|
2021-05-05 00:59:58 +02:00
|
|
|
d := plan.PriorState.ResourceInstance(mustResourceInstanceAddr("data.test_data_source.d"))
|
2020-09-17 23:12:10 +02:00
|
|
|
if d == nil || d.Current == nil {
|
2021-05-05 00:59:58 +02:00
|
|
|
t.Fatal("data.test_data_source.d not found in state:", plan.PriorState)
|
2020-09-17 23:12:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if d.Current.Status != states.ObjectReady {
|
|
|
|
t.Fatal("expected data.test_data_source.d to be fully read in refreshed state, got status", d.Current.Status)
|
|
|
|
}
|
|
|
|
}
|
2020-09-17 21:32:32 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_dataReferencesResource(t *testing.T) {
|
|
|
|
p := testProvider("test")
|
|
|
|
|
|
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
|
|
|
|
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("data source should not be read"))
|
|
|
|
return resp
|
|
|
|
}
|
|
|
|
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
locals {
|
2020-10-01 19:49:27 +02:00
|
|
|
x = "value"
|
2020-09-17 21:32:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
resource "test_resource" "a" {
|
2020-10-01 19:49:27 +02:00
|
|
|
value = local.x
|
2020-09-17 21:32:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// test_resource.a.value can be resolved during plan, but the reference implies
|
|
|
|
// that the data source should wait until the resource is created.
|
|
|
|
data "test_data_source" "d" {
|
2020-10-01 19:49:27 +02:00
|
|
|
foo = test_resource.a.value
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure referencing an indexed instance that has not yet created will also
|
|
|
|
// delay reading the data source
|
|
|
|
resource "test_resource" "b" {
|
|
|
|
count = 2
|
|
|
|
value = local.x
|
|
|
|
}
|
|
|
|
|
|
|
|
data "test_data_source" "e" {
|
|
|
|
foo = test_resource.b[0].value
|
2020-09-17 21:32:32 +02:00
|
|
|
}
|
|
|
|
`})
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2020-09-17 21:32:32 +02:00
|
|
|
assertNoErrors(t, diags)
|
|
|
|
}
|
2020-09-23 17:06:56 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_skipRefresh(t *testing.T) {
|
|
|
|
p := testProvider("test")
|
2020-10-08 19:13:13 +02:00
|
|
|
p.PlanResourceChangeFn = testDiffFn
|
2020-09-23 17:06:56 +02:00
|
|
|
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
resource "test_instance" "a" {
|
|
|
|
}
|
|
|
|
`})
|
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("test_instance.a").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
2020-10-08 14:56:03 +02:00
|
|
|
AttrsJSON: []byte(`{"id":"a","type":"test_instance"}`),
|
2020-09-23 17:06:56 +02:00
|
|
|
Dependencies: []addrs.ConfigResource{},
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
|
|
)
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
|
|
|
Mode: plans.NormalMode,
|
|
|
|
SkipRefresh: true,
|
|
|
|
})
|
2020-09-23 17:06:56 +02:00
|
|
|
assertNoErrors(t, diags)
|
|
|
|
|
|
|
|
if p.ReadResourceCalled {
|
|
|
|
t.Fatal("Resource should not have been refreshed")
|
|
|
|
}
|
2020-10-01 22:04:35 +02:00
|
|
|
|
|
|
|
for _, c := range plan.Changes.Resources {
|
|
|
|
if c.Action != plans.NoOp {
|
|
|
|
t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr)
|
|
|
|
}
|
|
|
|
}
|
2020-09-23 17:06:56 +02:00
|
|
|
}
|
2020-09-25 15:49:52 +02:00
|
|
|
|
|
|
|
func TestContext2Plan_dataInModuleDependsOn(t *testing.T) {
|
|
|
|
p := testProvider("test")
|
|
|
|
|
|
|
|
readDataSourceB := false
|
|
|
|
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
|
|
|
|
cfg := req.Config.AsValueMap()
|
|
|
|
foo := cfg["foo"].AsString()
|
|
|
|
|
|
|
|
cfg["id"] = cty.StringVal("ID")
|
|
|
|
cfg["foo"] = cty.StringVal("new")
|
|
|
|
|
|
|
|
if foo == "b" {
|
|
|
|
readDataSourceB = true
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.State = cty.ObjectVal(cfg)
|
|
|
|
return resp
|
|
|
|
}
|
|
|
|
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
module "a" {
|
|
|
|
source = "./mod_a"
|
|
|
|
}
|
|
|
|
|
|
|
|
module "b" {
|
|
|
|
source = "./mod_b"
|
|
|
|
depends_on = [module.a]
|
|
|
|
}`,
|
|
|
|
"mod_a/main.tf": `
|
|
|
|
data "test_data_source" "a" {
|
|
|
|
foo = "a"
|
|
|
|
}`,
|
|
|
|
"mod_b/main.tf": `
|
|
|
|
data "test_data_source" "b" {
|
|
|
|
foo = "b"
|
|
|
|
}`,
|
|
|
|
})
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2020-09-25 15:49:52 +02:00
|
|
|
assertNoErrors(t, diags)
|
|
|
|
|
|
|
|
// The change to data source a should not prevent data source b from being
|
|
|
|
// read.
|
|
|
|
if !readDataSourceB {
|
|
|
|
t.Fatal("data source b was not read during plan")
|
|
|
|
}
|
|
|
|
}
|
2020-10-28 19:51:04 +01:00
|
|
|
|
|
|
|
func TestContext2Plan_rpcDiagnostics(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
resource "test_instance" "a" {
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("test")
|
2020-10-28 22:38:01 +01:00
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
|
|
resp := testDiffFn(req)
|
|
|
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.SimpleWarning("don't frobble"))
|
|
|
|
return resp
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2020-10-28 19:51:04 +01:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Computed: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2020-10-28 19:51:04 +01:00
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2020-10-28 19:51:04 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.Err())
|
|
|
|
}
|
|
|
|
|
2020-10-28 22:38:01 +01:00
|
|
|
if len(diags) == 0 {
|
|
|
|
t.Fatal("expected warnings")
|
|
|
|
}
|
|
|
|
|
2020-10-28 19:51:04 +01:00
|
|
|
for _, d := range diags {
|
|
|
|
des := d.Description().Summary
|
2020-10-28 22:38:01 +01:00
|
|
|
if !strings.Contains(des, "frobble") {
|
|
|
|
t.Fatalf(`expected frobble, got %q`, des)
|
2020-10-28 19:51:04 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-03 23:42:03 +01:00
|
|
|
|
|
|
|
// ignore_changes needs to be re-applied to the planned value for provider
|
|
|
|
// using the LegacyTypeSystem
|
|
|
|
func TestContext2Plan_legacyProviderIgnoreChanges(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
resource "test_instance" "a" {
|
|
|
|
lifecycle {
|
|
|
|
ignore_changes = [data]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("test")
|
|
|
|
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
|
|
|
m := req.ProposedNewState.AsValueMap()
|
|
|
|
// this provider "hashes" the data attribute as bar
|
|
|
|
m["data"] = cty.StringVal("bar")
|
|
|
|
|
|
|
|
resp.PlannedState = cty.ObjectVal(m)
|
|
|
|
resp.LegacyTypeSystem = true
|
|
|
|
return resp
|
|
|
|
}
|
|
|
|
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2020-12-03 23:42:03 +01:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Computed: true},
|
|
|
|
"data": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2020-12-03 23:42:03 +01:00
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("test_instance.a").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"a","data":"foo"}`),
|
|
|
|
Dependencies: []addrs.ConfigResource{},
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
|
|
)
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2020-12-03 23:42:03 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range plan.Changes.Resources {
|
|
|
|
if c.Action != plans.NoOp {
|
|
|
|
t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-03 23:58:46 +01:00
|
|
|
|
|
|
|
func TestContext2Plan_validateIgnoreAll(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
resource "test_instance" "a" {
|
|
|
|
lifecycle {
|
|
|
|
ignore_changes = all
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("test")
|
2021-02-18 16:13:43 +01:00
|
|
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
2020-12-03 23:58:46 +01:00
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Computed: true},
|
|
|
|
"data": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-01-11 21:45:50 +01:00
|
|
|
})
|
2021-02-18 16:13:43 +01:00
|
|
|
p.ValidateResourceConfigFn = func(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
|
2020-12-03 23:58:46 +01:00
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
if req.TypeName == "test_instance" {
|
|
|
|
if !req.Config.GetAttr("id").IsNull() {
|
|
|
|
diags = diags.Append(errors.New("id cannot be set in config"))
|
|
|
|
}
|
|
|
|
}
|
2021-02-18 16:13:43 +01:00
|
|
|
return providers.ValidateResourceConfigResponse{
|
2020-12-03 23:58:46 +01:00
|
|
|
Diagnostics: diags,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("test_instance.a").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"a","data":"foo"}`),
|
|
|
|
Dependencies: []addrs.ConfigResource{},
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
|
|
)
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2020-12-03 23:58:46 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.Err())
|
|
|
|
}
|
|
|
|
}
|
2020-12-16 20:10:51 +01:00
|
|
|
|
|
|
|
func TestContext2Plan_dataRemovalNoProvider(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
resource "test_instance" "a" {
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("test")
|
|
|
|
|
|
|
|
state := states.NewState()
|
|
|
|
root := state.EnsureModule(addrs.RootModuleInstance)
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("test_instance.a").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"a","data":"foo"}`),
|
|
|
|
Dependencies: []addrs.ConfigResource{},
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
|
|
|
)
|
|
|
|
|
|
|
|
// the provider for this data source is no longer in the config, but that
|
|
|
|
// should not matter for state removal.
|
|
|
|
root.SetResourceInstanceCurrent(
|
|
|
|
mustResourceInstanceAddr("data.test_data_source.d").Resource,
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"d"}`),
|
|
|
|
Dependencies: []addrs.ConfigResource{},
|
|
|
|
},
|
|
|
|
mustProviderConfig(`provider["registry.terraform.io/local/test"]`),
|
|
|
|
)
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
// We still need to be able to locate the provider to decode the
|
|
|
|
// state, since we do not know during init that this provider is
|
|
|
|
// only used for an orphaned data source.
|
|
|
|
addrs.NewProvider("registry.terraform.io", "local", "test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2020-12-16 20:10:51 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.Err())
|
|
|
|
}
|
|
|
|
}
|
2020-12-17 18:41:34 +01:00
|
|
|
|
|
|
|
func TestContext2Plan_noSensitivityChange(t *testing.T) {
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
variable "sensitive_var" {
|
|
|
|
default = "hello"
|
|
|
|
sensitive = true
|
|
|
|
}
|
|
|
|
|
|
|
|
resource "test_resource" "foo" {
|
|
|
|
value = var.sensitive_var
|
|
|
|
sensitive_value = var.sensitive_var
|
|
|
|
}`,
|
|
|
|
})
|
|
|
|
|
|
|
|
p := testProvider("test")
|
|
|
|
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
})
|
|
|
|
state := states.BuildState(func(s *states.SyncState) {
|
|
|
|
s.SetResourceInstanceCurrent(
|
|
|
|
addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "test_resource",
|
|
|
|
Name: "foo",
|
|
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
|
|
&states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"foo", "value":"hello", "sensitive_value":"hello"}`),
|
|
|
|
AttrSensitivePaths: []cty.PathValueMarks{
|
|
|
|
{Path: cty.Path{cty.GetAttrStep{Name: "value"}}, Marks: cty.NewValueMarks(marks.Sensitive)},
|
|
|
|
{Path: cty.Path{cty.GetAttrStep{Name: "sensitive_value"}}, Marks: cty.NewValueMarks(marks.Sensitive)},
|
2020-12-17 18:41:34 +01:00
|
|
|
},
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
},
|
|
|
|
addrs.AbsProviderConfig{
|
|
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
|
|
Module: addrs.RootModule,
|
|
|
|
},
|
|
|
|
)
|
2020-12-17 18:41:34 +01:00
|
|
|
})
|
2021-12-21 01:38:52 +01:00
|
|
|
plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
|
2020-12-17 18:41:34 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range plan.Changes.Resources {
|
|
|
|
if c.Action != plans.NoOp {
|
|
|
|
t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-05 19:49:04 +01:00
|
|
|
|
|
|
|
func TestContext2Plan_variableCustomValidationsSensitive(t *testing.T) {
|
|
|
|
m := testModule(t, "validate-variable-custom-validations-child-sensitive")
|
|
|
|
|
|
|
|
p := testProvider("test")
|
|
|
|
ctx := testContext2(t, &ContextOpts{
|
|
|
|
Providers: map[addrs.Provider]providers.Factory{
|
|
|
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2021-01-05 19:49:04 +01:00
|
|
|
if !diags.HasErrors() {
|
|
|
|
t.Fatal("succeeded; want errors")
|
|
|
|
}
|
|
|
|
if got, want := diags.Err().Error(), `Invalid value for variable: Value must not be "nope".`; !strings.Contains(got, want) {
|
|
|
|
t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want)
|
|
|
|
}
|
|
|
|
}
|
2021-01-14 14:59:31 +01:00
|
|
|
|
|
|
|
func TestContext2Plan_nullOutputNoOp(t *testing.T) {
|
|
|
|
// this should always plan a NoOp change for the output
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
output "planned" {
|
|
|
|
value = false ? 1 : null
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{})
|
|
|
|
state := states.BuildState(func(s *states.SyncState) {
|
|
|
|
r := s.Module(addrs.RootModuleInstance)
|
|
|
|
r.SetOutputValue("planned", cty.NullVal(cty.DynamicPseudoType), false)
|
2021-01-14 14:59:31 +01:00
|
|
|
})
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
2021-01-14 14:59:31 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range plan.Changes.Outputs {
|
|
|
|
if c.Action != plans.NoOp {
|
|
|
|
t.Fatalf("expected no changes, got %s for %q", c.Action, c.Addr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-14 15:24:47 +01:00
|
|
|
|
|
|
|
func TestContext2Plan_createOutput(t *testing.T) {
|
|
|
|
// this should always plan a NoOp change for the output
|
|
|
|
m := testModuleInline(t, map[string]string{
|
|
|
|
"main.tf": `
|
|
|
|
output "planned" {
|
|
|
|
value = 1
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
|
core: Functional-style API for terraform.Context
Previously terraform.Context was built in an unfortunate way where all of
the data was provided up front in terraform.NewContext and then mutated
directly by subsequent operations. That made the data flow hard to follow,
commonly leading to bugs, and also meant that we were forced to take
various actions too early in terraform.NewContext, rather than waiting
until a more appropriate time during an operation.
This (enormous) commit changes terraform.Context so that its fields are
broadly just unchanging data about the execution context (current
workspace name, available plugins, etc) whereas the main data Terraform
works with arrives via individual method arguments and is returned in
return values.
Specifically, this means that terraform.Context no longer "has-a" config,
state, and "planned changes", instead holding on to those only temporarily
during an operation. The caller is responsible for propagating the outcome
of one step into the next step so that the data flow between operations is
actually visible.
However, since that's a change to the main entry points in the "terraform"
package, this commit also touches every file in the codebase which
interacted with those APIs. Most of the noise here is in updating tests
to take the same actions using the new API style, but this also affects
the main-code callers in the backends and in the command package.
My goal here was to refactor without changing observable behavior, but in
practice there are a couple externally-visible behavior variations here
that seemed okay in service of the broader goal:
- The "terraform graph" command is no longer hooked directly into the
core graph builders, because that's no longer part of the public API.
However, I did include a couple new Context functions whose contract
is to produce a UI-oriented graph, and _for now_ those continue to
return the physical graph we use for those operations. There's no
exported API for generating the "validate" and "eval" graphs, because
neither is particularly interesting in its own right, and so
"terraform graph" no longer supports those graph types.
- terraform.NewContext no longer has the responsibility for collecting
all of the provider schemas up front. Instead, we wait until we need
them. However, that means that some of our error messages now have a
slightly different shape due to unwinding through a differently-shaped
call stack. As of this commit we also end up reloading the schemas
multiple times in some cases, which is functionally acceptable but
likely represents a performance regression. I intend to rework this to
use caching, but I'm saving that for a later commit because this one is
big enough already.
The proximal reason for this change is to resolve the chicken/egg problem
whereby there was previously no single point where we could apply "moved"
statements to the previous run state before creating a plan. With this
change in place, we can now do that as part of Context.Plan, prior to
forking the input state into the three separate state artifacts we use
during planning.
However, this is at least the third project in a row where the previous
API design led to piling more functionality into terraform.NewContext and
then working around the incorrect order of operations that produces, so
I intend that by paying the cost/risk of this large diff now we can in
turn reduce the cost/risk of future projects that relate to our main
workflow actions.
2021-08-24 21:06:38 +02:00
|
|
|
ctx := testContext2(t, &ContextOpts{})
|
|
|
|
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
2021-01-14 15:24:47 +01:00
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range plan.Changes.Outputs {
|
|
|
|
if c.Action != plans.Create {
|
|
|
|
t.Fatalf("expected Create change, got %s for %q", c.Action, c.Addr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-21 18:43:50 +01:00
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// NOTE: Due to the size of this file, new tests should be added to
|
|
|
|
// context_plan2_test.go.
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|