2020-08-20 19:30:08 -05:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
|
|
"github.com/hashicorp/terraform/configs"
|
2020-12-04 08:16:26 -06:00
|
|
|
"github.com/hashicorp/terraform/configs/configschema"
|
|
|
|
"github.com/hashicorp/terraform/providers"
|
|
|
|
"github.com/hashicorp/terraform/states"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
2020-08-20 19:30:08 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestNodeAbstractResourceProvider(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
Addr addrs.ConfigResource
|
|
|
|
Config *configs.Resource
|
|
|
|
Want addrs.Provider
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
Addr: addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "null_resource",
|
|
|
|
Name: "baz",
|
|
|
|
}.InModule(addrs.RootModule),
|
|
|
|
Want: addrs.Provider{
|
|
|
|
Hostname: addrs.DefaultRegistryHost,
|
|
|
|
Namespace: "hashicorp",
|
|
|
|
Type: "null",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: addrs.Resource{
|
|
|
|
Mode: addrs.DataResourceMode,
|
|
|
|
Type: "terraform_remote_state",
|
|
|
|
Name: "baz",
|
|
|
|
}.InModule(addrs.RootModule),
|
|
|
|
Want: addrs.Provider{
|
|
|
|
// As a special case, the type prefix "terraform_" maps to
|
|
|
|
// the builtin provider, not the default one.
|
|
|
|
Hostname: addrs.BuiltInProviderHost,
|
|
|
|
Namespace: addrs.BuiltInProviderNamespace,
|
|
|
|
Type: "terraform",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "null_resource",
|
|
|
|
Name: "baz",
|
|
|
|
}.InModule(addrs.RootModule),
|
|
|
|
Config: &configs.Resource{
|
|
|
|
// Just enough configs.Resource for the Provider method. Not
|
|
|
|
// actually valid for general use.
|
|
|
|
Provider: addrs.Provider{
|
|
|
|
Hostname: addrs.DefaultRegistryHost,
|
|
|
|
Namespace: "awesomecorp",
|
|
|
|
Type: "happycloud",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// The config overrides the default behavior.
|
|
|
|
Want: addrs.Provider{
|
|
|
|
Hostname: addrs.DefaultRegistryHost,
|
|
|
|
Namespace: "awesomecorp",
|
|
|
|
Type: "happycloud",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Addr: addrs.Resource{
|
|
|
|
Mode: addrs.DataResourceMode,
|
|
|
|
Type: "terraform_remote_state",
|
|
|
|
Name: "baz",
|
|
|
|
}.InModule(addrs.RootModule),
|
|
|
|
Config: &configs.Resource{
|
|
|
|
// Just enough configs.Resource for the Provider method. Not
|
|
|
|
// actually valid for general use.
|
|
|
|
Provider: addrs.Provider{
|
|
|
|
Hostname: addrs.DefaultRegistryHost,
|
|
|
|
Namespace: "awesomecorp",
|
|
|
|
Type: "happycloud",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// The config overrides the default behavior.
|
|
|
|
Want: addrs.Provider{
|
|
|
|
Hostname: addrs.DefaultRegistryHost,
|
|
|
|
Namespace: "awesomecorp",
|
|
|
|
Type: "happycloud",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
var name string
|
|
|
|
if test.Config != nil {
|
|
|
|
name = fmt.Sprintf("%s with configured %s", test.Addr, test.Config.Provider)
|
|
|
|
} else {
|
|
|
|
name = fmt.Sprintf("%s with no configuration", test.Addr)
|
|
|
|
}
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
node := &NodeAbstractResource{
|
|
|
|
// Just enough NodeAbstractResource for the Provider function.
|
|
|
|
// (This would not be valid for some other functions.)
|
|
|
|
Addr: test.Addr,
|
|
|
|
Config: test.Config,
|
|
|
|
}
|
|
|
|
got := node.Provider()
|
|
|
|
if got != test.Want {
|
|
|
|
t.Errorf("wrong result\naddr: %s\nconfig: %#v\ngot: %s\nwant: %s", test.Addr, test.Config, got, test.Want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-12-04 08:16:26 -06:00
|
|
|
|
|
|
|
func TestNodeAbstractResource_ReadResourceInstanceState(t *testing.T) {
|
|
|
|
mockProvider := mockProviderWithResourceTypeSchema("aws_instance", &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
tests := map[string]struct {
|
|
|
|
State *states.State
|
|
|
|
Node *NodeAbstractResource
|
|
|
|
ExpectedInstanceId string
|
|
|
|
}{
|
|
|
|
"ReadState gets primary instance state": {
|
|
|
|
State: states.BuildState(func(s *states.SyncState) {
|
|
|
|
providerAddr := addrs.AbsProviderConfig{
|
|
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
|
|
Module: addrs.RootModule,
|
|
|
|
}
|
|
|
|
oneAddr := addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "aws_instance",
|
|
|
|
Name: "bar",
|
|
|
|
}.Absolute(addrs.RootModuleInstance)
|
|
|
|
s.SetResourceProvider(oneAddr, providerAddr)
|
|
|
|
s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.NoKey), &states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
|
|
}, providerAddr)
|
|
|
|
}),
|
|
|
|
Node: &NodeAbstractResource{
|
|
|
|
Addr: mustConfigResourceAddr("aws_instance.bar"),
|
|
|
|
ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
},
|
|
|
|
ExpectedInstanceId: "i-abc123",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, test := range tests {
|
|
|
|
t.Run(k, func(t *testing.T) {
|
|
|
|
ctx := new(MockEvalContext)
|
|
|
|
ctx.StateState = test.State.SyncWrapper()
|
|
|
|
ctx.PathPath = addrs.RootModuleInstance
|
|
|
|
ctx.ProviderSchemaSchema = mockProvider.GetSchemaReturn
|
|
|
|
ctx.ProviderProvider = providers.Interface(mockProvider)
|
|
|
|
|
Eval() Refactor: Plan Edition (#27177)
* terraforn: refactor EvalRefresh
EvalRefresh.Eval(ctx) is now Refresh(evalRefreshReqest, ctx). While none
of the inner logic of the function has changed, it now returns a
states.ResourceInstanceObject instead of updating a pointer. This is a
human-centric change, meant to make the logic flow (in the calling
functions) easier to follow.
* terraform: refactor EvalReadDataPlan and Apply
This is a very minor refactor that removes the (currently) redundant
types EvalReadDataPlan and EvalReadDataApply in favor of using
EvalReadData with a Plan and Apply functions.
This is in effect an aesthetic change; since there is no longer an
Eval() abstraction we can rename functions to make their functionality
as obvious as possible.
* terraform: refactor EvalCheckPlannedChange
EvalCheckPlannedChange was only used by NodeApplyableResourceInstance
and has been refactored into a method on that type called
checkPlannedChange.
* terraform: refactor EvalDiff.Eval
EvalDiff.Eval is now a method on NodeResourceAbstracted called Plan
which takes as a parameter an EvalPlanRequest. Instead of updating
pointers it returns a new plan and state.
I removed as many redundant fields from the original EvalDiff struct as
possible.
* terraform: refactor EvalReduceDiff
EvalReduceDiff is now reducePlan, a regular function (without a method)
that returns a value.
* terraform: refactor EvalDiffDestroy
EvalDiffDestroy.Eval is now NodeAbstractResourceInstance.PlanDestroy
which takes ctx, state and optional DeposedKey and returns a change.
I've removed the state return value since it was only ever returning a
nil state.
* terraform: refactor EvalWriteDiff
EvalWriteDiff.Eval is now NodeAbstractResourceInstance.WriteChange.
* rename files to something more logical
* terrafrom: refresh refactor, continued!
I had originally made Refresh a stand-alone function since it was
(obnoxiously) called from a graphNodeImportStateSub, but after some
(greatly appreciated) prompting in the PR I instead made it a method on
the NodeAbstractResourceInstance, in keeping with the other refactored
eval nodes, and then built a NodeAbstractResourceInstance inside import.
Since I did that I could also remove my duplicated 'writeState' code
inside graphNodeImportStateSub and use n.writeResourceInstanceState, so
double thanks!
* unexport eval methods
* re-refactor Plan, it made more sense on NodeAbstractResourceInstance. Sorry
* Remove uninformative `Eval`s from EvalReadData, consolidate to a single
file, and rename file to match function names.
* manual rebase
2020-12-08 07:50:30 -06:00
|
|
|
got, err := test.Node.readResourceInstanceState(ctx, test.Node.Addr.Resource.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance))
|
2020-12-04 08:16:26 -06:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("[%s] Got err: %#v", k, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := test.ExpectedInstanceId
|
|
|
|
|
|
|
|
if !(got != nil && got.Value.GetAttr("id") == cty.StringVal(expected)) {
|
|
|
|
t.Fatalf("[%s] Expected output with ID %#v, got: %#v", k, expected, got)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNodeAbstractResource_ReadResourceInstanceStateDeposed(t *testing.T) {
|
|
|
|
mockProvider := mockProviderWithResourceTypeSchema("aws_instance", &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {
|
|
|
|
Type: cty.String,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
tests := map[string]struct {
|
|
|
|
State *states.State
|
|
|
|
Node *NodeAbstractResource
|
|
|
|
ExpectedInstanceId string
|
|
|
|
}{
|
|
|
|
"ReadStateDeposed gets deposed instance": {
|
|
|
|
State: states.BuildState(func(s *states.SyncState) {
|
|
|
|
providerAddr := addrs.AbsProviderConfig{
|
|
|
|
Provider: addrs.NewDefaultProvider("aws"),
|
|
|
|
Module: addrs.RootModule,
|
|
|
|
}
|
|
|
|
oneAddr := addrs.Resource{
|
|
|
|
Mode: addrs.ManagedResourceMode,
|
|
|
|
Type: "aws_instance",
|
|
|
|
Name: "bar",
|
|
|
|
}.Absolute(addrs.RootModuleInstance)
|
|
|
|
s.SetResourceProvider(oneAddr, providerAddr)
|
|
|
|
s.SetResourceInstanceDeposed(oneAddr.Instance(addrs.NoKey), states.DeposedKey("00000001"), &states.ResourceInstanceObjectSrc{
|
|
|
|
Status: states.ObjectReady,
|
|
|
|
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
|
|
|
}, providerAddr)
|
|
|
|
}),
|
|
|
|
Node: &NodeAbstractResource{
|
|
|
|
Addr: mustConfigResourceAddr("aws_instance.bar"),
|
|
|
|
ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
|
|
|
},
|
|
|
|
ExpectedInstanceId: "i-abc123",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for k, test := range tests {
|
|
|
|
t.Run(k, func(t *testing.T) {
|
|
|
|
ctx := new(MockEvalContext)
|
|
|
|
ctx.StateState = test.State.SyncWrapper()
|
|
|
|
ctx.PathPath = addrs.RootModuleInstance
|
|
|
|
ctx.ProviderSchemaSchema = mockProvider.GetSchemaReturn
|
|
|
|
ctx.ProviderProvider = providers.Interface(mockProvider)
|
|
|
|
|
|
|
|
key := states.DeposedKey("00000001") // shim from legacy state assigns 0th deposed index this key
|
|
|
|
|
Mildwonkey/eval apply (#27222)
* rename files for consistency with contents
* terraform: refactor EvalValidateSelfref
The EvalValidateSelfref eval node implementation was removed in favor of a regular function.
* terraform: refactor EvalValidateProvisioner
EvalValidateProvisioner is now a method on NodeValidatableResource.
* terraform: refactor EvalValidateResource
EvalValidateResource is now a method on NodeValidatableResource, and the
functions called by (the new) validateResource are now standalone
functions.
This particular refactor gets the prize for "most complicated test
refactoring".
* terraform: refactor EvalMaybeTainted
EvalMaybeTainted was a relatively simple operation which never returned
an error, so I've refactored it into a plain function and moved it into
the only file its called from.
* terraform: eval-related cleanup
De-exported preApplyHook, which got missed in my general cleanup sweeps.
Removed resourceHasUserVisibleApply in favor of moving the logic inline
- it was a single-line check so calling the function was (nearly) as
much code as just checking if the resource was managed.
* terraform: refactor EvalApplyProvisioners
EvalApplyProvisioners.Eval is now a method on
NodeResourceAbstractInstance. There were two "apply"ish functions, so I
named the first "evalApplyProvisioners" since it mainly determined if
provisioners should be run before passing off execution to
applyProvisioners.
* terraform: refactor EvalApply
EvalApply is now a method on NodeAbstractResourceInstance. This was one
of the trickier Eval()s to refactor, and my goal was to change as little
as possible to avoid unintended side effects.
One notable change: there was a createNew boolean that was only used in
NodeApplyableResourceInstance.managedResourceExecute, and that boolean
was populated from the change (which was available from
managedResourceExecute), so I removed it from apply entirely. Out of an
abundance of caution I assigned the value to createNew in (roughtly) the same spot,
in case I was missing some place where the change might get modified.
TODO: Destroy nodes passed nil configs into apply, and I am curious if
we can get the same functionality by checking if the planned change is a
destroy, instead of passing a config into apply. That felt too risky for
this refactor but it is something I would like to explore at a future
point.
There are also a few updates to log output in this PR, since I spent
some time staring at logs and noticed various spots I missed.
2020-12-10 07:05:53 -06:00
|
|
|
got, err := test.Node.readResourceInstanceStateDeposed(ctx, test.Node.Addr.Resource.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), key)
|
2020-12-04 08:16:26 -06:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("[%s] Got err: %#v", k, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
expected := test.ExpectedInstanceId
|
|
|
|
|
|
|
|
if !(got != nil && got.Value.GetAttr("id") == cty.StringVal(expected)) {
|
|
|
|
t.Fatalf("[%s] Expected output with ID %#v, got: %#v", k, expected, got)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|