mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Add exclude flag support (#1900)
Signed-off-by: RLRabinowitz <rlrabinowitz2@gmail.com>
This commit is contained in:
parent
e802b23200
commit
3d4bf29c56
@ -35,6 +35,9 @@ linters-settings:
|
||||
gocognit:
|
||||
min-complexity: 50
|
||||
|
||||
goconst:
|
||||
ignore-tests: true # Is documented to be the default behaviour, but that doesn't seem to be the case
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: (.+)_test.go
|
||||
|
@ -5,6 +5,7 @@ UPGRADE NOTES:
|
||||
* Using the `ghcr.io/opentofu/opentofu` image as a base image for custom images is deprecated and this will be removed in OpenTofu 1.10. Please see https://opentofu.org/docs/intro/install/docker/ for instructions on building your own image.
|
||||
|
||||
NEW FEATURES:
|
||||
* Add support for `-exclude` flag, to allow excluding specific resources and modules with resource targeting ([#426](https://github.com/opentofu/opentofu/issues/426))
|
||||
|
||||
ENHANCEMENTS:
|
||||
* State encryption key providers now support customizing the metadata key via `encrypted_metadata_alias` ([#1605](https://github.com/opentofu/opentofu/issues/1605))
|
||||
|
@ -282,6 +282,7 @@ type Operation struct {
|
||||
PlanMode plans.Mode
|
||||
AutoApprove bool
|
||||
Targets []addrs.Targetable
|
||||
Excludes []addrs.Targetable
|
||||
ForceReplace []addrs.AbsResourceInstance
|
||||
// Injected by the command creating the operation (plan/apply/refresh/etc...)
|
||||
Variables map[string]UnparsedVariableValue
|
||||
|
@ -203,6 +203,7 @@ func (b *Local) localRunDirect(op *backend.Operation, run *backend.LocalRun, cor
|
||||
planOpts := &tofu.PlanOpts{
|
||||
Mode: op.PlanMode,
|
||||
Targets: op.Targets,
|
||||
Excludes: op.Excludes,
|
||||
ForceReplace: op.ForceReplace,
|
||||
SetVariables: variables,
|
||||
SkipRefresh: op.Type != backend.OperationTypeRefresh && !op.PlanRefresh,
|
||||
|
@ -161,6 +161,14 @@ func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operati
|
||||
}
|
||||
}
|
||||
|
||||
if len(op.Excludes) != 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"-exclude option is not supported",
|
||||
"The -exclude option is not currently supported for remote plans.",
|
||||
))
|
||||
}
|
||||
|
||||
// Return if there are any errors.
|
||||
if diags.HasErrors() {
|
||||
return nil, diags.Err()
|
||||
|
@ -466,6 +466,45 @@ func TestRemote_applyWithTarget(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Applying with an exclude flag should error
|
||||
func TestRemote_applyWithExclude(t *testing.T) {
|
||||
b, bCleanup := testBackendDefault(t)
|
||||
defer bCleanup()
|
||||
|
||||
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
|
||||
defer configCleanup()
|
||||
|
||||
addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
|
||||
|
||||
op.Workspace = backend.DefaultStateName
|
||||
op.Excludes = []addrs.Targetable{addr}
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting operation: %v", err)
|
||||
}
|
||||
|
||||
<-run.Done()
|
||||
output := done(t)
|
||||
if run.Result == backend.OperationSuccess {
|
||||
t.Fatal("expected apply operation to fail")
|
||||
}
|
||||
if !run.PlanEmpty {
|
||||
t.Fatalf("expected plan to be empty")
|
||||
}
|
||||
|
||||
errOutput := output.Stderr()
|
||||
if !strings.Contains(errOutput, "-exclude option is not supported") {
|
||||
t.Fatalf("expected -exclude option is not supported error, got: %v", errOutput)
|
||||
}
|
||||
|
||||
stateMgr, _ := b.StateMgr(backend.DefaultStateName)
|
||||
// An error suggests that the state was not unlocked after apply
|
||||
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
|
||||
t.Fatalf("unexpected error locking state after failed apply: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemote_applyWithTargetIncompatibleAPIVersion(t *testing.T) {
|
||||
b, bCleanup := testBackendDefault(t)
|
||||
defer bCleanup()
|
||||
|
@ -27,8 +27,9 @@ func (b *Remote) LocalRun(op *backend.Operation) (*backend.LocalRun, statemgr.Fu
|
||||
var diags tfdiags.Diagnostics
|
||||
ret := &backend.LocalRun{
|
||||
PlanOpts: &tofu.PlanOpts{
|
||||
Mode: op.PlanMode,
|
||||
Targets: op.Targets,
|
||||
Mode: op.PlanMode,
|
||||
Targets: op.Targets,
|
||||
Excludes: op.Excludes,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -128,6 +128,14 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio
|
||||
}
|
||||
}
|
||||
|
||||
if len(op.Excludes) != 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"-exclude option is not supported",
|
||||
"The -exclude option is not currently supported for remote plans.",
|
||||
))
|
||||
}
|
||||
|
||||
if !op.PlanRefresh {
|
||||
desiredAPIVersion, _ := version.NewVersion("2.4")
|
||||
|
||||
|
@ -537,6 +537,39 @@ func TestRemote_planWithTargetIncompatibleAPIVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Planning with an exclude flag should error
|
||||
func TestRemote_planWithExclude(t *testing.T) {
|
||||
b, bCleanup := testBackendDefault(t)
|
||||
defer bCleanup()
|
||||
|
||||
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
||||
defer configCleanup()
|
||||
|
||||
addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
|
||||
|
||||
op.Workspace = backend.DefaultStateName
|
||||
op.Excludes = []addrs.Targetable{addr}
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting operation: %v", err)
|
||||
}
|
||||
|
||||
<-run.Done()
|
||||
output := done(t)
|
||||
if run.Result == backend.OperationSuccess {
|
||||
t.Fatal("expected apply operation to fail")
|
||||
}
|
||||
if !run.PlanEmpty {
|
||||
t.Fatalf("expected plan to be empty")
|
||||
}
|
||||
|
||||
errOutput := output.Stderr()
|
||||
if !strings.Contains(errOutput, "-exclude option is not supported") {
|
||||
t.Fatalf("expected -exclude option is not supported error, got: %v", errOutput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemote_planWithReplace(t *testing.T) {
|
||||
b, bCleanup := testBackendDefault(t)
|
||||
defer bCleanup()
|
||||
|
@ -330,4 +330,4 @@ func TestResourceProvisioner_nullsInOptionals(t *testing.T) {
|
||||
|
||||
func normaliseNewlines(input string) string {
|
||||
return strings.ReplaceAll(input, "\r\n", "\n")
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,14 @@ func (b *Cloud) opApply(stopCtx, cancelCtx context.Context, op *backend.Operatio
|
||||
))
|
||||
}
|
||||
|
||||
if len(op.Excludes) != 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"-exclude option is not supported",
|
||||
"The -exclude option is not currently supported for remote plans.",
|
||||
))
|
||||
}
|
||||
|
||||
// Return if there are any errors.
|
||||
if diags.HasErrors() {
|
||||
return nil, diags.Err()
|
||||
|
@ -623,6 +623,45 @@ func TestCloud_applyWithTarget(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Applying with an exclude flag should error
|
||||
func TestCloud_applyWithExclude(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
||||
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
|
||||
defer configCleanup()
|
||||
|
||||
addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
|
||||
|
||||
op.Workspace = testBackendSingleWorkspaceName
|
||||
op.Excludes = []addrs.Targetable{addr}
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting operation: %v", err)
|
||||
}
|
||||
|
||||
<-run.Done()
|
||||
output := done(t)
|
||||
if run.Result == backend.OperationSuccess {
|
||||
t.Fatal("expected apply operation to fail")
|
||||
}
|
||||
if !run.PlanEmpty {
|
||||
t.Fatalf("expected plan to be empty")
|
||||
}
|
||||
|
||||
errOutput := output.Stderr()
|
||||
if !strings.Contains(errOutput, "-exclude option is not supported") {
|
||||
t.Fatalf("expected -exclude option is not supported error, got: %v", errOutput)
|
||||
}
|
||||
|
||||
stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
|
||||
// An error suggests that the state was not unlocked after apply
|
||||
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
|
||||
t.Fatalf("unexpected error locking state after failed apply: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloud_applyWithReplace(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
@ -27,8 +27,9 @@ func (b *Cloud) LocalRun(op *backend.Operation) (*backend.LocalRun, statemgr.Ful
|
||||
var diags tfdiags.Diagnostics
|
||||
ret := &backend.LocalRun{
|
||||
PlanOpts: &tofu.PlanOpts{
|
||||
Mode: op.PlanMode,
|
||||
Targets: op.Targets,
|
||||
Mode: op.PlanMode,
|
||||
Targets: op.Targets,
|
||||
Excludes: op.Excludes,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,14 @@ func (b *Cloud) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operation
|
||||
))
|
||||
}
|
||||
|
||||
if len(op.Excludes) != 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"-exclude option is not supported",
|
||||
"The -exclude option is not currently supported for remote plans.",
|
||||
))
|
||||
}
|
||||
|
||||
if len(op.GenerateConfigOut) > 0 {
|
||||
diags = diags.Append(genconfig.ValidateTargetFile(op.GenerateConfigOut))
|
||||
}
|
||||
|
@ -565,6 +565,39 @@ func TestCloud_planWithTarget(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Planning with an exclude flag should error
|
||||
func TestCloud_planWithExclude(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
||||
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
|
||||
defer configCleanup()
|
||||
|
||||
addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
|
||||
|
||||
op.Workspace = testBackendSingleWorkspaceName
|
||||
op.Excludes = []addrs.Targetable{addr}
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting operation: %v", err)
|
||||
}
|
||||
|
||||
<-run.Done()
|
||||
output := done(t)
|
||||
if run.Result == backend.OperationSuccess {
|
||||
t.Fatal("expected apply operation to fail")
|
||||
}
|
||||
if !run.PlanEmpty {
|
||||
t.Fatalf("expected plan to be empty")
|
||||
}
|
||||
|
||||
errOutput := output.Stderr()
|
||||
if !strings.Contains(errOutput, "-exclude option is not supported") {
|
||||
t.Fatalf("expected -exclude option is not supported error, got: %v", errOutput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloud_planWithReplace(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
@ -284,6 +284,7 @@ func (c *ApplyCommand) OperationRequest(
|
||||
opReq.PlanFile = planFile
|
||||
opReq.PlanRefresh = args.Refresh
|
||||
opReq.Targets = args.Targets
|
||||
opReq.Excludes = args.Excludes
|
||||
opReq.ForceReplace = args.ForceReplace
|
||||
opReq.Type = backend.OperationTypeApply
|
||||
opReq.View = view.Operation()
|
||||
|
@ -7,6 +7,7 @@ package command
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -386,290 +387,222 @@ func TestApply_destroyPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Config with multiple resources with dependencies, targeting destroy of a
|
||||
// root node, expecting all other resources to be destroyed due to
|
||||
// dependencies.
|
||||
func TestApply_destroyTargetedDependencies(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath("apply-destroy-targeted"), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
originalState := states.BuildState(func(s *states.SyncState) {
|
||||
s.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"id":"i-ab123"}`),
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
)
|
||||
s.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_load_balancer",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
||||
Dependencies: []addrs.ConfigResource{mustResourceAddr("test_instance.foo")},
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
)
|
||||
})
|
||||
statePath := testStateFile(t, originalState)
|
||||
|
||||
p := testProvider()
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_instance": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Computed: true},
|
||||
func TestApply_targetedDestroy(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
flagName string
|
||||
flagValue string
|
||||
wantStatFunc func(s *states.SyncState)
|
||||
}{
|
||||
{
|
||||
// Config with multiple resources with dependencies, targeting destroy of a
|
||||
// leaf node, expecting the other resources to remain.
|
||||
name: "Targeted Destroy",
|
||||
flagName: "-target",
|
||||
flagValue: "test_load_balancer.foo",
|
||||
wantStatFunc: func(s *states.SyncState) {
|
||||
s.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"id":"i-ab123"}`),
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
},
|
||||
},
|
||||
"test_load_balancer": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Computed: true},
|
||||
"instances": {Type: cty.List(cty.String), Optional: true},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// Config with multiple resources with dependencies, targeting destroy of a
|
||||
// root node, expecting all other resources to be destroyed due to
|
||||
// dependencies.
|
||||
name: "Targeted Destroy of root",
|
||||
flagName: "-target",
|
||||
flagValue: "test_instance.foo",
|
||||
// No wantStatFunc, expecting empty state
|
||||
},
|
||||
{
|
||||
// Config with multiple resources with dependencies, destroy excluding a
|
||||
// non-existent node, expecting all other resources to be destroyed.
|
||||
name: "Targeted Destroy excluding non-existent resource",
|
||||
flagName: "-exclude",
|
||||
flagValue: "test_load_balancer.foo-nonexistent",
|
||||
// No wantStatFunc, expecting empty state
|
||||
},
|
||||
{
|
||||
// Config with multiple resources with dependencies, destroy excluding the root node,
|
||||
// expecting other resources to remain
|
||||
name: "Targeted Destroy with exclude of root",
|
||||
flagName: "-exclude",
|
||||
flagValue: "test_instance.foo",
|
||||
wantStatFunc: func(s *states.SyncState) {
|
||||
s.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"id":"i-ab123"}`),
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
|
||||
view, done := testView(t)
|
||||
c := &ApplyCommand{
|
||||
Destroy: true,
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := filepath.Join(t.TempDir(), t.Name())
|
||||
testCopyDir(t, testFixturePath("apply-destroy-targeted"), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
// Run the apply command pointing to our existing state
|
||||
args := []string{
|
||||
"-auto-approve",
|
||||
"-target", "test_instance.foo",
|
||||
"-state", statePath,
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Log(output.Stdout())
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
originalState := states.BuildState(func(s *states.SyncState) {
|
||||
s.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"id":"i-ab123"}`),
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
)
|
||||
s.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_load_balancer",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
||||
Dependencies: []addrs.ConfigResource{mustResourceAddr("test_instance.foo")},
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
// Verify a new state exists
|
||||
if _, err := os.Stat(statePath); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
statePath := testStateFile(t, originalState)
|
||||
|
||||
f, err := os.Open(statePath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
stateFile, err := statefile.Read(f, encryption.StateEncryptionDisabled())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if stateFile == nil || stateFile.State == nil {
|
||||
t.Fatal("state should not be nil")
|
||||
}
|
||||
|
||||
spew.Config.DisableMethods = true
|
||||
if !stateFile.State.Empty() {
|
||||
t.Fatalf("unexpected final state\ngot: %s\nwant: empty state", spew.Sdump(stateFile.State))
|
||||
}
|
||||
|
||||
// Should have a backup file
|
||||
f, err = os.Open(statePath + DefaultBackupExtension)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
backupStateFile, err := statefile.Read(f, encryption.StateEncryptionDisabled())
|
||||
f.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actualStr := strings.TrimSpace(backupStateFile.State.String())
|
||||
expectedStr := strings.TrimSpace(originalState.String())
|
||||
if actualStr != expectedStr {
|
||||
t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", actualStr, expectedStr)
|
||||
}
|
||||
}
|
||||
|
||||
// Config with multiple resources with dependencies, targeting destroy of a
|
||||
// leaf node, expecting the other resources to remain.
|
||||
func TestApply_destroyTargeted(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath("apply-destroy-targeted"), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
originalState := states.BuildState(func(s *states.SyncState) {
|
||||
s.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"id":"i-ab123"}`),
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
)
|
||||
s.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_load_balancer",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
||||
Dependencies: []addrs.ConfigResource{mustResourceAddr("test_instance.foo")},
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
)
|
||||
})
|
||||
wantState := states.BuildState(func(s *states.SyncState) {
|
||||
s.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test_instance",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{"id":"i-ab123"}`),
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
Module: addrs.RootModule,
|
||||
},
|
||||
)
|
||||
})
|
||||
statePath := testStateFile(t, originalState)
|
||||
|
||||
p := testProvider()
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_instance": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Computed: true},
|
||||
p := testProvider()
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_instance": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Computed: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
"test_load_balancer": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Computed: true},
|
||||
"instances": {Type: cty.List(cty.String), Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"test_load_balancer": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Computed: true},
|
||||
"instances": {Type: cty.List(cty.String), Optional: true},
|
||||
},
|
||||
}
|
||||
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
|
||||
view, done := testView(t)
|
||||
c := &ApplyCommand{
|
||||
Destroy: true,
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view, done := testView(t)
|
||||
c := &ApplyCommand{
|
||||
Destroy: true,
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
// Run the apply command pointing to our existing state
|
||||
args := []string{
|
||||
"-auto-approve",
|
||||
tc.flagName, tc.flagValue,
|
||||
"-state", statePath,
|
||||
}
|
||||
|
||||
// Run the apply command pointing to our existing state
|
||||
args := []string{
|
||||
"-auto-approve",
|
||||
"-target", "test_load_balancer.foo",
|
||||
"-state", statePath,
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Log(output.Stdout())
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Log(output.Stdout())
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
|
||||
// Verify a new state exists
|
||||
if _, err := os.Stat(statePath); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
// Verify a new state exists
|
||||
if _, err := os.Stat(statePath); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
f, err := os.Open(statePath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
f, err := os.Open(statePath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
stateFile, err := statefile.Read(f, encryption.StateEncryptionDisabled())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if stateFile == nil || stateFile.State == nil {
|
||||
t.Fatal("state should not be nil")
|
||||
}
|
||||
stateFile, err := statefile.Read(f, encryption.StateEncryptionDisabled())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if stateFile == nil || stateFile.State == nil {
|
||||
t.Fatal("state should not be nil")
|
||||
}
|
||||
|
||||
actualStr := strings.TrimSpace(stateFile.State.String())
|
||||
expectedStr := strings.TrimSpace(wantState.String())
|
||||
if actualStr != expectedStr {
|
||||
t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", actualStr, expectedStr)
|
||||
}
|
||||
if tc.wantStatFunc != nil {
|
||||
wantState := states.BuildState(tc.wantStatFunc)
|
||||
actualStr := strings.TrimSpace(stateFile.State.String())
|
||||
expectedStr := strings.TrimSpace(wantState.String())
|
||||
if actualStr != expectedStr {
|
||||
t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", actualStr, expectedStr)
|
||||
}
|
||||
} else if !stateFile.State.Empty() {
|
||||
// Missing wantStatFunc means expected empty state
|
||||
t.Fatalf("unexpected final state\ngot: %s\nwant: empty state", spew.Sdump(stateFile.State))
|
||||
}
|
||||
|
||||
// Should have a backup file
|
||||
f, err = os.Open(statePath + DefaultBackupExtension)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
// Should have a backup file
|
||||
f, err = os.Open(statePath + DefaultBackupExtension)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
backupStateFile, err := statefile.Read(f, encryption.StateEncryptionDisabled())
|
||||
f.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
backupStateFile, err := statefile.Read(f, encryption.StateEncryptionDisabled())
|
||||
f.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
backupActualStr := strings.TrimSpace(backupStateFile.State.String())
|
||||
backupExpectedStr := strings.TrimSpace(originalState.String())
|
||||
if backupActualStr != backupExpectedStr {
|
||||
t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", backupActualStr, backupExpectedStr)
|
||||
backupActualStr := strings.TrimSpace(backupStateFile.State.String())
|
||||
backupExpectedStr := strings.TrimSpace(originalState.String())
|
||||
if backupActualStr != backupExpectedStr {
|
||||
t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", backupActualStr, backupExpectedStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2020,6 +2020,94 @@ func TestApply_targetFlagsDiags(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Config with multiple resources, targeted apply with exclude
|
||||
func TestApply_excluded(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath("apply-excluded"), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
p := testProvider()
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_instance": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Computed: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
|
||||
view, done := testView(t)
|
||||
c := &ApplyCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-auto-approve",
|
||||
"-exclude", "test_instance.bar",
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
|
||||
if got, want := output.Stdout(), "3 added, 0 changed, 0 destroyed"; !strings.Contains(got, want) {
|
||||
t.Fatalf("bad change summary, want %q, got:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostics for invalid -exclude flags
|
||||
func TestApply_excludeFlagsDiags(t *testing.T) {
|
||||
testCases := map[string]string{
|
||||
"test_instance.": "Dot must be followed by attribute name.",
|
||||
"test_instance": "Resource specification must include a resource type and name.",
|
||||
}
|
||||
|
||||
for exclude, wantDiag := range testCases {
|
||||
t.Run(exclude, func(t *testing.T) {
|
||||
td := testTempDir(t)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
view, done := testView(t)
|
||||
c := &ApplyCommand{
|
||||
Meta: Meta{
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-auto-approve",
|
||||
"-exclude", exclude,
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 1 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
|
||||
got := output.Stderr()
|
||||
if !strings.Contains(got, exclude) {
|
||||
t.Fatalf("bad error output, want %q, got:\n%s", exclude, got)
|
||||
}
|
||||
if !strings.Contains(got, wantDiag) {
|
||||
t.Fatalf("bad error output, want %q, got:\n%s", wantDiag, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApply_replace(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath("apply-replace"), td)
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
@ -202,9 +204,11 @@ func TestParseApply_targets(t *testing.T) {
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, diags := ParseApply(tc.args)
|
||||
if len(diags) > 0 {
|
||||
if tc.wantErr == "" {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
if tc.wantErr == "" && len(diags) > 0 {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
} else if tc.wantErr != "" {
|
||||
if len(diags) == 0 {
|
||||
t.Fatalf("expected diags but got none")
|
||||
} else if got := diags.Err().Error(); !strings.Contains(got, tc.wantErr) {
|
||||
t.Fatalf("wrong diags\n got: %s\nwant: %s", got, tc.wantErr)
|
||||
}
|
||||
@ -216,6 +220,81 @@ func TestParseApply_targets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseApply_excludes(t *testing.T) {
|
||||
foobarbaz, _ := addrs.ParseTargetStr("foo_bar.baz")
|
||||
boop, _ := addrs.ParseTargetStr("module.boop")
|
||||
testCases := map[string]struct {
|
||||
args []string
|
||||
want []addrs.Targetable
|
||||
wantErr string
|
||||
}{
|
||||
"no excludes by default": {
|
||||
args: nil,
|
||||
want: nil,
|
||||
},
|
||||
"one exclude": {
|
||||
args: []string{"-exclude=foo_bar.baz"},
|
||||
want: []addrs.Targetable{foobarbaz.Subject},
|
||||
},
|
||||
"two excludes": {
|
||||
args: []string{"-exclude=foo_bar.baz", "-exclude", "module.boop"},
|
||||
want: []addrs.Targetable{foobarbaz.Subject, boop.Subject},
|
||||
},
|
||||
"invalid traversal": {
|
||||
args: []string{"-exclude=foo."},
|
||||
want: nil,
|
||||
wantErr: "Dot must be followed by attribute name",
|
||||
},
|
||||
"invalid target": {
|
||||
args: []string{"-exclude=data[0].foo"},
|
||||
want: nil,
|
||||
wantErr: "A data source name is required",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, diags := ParseApply(tc.args)
|
||||
if tc.wantErr == "" && len(diags) > 0 {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
} else if tc.wantErr != "" {
|
||||
if len(diags) == 0 {
|
||||
t.Fatalf("expected diags but got none")
|
||||
} else if got := diags.Err().Error(); !strings.Contains(got, tc.wantErr) {
|
||||
t.Fatalf("wrong diags\n got: %s\nwant: %s", got, tc.wantErr)
|
||||
}
|
||||
}
|
||||
if !cmp.Equal(got.Operation.Excludes, tc.want) {
|
||||
t.Fatalf("unexpected result\n%s", cmp.Diff(got.Operation.Targets, tc.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseApply_excludeAndTarget(t *testing.T) {
|
||||
got, gotDiags := ParseApply([]string{"-exclude=foo_bar.baz", "-target=foo_bar.bar"})
|
||||
if len(gotDiags) == 0 {
|
||||
t.Fatalf("expected error, but there was none")
|
||||
}
|
||||
|
||||
wantDiags := tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid combination of arguments",
|
||||
"-target and -exclude flags cannot be used together. Please remove one of the flags",
|
||||
),
|
||||
}
|
||||
if diff := cmp.Diff(wantDiags.ForRPC(), gotDiags.ForRPC()); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
}
|
||||
if len(got.Operation.Targets) > 0 {
|
||||
t.Errorf("Did not expect operation to parse targets, but it parsed %d targets", len(got.Operation.Targets))
|
||||
}
|
||||
if len(got.Operation.Excludes) > 0 {
|
||||
t.Errorf("Did not expect operation to parse excludes, but it parsed %d targets", len(got.Operation.Excludes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseApply_replace(t *testing.T) {
|
||||
foobarbaz, _ := addrs.ParseAbsResourceInstanceStr("foo_bar.baz")
|
||||
foobarbeep, _ := addrs.ParseAbsResourceInstanceStr("foo_bar.beep")
|
||||
@ -261,15 +340,17 @@ func TestParseApply_replace(t *testing.T) {
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, diags := ParseApply(tc.args)
|
||||
if len(diags) > 0 {
|
||||
if tc.wantErr == "" {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
if tc.wantErr == "" && len(diags) > 0 {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
} else if tc.wantErr != "" {
|
||||
if len(diags) == 0 {
|
||||
t.Fatalf("expected diags but got none")
|
||||
} else if got := diags.Err().Error(); !strings.Contains(got, tc.wantErr) {
|
||||
t.Fatalf("wrong diags\n got: %s\nwant: %s", got, tc.wantErr)
|
||||
}
|
||||
}
|
||||
if !cmp.Equal(got.Operation.ForceReplace, tc.want) {
|
||||
t.Fatalf("unexpected result\n%s", cmp.Diff(got.Operation.Targets, tc.want))
|
||||
t.Fatalf("unexpected result\n%s", cmp.Diff(got.Operation.ForceReplace, tc.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -69,6 +69,10 @@ type Operation struct {
|
||||
// their dependencies.
|
||||
Targets []addrs.Targetable
|
||||
|
||||
// Excludes allow limiting an operation to execute on all resources other
|
||||
// than a set of excluded resource addresses and resources dependent on them.
|
||||
Excludes []addrs.Targetable
|
||||
|
||||
// ForceReplace addresses cause OpenTofu to force a particular set of
|
||||
// resource instances to generate "replace" actions in any plan where they
|
||||
// would normally have generated "no-op" or "update" actions.
|
||||
@ -85,25 +89,25 @@ type Operation struct {
|
||||
// method Parse to populate the exported fields from these, validating
|
||||
// the raw values in the process.
|
||||
targetsRaw []string
|
||||
excludesRaw []string
|
||||
forceReplaceRaw []string
|
||||
destroyRaw bool
|
||||
refreshOnlyRaw bool
|
||||
}
|
||||
|
||||
// Parse must be called on Operation after initial flag parse. This processes
|
||||
// the raw target flags into addrs.Targetable values, returning diagnostics if
|
||||
// invalid.
|
||||
func (o *Operation) Parse() tfdiags.Diagnostics {
|
||||
// parseTargetables gets a list of strings, each representing a targetable object, and returns a list of
|
||||
// addrs.Targetable
|
||||
// This is used for parsing the input of -target and -exclude flags
|
||||
func parseTargetables(rawTargetables []string, flag string) ([]addrs.Targetable, tfdiags.Diagnostics) {
|
||||
var targetables []addrs.Targetable
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
o.Targets = nil
|
||||
|
||||
for _, tr := range o.targetsRaw {
|
||||
for _, tr := range rawTargetables {
|
||||
traversal, syntaxDiags := hclsyntax.ParseTraversalAbs([]byte(tr), "", hcl.Pos{Line: 1, Column: 1})
|
||||
if syntaxDiags.HasErrors() {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
fmt.Sprintf("Invalid target %q", tr),
|
||||
fmt.Sprintf("Invalid %s %q", flag, tr),
|
||||
syntaxDiags[0].Detail,
|
||||
))
|
||||
continue
|
||||
@ -113,14 +117,50 @@ func (o *Operation) Parse() tfdiags.Diagnostics {
|
||||
if targetDiags.HasErrors() {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
fmt.Sprintf("Invalid target %q", tr),
|
||||
fmt.Sprintf("Invalid %s %q", flag, tr),
|
||||
targetDiags[0].Description().Detail,
|
||||
))
|
||||
continue
|
||||
}
|
||||
|
||||
o.Targets = append(o.Targets, target.Subject)
|
||||
targetables = append(targetables, target.Subject)
|
||||
}
|
||||
return targetables, diags
|
||||
}
|
||||
|
||||
func parseRawTargetsAndExcludes(targets []string, excludes []string) ([]addrs.Targetable, []addrs.Targetable, tfdiags.Diagnostics) {
|
||||
var parsedTargets []addrs.Targetable
|
||||
var parsedExcludes []addrs.Targetable
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
if len(targets) > 0 && len(excludes) > 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid combination of arguments",
|
||||
"-target and -exclude flags cannot be used together. Please remove one of the flags",
|
||||
))
|
||||
return parsedTargets, parsedExcludes, diags
|
||||
}
|
||||
|
||||
var parseDiags tfdiags.Diagnostics
|
||||
parsedTargets, parseDiags = parseTargetables(targets, "target")
|
||||
diags = diags.Append(parseDiags)
|
||||
|
||||
parsedExcludes, parseDiags = parseTargetables(excludes, "exclude")
|
||||
diags = diags.Append(parseDiags)
|
||||
|
||||
return parsedTargets, parsedExcludes, diags
|
||||
}
|
||||
|
||||
// Parse must be called on Operation after initial flag parse. This processes
|
||||
// the raw target flags into addrs.Targetable values, returning diagnostics if
|
||||
// invalid.
|
||||
func (o *Operation) Parse() tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
var parseDiags tfdiags.Diagnostics
|
||||
o.Targets, o.Excludes, parseDiags = parseRawTargetsAndExcludes(o.targetsRaw, o.excludesRaw)
|
||||
diags = diags.Append(parseDiags)
|
||||
|
||||
for _, raw := range o.forceReplaceRaw {
|
||||
traversal, syntaxDiags := hclsyntax.ParseTraversalAbs([]byte(raw), "", hcl.Pos{Line: 1, Column: 1})
|
||||
@ -230,6 +270,7 @@ func extendedFlagSet(name string, state *State, operation *Operation, vars *Vars
|
||||
f.BoolVar(&operation.destroyRaw, "destroy", false, "destroy")
|
||||
f.BoolVar(&operation.refreshOnlyRaw, "refresh-only", false, "refresh-only")
|
||||
f.Var((*flagStringSlice)(&operation.targetsRaw), "target", "target")
|
||||
f.Var((*flagStringSlice)(&operation.excludesRaw), "exclude", "exclude")
|
||||
f.Var((*flagStringSlice)(&operation.forceReplaceRaw), "replace", "replace")
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
@ -134,25 +136,33 @@ func TestParsePlan_targets(t *testing.T) {
|
||||
"invalid traversal": {
|
||||
args: []string{"-target=foo."},
|
||||
want: nil,
|
||||
wantErr: "Dot must be followed by attribute name",
|
||||
wantErr: "Invalid target \"foo.\": Dot must be followed by attribute name",
|
||||
},
|
||||
"invalid target": {
|
||||
args: []string{"-target=data[0].foo"},
|
||||
want: nil,
|
||||
wantErr: "A data source name is required",
|
||||
wantErr: "Invalid target \"data[0].foo\": A data source name is required",
|
||||
},
|
||||
"empty target": {
|
||||
args: []string{"-target="},
|
||||
want: nil,
|
||||
wantErr: "Invalid target \"\": Must begin with a variable name.", // The error is `Invalid target "": Must begin with a variable name.`
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, diags := ParsePlan(tc.args)
|
||||
if len(diags) > 0 {
|
||||
if tc.wantErr == "" {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
if tc.wantErr == "" && len(diags) > 0 {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
} else if tc.wantErr != "" {
|
||||
if len(diags) == 0 {
|
||||
t.Fatalf("expected diags but got none")
|
||||
} else if got := diags.Err().Error(); !strings.Contains(got, tc.wantErr) {
|
||||
t.Fatalf("wrong diags\n got: %s\nwant: %s", got, tc.wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
if !cmp.Equal(got.Operation.Targets, tc.want) {
|
||||
t.Fatalf("unexpected result\n%s", cmp.Diff(got.Operation.Targets, tc.want))
|
||||
}
|
||||
@ -160,6 +170,86 @@ func TestParsePlan_targets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePlan_excludes(t *testing.T) {
|
||||
foobarbaz, _ := addrs.ParseTargetStr("foo_bar.baz")
|
||||
boop, _ := addrs.ParseTargetStr("module.boop")
|
||||
testCases := map[string]struct {
|
||||
args []string
|
||||
want []addrs.Targetable
|
||||
wantErr string
|
||||
}{
|
||||
"no excludes by default": {
|
||||
args: nil,
|
||||
want: nil,
|
||||
},
|
||||
"one exclude": {
|
||||
args: []string{"-exclude=foo_bar.baz"},
|
||||
want: []addrs.Targetable{foobarbaz.Subject},
|
||||
},
|
||||
"two excludes": {
|
||||
args: []string{"-exclude=foo_bar.baz", "-exclude", "module.boop"},
|
||||
want: []addrs.Targetable{foobarbaz.Subject, boop.Subject},
|
||||
},
|
||||
"invalid traversal": {
|
||||
args: []string{"-exclude=foo."},
|
||||
want: nil,
|
||||
wantErr: "Invalid exclude \"foo.\": Dot must be followed by attribute name",
|
||||
},
|
||||
"invalid exclude": {
|
||||
args: []string{"-exclude=data[0].foo"},
|
||||
want: nil,
|
||||
wantErr: "Invalid exclude \"data[0].foo\": A data source name is required",
|
||||
},
|
||||
"empty exclude": {
|
||||
args: []string{"-exclude="},
|
||||
want: nil,
|
||||
wantErr: "Invalid exclude \"\": Must begin with a variable name.",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, diags := ParsePlan(tc.args)
|
||||
if tc.wantErr == "" && len(diags) > 0 {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
} else if tc.wantErr != "" {
|
||||
if len(diags) == 0 {
|
||||
t.Fatalf("expected diags but got none")
|
||||
} else if got := diags.Err().Error(); !strings.Contains(got, tc.wantErr) {
|
||||
t.Fatalf("wrong diags\n got: %s\nwant: %s", got, tc.wantErr)
|
||||
}
|
||||
}
|
||||
if !cmp.Equal(got.Operation.Excludes, tc.want) {
|
||||
t.Fatalf("unexpected result\n%s", cmp.Diff(got.Operation.Excludes, tc.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePlan_excludeAndTarget(t *testing.T) {
|
||||
got, gotDiags := ParsePlan([]string{"-exclude=foo_bar.baz", "-target=foo_bar.bar"})
|
||||
if len(gotDiags) == 0 {
|
||||
t.Fatalf("expected error, but there was none")
|
||||
}
|
||||
|
||||
wantDiags := tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid combination of arguments",
|
||||
"-target and -exclude flags cannot be used together. Please remove one of the flags",
|
||||
),
|
||||
}
|
||||
if diff := cmp.Diff(wantDiags.ForRPC(), gotDiags.ForRPC()); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
}
|
||||
if len(got.Operation.Targets) > 0 {
|
||||
t.Errorf("Did not expect operation to parse targets, but it parsed %d targets", len(got.Operation.Targets))
|
||||
}
|
||||
if len(got.Operation.Excludes) > 0 {
|
||||
t.Errorf("Did not expect operation to parse excludes, but it parsed %d targets", len(got.Operation.Excludes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePlan_vars(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
args []string
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
)
|
||||
@ -119,13 +121,16 @@ func TestParseRefresh_targets(t *testing.T) {
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, diags := ParseRefresh(tc.args)
|
||||
if len(diags) > 0 {
|
||||
if tc.wantErr == "" {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
if tc.wantErr == "" && len(diags) > 0 {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
} else if tc.wantErr != "" {
|
||||
if len(diags) == 0 {
|
||||
t.Fatalf("expected diags but got none")
|
||||
} else if got := diags.Err().Error(); !strings.Contains(got, tc.wantErr) {
|
||||
t.Fatalf("wrong diags\n got: %s\nwant: %s", got, tc.wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
if !cmp.Equal(got.Operation.Targets, tc.want) {
|
||||
t.Fatalf("unexpected result\n%s", cmp.Diff(got.Operation.Targets, tc.want))
|
||||
}
|
||||
@ -133,6 +138,80 @@ func TestParseRefresh_targets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRefresh_excludes(t *testing.T) {
|
||||
foobarbaz, _ := addrs.ParseTargetStr("foo_bar.baz")
|
||||
boop, _ := addrs.ParseTargetStr("module.boop")
|
||||
testCases := map[string]struct {
|
||||
args []string
|
||||
want []addrs.Targetable
|
||||
wantErr string
|
||||
}{
|
||||
"no excludes by default": {
|
||||
args: nil,
|
||||
want: nil,
|
||||
},
|
||||
"one exclude": {
|
||||
args: []string{"-exclude=foo_bar.baz"},
|
||||
want: []addrs.Targetable{foobarbaz.Subject},
|
||||
},
|
||||
"two excludes": {
|
||||
args: []string{"-exclude=foo_bar.baz", "-exclude", "module.boop"},
|
||||
want: []addrs.Targetable{foobarbaz.Subject, boop.Subject},
|
||||
},
|
||||
"invalid traversal": {
|
||||
args: []string{"-exclude=foo."},
|
||||
want: nil,
|
||||
wantErr: "Dot must be followed by attribute name",
|
||||
},
|
||||
"invalid target": {
|
||||
args: []string{"-exclude=data[0].foo"},
|
||||
want: nil,
|
||||
wantErr: "A data source name is required",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, diags := ParseRefresh(tc.args)
|
||||
if tc.wantErr == "" && len(diags) > 0 {
|
||||
t.Fatalf("unexpected diags: %v", diags)
|
||||
} else if tc.wantErr != "" {
|
||||
if len(diags) == 0 {
|
||||
t.Fatalf("expected diags but got none")
|
||||
} else if got := diags.Err().Error(); !strings.Contains(got, tc.wantErr) {
|
||||
t.Fatalf("wrong diags\n got: %s\nwant: %s", got, tc.wantErr)
|
||||
}
|
||||
}
|
||||
if !cmp.Equal(got.Operation.Excludes, tc.want) {
|
||||
t.Fatalf("unexpected result\n%s", cmp.Diff(got.Operation.Excludes, tc.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRefresh_excludeAndTarget(t *testing.T) {
|
||||
got, gotDiags := ParseRefresh([]string{"-exclude=foo_bar.baz", "-target=foo_bar.bar"})
|
||||
if len(gotDiags) == 0 {
|
||||
t.Fatalf("expected error, but there was none")
|
||||
}
|
||||
|
||||
wantDiags := tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid combination of arguments",
|
||||
"-target and -exclude flags cannot be used together. Please remove one of the flags",
|
||||
),
|
||||
}
|
||||
if diff := cmp.Diff(wantDiags.ForRPC(), gotDiags.ForRPC()); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
}
|
||||
if len(got.Operation.Targets) > 0 {
|
||||
t.Errorf("Did not expect operation to parse targets, but it parsed %d targets", len(got.Operation.Targets))
|
||||
}
|
||||
if len(got.Operation.Excludes) > 0 {
|
||||
t.Errorf("Did not expect operation to parse excludes, but it parsed %d targets", len(got.Operation.Excludes))
|
||||
}
|
||||
}
|
||||
func TestParseRefresh_vars(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
args []string
|
||||
|
@ -213,6 +213,10 @@ type Meta struct {
|
||||
targets []addrs.Targetable
|
||||
targetFlags []string
|
||||
|
||||
// Excludes for this context (private)
|
||||
excludes []addrs.Targetable
|
||||
excludeFlags []string
|
||||
|
||||
// Internal fields
|
||||
color bool
|
||||
oldUi cli.Ui
|
||||
@ -618,6 +622,7 @@ func (m *Meta) extendedFlagSet(n string) *flag.FlagSet {
|
||||
|
||||
f.BoolVar(&m.input, "input", true, "input")
|
||||
f.Var((*FlagStringSlice)(&m.targetFlags), "target", "resource to target")
|
||||
f.Var((*FlagStringSlice)(&m.excludeFlags), "exclude", "resource to exclude")
|
||||
f.BoolVar(&m.compactWarnings, "compact-warnings", false, "use compact warnings")
|
||||
f.BoolVar(&m.consolidateWarnings, "consolidate-warnings", true, "consolidate warnings")
|
||||
f.BoolVar(&m.consolidateErrors, "consolidate-errors", false, "consolidate errors")
|
||||
|
@ -443,6 +443,7 @@ func (m *Meta) Operation(b backend.Backend, vt arguments.ViewType, enc encryptio
|
||||
Encryption: enc,
|
||||
PlanOutBackend: planOutBackend,
|
||||
Targets: m.targets,
|
||||
Excludes: m.excludes,
|
||||
UIIn: m.UIInput(),
|
||||
UIOut: m.Ui,
|
||||
Workspace: workspace,
|
||||
|
@ -166,6 +166,7 @@ func (c *PlanCommand) OperationRequest(
|
||||
opReq.PlanOutPath = planOutPath
|
||||
opReq.GenerateConfigOut = generateConfigOut
|
||||
opReq.Targets = args.Targets
|
||||
opReq.Excludes = args.Excludes
|
||||
opReq.ForceReplace = args.ForceReplace
|
||||
opReq.Type = backend.OperationTypePlan
|
||||
opReq.View = view.Operation()
|
||||
@ -238,7 +239,14 @@ Plan Customization Options:
|
||||
resource, or resource instance and all of its
|
||||
dependencies. You can use this option multiple times to
|
||||
include more than one object. This is for exceptional
|
||||
use only.
|
||||
use only. Cannot be used alongside the -exclude flag
|
||||
|
||||
-exclude=resource Limit the planning operation to not operate on the given
|
||||
module, resource, or resource instance and all of the
|
||||
resources and modules that depend on it. You can use this
|
||||
option multiple times to exclude more than one object.
|
||||
This is for exceptional use only. Cannot be used alongside
|
||||
the -target flag
|
||||
|
||||
-var 'foo=bar' Set a value for one of the input variables in the root
|
||||
module of the configuration. Use this option more than
|
||||
|
@ -1401,6 +1401,92 @@ func TestPlan_targetFlagsDiags(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Config with multiple resources, targeted plan with exclude
|
||||
func TestPlan_excluded(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath("apply-excluded"), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
p := testProvider()
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_instance": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Computed: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-exclude", "test_instance.bar",
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
|
||||
if got, want := output.Stdout(), "3 to add, 0 to change, 0 to destroy"; !strings.Contains(got, want) {
|
||||
t.Fatalf("bad change summary, want %q, got:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostics for invalid -exclude flags
|
||||
func TestPlan_excludeFlagsDiags(t *testing.T) {
|
||||
testCases := map[string]string{
|
||||
"test_instance.": "Dot must be followed by attribute name.",
|
||||
"test_instance": "Resource specification must include a resource type and name.",
|
||||
}
|
||||
|
||||
for exclude, wantDiag := range testCases {
|
||||
t.Run(exclude, func(t *testing.T) {
|
||||
td := testTempDir(t)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-exclude", exclude,
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 1 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stdout())
|
||||
}
|
||||
|
||||
got := output.Stderr()
|
||||
if !strings.Contains(got, exclude) {
|
||||
t.Fatalf("bad error output, want %q, got:\n%s", exclude, got)
|
||||
}
|
||||
if !strings.Contains(got, wantDiag) {
|
||||
t.Fatalf("bad error output, want %q, got:\n%s", wantDiag, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlan_replace(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath("plan-replace"), td)
|
||||
|
@ -150,6 +150,7 @@ func (c *RefreshCommand) OperationRequest(be backend.Enhanced, view views.Refres
|
||||
opReq.ConfigDir = "."
|
||||
opReq.Hooks = view.Hooks()
|
||||
opReq.Targets = args.Targets
|
||||
opReq.Excludes = args.Excludes
|
||||
opReq.Type = backend.OperationTypeRefresh
|
||||
opReq.View = view.Operation()
|
||||
|
||||
@ -205,6 +206,11 @@ Options:
|
||||
will be performed. All locations, for all errors
|
||||
will be listed. Disabled by default
|
||||
|
||||
-exclude=resource Resource to exclude. Operation will be limited to all
|
||||
resources that are not excluded or dependent on excluded
|
||||
resources. This flag can be used multiple times. Cannot
|
||||
be used alongside the -target flag.
|
||||
|
||||
-input=true Ask for input for variables if not directly set.
|
||||
|
||||
-lock=false Don't hold a state lock during the operation. This is
|
||||
@ -219,7 +225,8 @@ Options:
|
||||
|
||||
-target=resource Resource to target. Operation will be limited to this
|
||||
resource and its dependencies. This flag can be used
|
||||
multiple times.
|
||||
multiple times. Cannot be used alongside the -exclude
|
||||
flag.
|
||||
|
||||
-var 'foo=bar' Set a variable in the OpenTofu configuration. This
|
||||
flag can be set multiple times.
|
||||
|
@ -852,6 +852,100 @@ func TestRefresh_targetFlagsDiags(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Config with multiple resources, targeted refresh with exclude
|
||||
func TestRefresh_excluded(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath("refresh-targeted"), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
state := testState()
|
||||
statePath := testStateFile(t, state)
|
||||
|
||||
p := testProvider()
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_instance": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Computed: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
|
||||
view, done := testView(t)
|
||||
c := &RefreshCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-exclude", "test_instance.bar",
|
||||
"-state", statePath,
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
|
||||
got := output.Stdout()
|
||||
if want := "test_instance.foo: Refreshing"; !strings.Contains(got, want) {
|
||||
t.Fatalf("expected output to contain %q, got:\n%s", want, got)
|
||||
}
|
||||
if doNotWant := "test_instance.bar: Refreshing"; strings.Contains(got, doNotWant) {
|
||||
t.Fatalf("expected output not to contain %q, got:\n%s", doNotWant, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostics for invalid -exclude flags
|
||||
func TestRefresh_excludeFlagsDiags(t *testing.T) {
|
||||
testCases := map[string]string{
|
||||
"test_instance.": "Dot must be followed by attribute name.",
|
||||
"test_instance": "Resource specification must include a resource type and name.",
|
||||
}
|
||||
|
||||
for exclude, wantDiag := range testCases {
|
||||
t.Run(exclude, func(t *testing.T) {
|
||||
td := testTempDir(t)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
view, done := testView(t)
|
||||
c := &RefreshCommand{
|
||||
Meta: Meta{
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-exclude", exclude,
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 1 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
|
||||
got := output.Stderr()
|
||||
if !strings.Contains(got, exclude) {
|
||||
t.Fatalf("bad error output, want %q, got:\n%s", exclude, got)
|
||||
}
|
||||
if !strings.Contains(got, wantDiag) {
|
||||
t.Fatalf("bad error output, want %q, got:\n%s", wantDiag, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefresh_warnings(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := t.TempDir()
|
||||
|
@ -1024,25 +1024,26 @@ func TestTest_PartialUpdates(t *testing.T) {
|
||||
|
||||
Warning: Resource targeting is in effect
|
||||
|
||||
You are creating a plan with the -target option, which means that the result
|
||||
of this plan may not represent all of the changes requested by the current
|
||||
configuration.
|
||||
You are creating a plan with either the -target option or the -exclude
|
||||
option, which means that the result of this plan may not represent all of the
|
||||
changes requested by the current configuration.
|
||||
|
||||
The -target option is not for routine use, and is provided only for
|
||||
exceptional situations such as recovering from errors or mistakes, or when
|
||||
OpenTofu specifically suggests to use it as part of an error message.
|
||||
The -target and -exclude options are not for routine use, and are provided
|
||||
only for exceptional situations such as recovering from errors or mistakes,
|
||||
or when OpenTofu specifically suggests to use it as part of an error message.
|
||||
|
||||
Warning: Applied changes may be incomplete
|
||||
|
||||
The plan was created with the -target option in effect, so some changes
|
||||
requested in the configuration may have been ignored and the output values
|
||||
may not be fully updated. Run the following command to verify that no other
|
||||
changes are pending:
|
||||
The plan was created with the -target or the -exclude option in effect, so
|
||||
some changes requested in the configuration may have been ignored and the
|
||||
output values may not be fully updated. Run the following command to verify
|
||||
that no other changes are pending:
|
||||
tofu plan
|
||||
|
||||
Note that the -target option is not suitable for routine use, and is provided
|
||||
only for exceptional situations such as recovering from errors or mistakes,
|
||||
or when OpenTofu specifically suggests to use it as part of an error message.
|
||||
Note that the -target and -exclude options are not suitable for routine use,
|
||||
and are provided only for exceptional situations such as recovering from
|
||||
errors or mistakes, or when OpenTofu specifically suggests to use it as part
|
||||
of an error message.
|
||||
run "second"... pass
|
||||
|
||||
Success! 2 passed, 0 failed.
|
||||
@ -1055,25 +1056,26 @@ Success! 2 passed, 0 failed.
|
||||
|
||||
Warning: Resource targeting is in effect
|
||||
|
||||
You are creating a plan with the -target option, which means that the result
|
||||
of this plan may not represent all of the changes requested by the current
|
||||
configuration.
|
||||
You are creating a plan with either the -target option or the -exclude
|
||||
option, which means that the result of this plan may not represent all of the
|
||||
changes requested by the current configuration.
|
||||
|
||||
The -target option is not for routine use, and is provided only for
|
||||
exceptional situations such as recovering from errors or mistakes, or when
|
||||
OpenTofu specifically suggests to use it as part of an error message.
|
||||
The -target and -exclude options are not for routine use, and are provided
|
||||
only for exceptional situations such as recovering from errors or mistakes,
|
||||
or when OpenTofu specifically suggests to use it as part of an error message.
|
||||
|
||||
Warning: Applied changes may be incomplete
|
||||
|
||||
The plan was created with the -target option in effect, so some changes
|
||||
requested in the configuration may have been ignored and the output values
|
||||
may not be fully updated. Run the following command to verify that no other
|
||||
changes are pending:
|
||||
The plan was created with the -target or the -exclude option in effect, so
|
||||
some changes requested in the configuration may have been ignored and the
|
||||
output values may not be fully updated. Run the following command to verify
|
||||
that no other changes are pending:
|
||||
tofu plan
|
||||
|
||||
Note that the -target option is not suitable for routine use, and is provided
|
||||
only for exceptional situations such as recovering from errors or mistakes,
|
||||
or when OpenTofu specifically suggests to use it as part of an error message.
|
||||
Note that the -target and -exclude options are not suitable for routine use,
|
||||
and are provided only for exceptional situations such as recovering from
|
||||
errors or mistakes, or when OpenTofu specifically suggests to use it as part
|
||||
of an error message.
|
||||
|
||||
Failure! 0 passed, 1 failed.
|
||||
`,
|
||||
|
9
internal/command/testdata/apply-excluded/main.tf
vendored
Normal file
9
internal/command/testdata/apply-excluded/main.tf
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
resource "test_instance" "foo" {
|
||||
count = 2
|
||||
}
|
||||
|
||||
resource "test_instance" "bar" {
|
||||
}
|
||||
|
||||
resource "test_instance" "baz" {
|
||||
}
|
@ -387,6 +387,10 @@ type Plan struct {
|
||||
// target addresses are present, the plan applies to the whole
|
||||
// configuration.
|
||||
TargetAddrs []string `protobuf:"bytes,5,rep,name=target_addrs,json=targetAddrs,proto3" json:"target_addrs,omitempty"`
|
||||
// An unordered set of exclude addresses to exclude when applying. If no
|
||||
// target addresses are present, the plan applies to the whole
|
||||
// configuration.
|
||||
ExcludeAddrs []string `protobuf:"bytes,6,rep,name=exclude_addrs,json=excludeAddrs,proto3" json:"exclude_addrs,omitempty"`
|
||||
// An unordered set of force-replace addresses to include when applying.
|
||||
// This must match the set of addresses that was used when creating the
|
||||
// plan, or else applying the plan will fail when it reaches a different
|
||||
@ -499,6 +503,13 @@ func (x *Plan) GetTargetAddrs() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Plan) GetExcludeAddrs() []string {
|
||||
if x != nil {
|
||||
return x.ExcludeAddrs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Plan) GetForceReplaceAddrs() []string {
|
||||
if x != nil {
|
||||
return x.ForceReplaceAddrs
|
||||
@ -1348,7 +1359,7 @@ var File_planfile_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_planfile_proto_rawDesc = []byte{
|
||||
0x0a, 0x0e, 0x70, 0x6c, 0x61, 0x6e, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x12, 0x06, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x22, 0xdf, 0x06, 0x0a, 0x04, 0x50, 0x6c, 0x61,
|
||||
0x12, 0x06, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x22, 0x84, 0x07, 0x0a, 0x04, 0x50, 0x6c, 0x61,
|
||||
0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x07, 0x75,
|
||||
0x69, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x74,
|
||||
@ -1377,178 +1388,181 @@ var file_planfile_proto_rawDesc = []byte{
|
||||
0x6c, 0x74, 0x73, 0x52, 0x0c, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
|
||||
0x73, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72,
|
||||
0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41,
|
||||
0x64, 0x64, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65,
|
||||
0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28,
|
||||
0x09, 0x52, 0x11, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x41,
|
||||
0x64, 0x64, 0x72, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72,
|
||||
0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x10, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x29, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x0d, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x42, 0x61, 0x63, 0x6b,
|
||||
0x65, 0x6e, 0x64, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x4b, 0x0a, 0x13,
|
||||
0x72, 0x65, 0x6c, 0x65, 0x76, 0x61, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75,
|
||||
0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x66, 0x70, 0x6c,
|
||||
0x61, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
||||
0x5f, 0x61, 0x74, 0x74, 0x72, 0x52, 0x12, 0x72, 0x65, 0x6c, 0x65, 0x76, 0x61, 0x6e, 0x74, 0x41,
|
||||
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x1a, 0x52, 0x0a, 0x0e, 0x56, 0x61, 0x72, 0x69, 0x61,
|
||||
0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70,
|
||||
0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4d, 0x0a, 0x0d, 0x72,
|
||||
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
||||
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x04, 0x61, 0x74, 0x74, 0x72,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e,
|
||||
0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x61, 0x74, 0x74, 0x72, 0x22, 0x69, 0x0a, 0x07, 0x42, 0x61,
|
||||
0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x06, 0x63, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c,
|
||||
0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
|
||||
0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73,
|
||||
0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b,
|
||||
0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0xc0, 0x02, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65,
|
||||
0x12, 0x26, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
|
||||
0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61,
|
||||
0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x16, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65,
|
||||
0x5f, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
|
||||
0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e,
|
||||
0x50, 0x61, 0x74, 0x68, 0x52, 0x14, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x6e, 0x73,
|
||||
0x69, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x40, 0x0a, 0x15, 0x61, 0x66,
|
||||
0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61,
|
||||
0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c,
|
||||
0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x13, 0x61, 0x66, 0x74, 0x65, 0x72, 0x53, 0x65,
|
||||
0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x2f, 0x0a, 0x09,
|
||||
0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x11, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69,
|
||||
0x6e, 0x67, 0x52, 0x09, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x29, 0x0a,
|
||||
0x10, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
|
||||
0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xd3, 0x02, 0x0a, 0x16, 0x52, 0x65, 0x73,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61,
|
||||
0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x5f,
|
||||
0x72, 0x75, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
||||
0x70, 0x72, 0x65, 0x76, 0x52, 0x75, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x64,
|
||||
0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
||||
0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e,
|
||||
0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61,
|
||||
0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x37, 0x0a, 0x10, 0x72, 0x65,
|
||||
0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x0b,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61,
|
||||
0x74, 0x68, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c,
|
||||
0x61, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65,
|
||||
0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x74, 0x66, 0x70,
|
||||
0x6c, 0x61, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74,
|
||||
0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
|
||||
0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x68,
|
||||
0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e,
|
||||
0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65,
|
||||
0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73,
|
||||
0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0xfc, 0x03, 0x0a, 0x0c, 0x43, 0x68, 0x65,
|
||||
0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x33, 0x0a, 0x04, 0x6b, 0x69, 0x6e,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e,
|
||||
0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x4f, 0x62,
|
||||
0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1f,
|
||||
0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x41, 0x64, 0x64, 0x72, 0x12,
|
||||
0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65,
|
||||
0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18,
|
||||
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43,
|
||||
0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x4f, 0x62, 0x6a, 0x65,
|
||||
0x63, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74,
|
||||
0x73, 0x1a, 0x8f, 0x01, 0x0a, 0x0c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x75,
|
||||
0x6c, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64,
|
||||
0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x41,
|
||||
0x64, 0x64, 0x72, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x65,
|
||||
0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x66, 0x61, 0x69, 0x6c,
|
||||
0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03,
|
||||
0x28, 0x09, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a,
|
||||
0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x41,
|
||||
0x53, 0x53, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x02, 0x12, 0x09,
|
||||
0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x22, 0x5c, 0x0a, 0x0a, 0x4f, 0x62, 0x6a,
|
||||
0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45,
|
||||
0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x4f,
|
||||
0x55, 0x52, 0x43, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54,
|
||||
0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x48, 0x45, 0x43,
|
||||
0x4b, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x56, 0x41, 0x52,
|
||||
0x49, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x04, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79, 0x6e, 0x61, 0x6d,
|
||||
0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61,
|
||||
0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61, 0x63,
|
||||
0x6b, 0x22, 0xa5, 0x01, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74,
|
||||
0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x66, 0x70, 0x6c,
|
||||
0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52, 0x05, 0x73, 0x74,
|
||||
0x65, 0x70, 0x73, 0x1a, 0x74, 0x0a, 0x04, 0x53, 0x74, 0x65, 0x70, 0x12, 0x27, 0x0a, 0x0e, 0x61,
|
||||
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
|
||||
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f,
|
||||
0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c,
|
||||
0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48,
|
||||
0x00, 0x52, 0x0a, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x42, 0x0a, 0x0a,
|
||||
0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x1b, 0x0a, 0x09, 0x49, 0x6d, 0x70,
|
||||
0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x2a, 0x31, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0a,
|
||||
0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45,
|
||||
0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x46, 0x52, 0x45,
|
||||
0x53, 0x48, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x2a, 0x7c, 0x0a, 0x06, 0x41, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, 0x12, 0x0a, 0x0a,
|
||||
0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x45, 0x41,
|
||||
0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12,
|
||||
0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x44,
|
||||
0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54,
|
||||
0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x48,
|
||||
0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x07, 0x12, 0x0a, 0x0a, 0x06, 0x46,
|
||||
0x4f, 0x52, 0x47, 0x45, 0x54, 0x10, 0x08, 0x2a, 0xc8, 0x03, 0x0a, 0x1c, 0x52, 0x65, 0x73, 0x6f,
|
||||
0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45,
|
||||
0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45,
|
||||
0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x54, 0x41, 0x49, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12,
|
||||
0x16, 0x0a, 0x12, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x59, 0x5f, 0x52, 0x45,
|
||||
0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x50, 0x4c, 0x41,
|
||||
0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x4e, 0x4f,
|
||||
0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x44, 0x45,
|
||||
0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f,
|
||||
0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10,
|
||||
0x04, 0x12, 0x23, 0x0a, 0x1f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41,
|
||||
0x55, 0x53, 0x45, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x50, 0x45, 0x54, 0x49,
|
||||
0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45,
|
||||
0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x49,
|
||||
0x4e, 0x44, 0x45, 0x58, 0x10, 0x06, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45,
|
||||
0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x45, 0x41, 0x43, 0x48, 0x5f, 0x4b, 0x45,
|
||||
0x59, 0x10, 0x07, 0x12, 0x1c, 0x0a, 0x18, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45,
|
||||
0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x44, 0x55, 0x4c, 0x45, 0x10,
|
||||
0x08, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x59, 0x5f,
|
||||
0x54, 0x52, 0x49, 0x47, 0x47, 0x45, 0x52, 0x53, 0x10, 0x09, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45,
|
||||
0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49,
|
||||
0x47, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x0a, 0x12, 0x23, 0x0a, 0x1f, 0x52,
|
||||
0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x44, 0x45, 0x50, 0x45,
|
||||
0x4e, 0x44, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x0b,
|
||||
0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45,
|
||||
0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x10, 0x0d, 0x12,
|
||||
0x21, 0x0a, 0x1d, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53,
|
||||
0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54,
|
||||
0x10, 0x0c, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x6f, 0x66, 0x75, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x6f,
|
||||
0x66, 0x75, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e,
|
||||
0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x64, 0x64, 0x72, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f,
|
||||
0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x63,
|
||||
0x6c, 0x75, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x6f, 0x72,
|
||||
0x63, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73,
|
||||
0x18, 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70,
|
||||
0x6c, 0x61, 0x63, 0x65, 0x41, 0x64, 0x64, 0x72, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x74, 0x65, 0x72,
|
||||
0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0e,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x56,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e,
|
||||
0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e,
|
||||
0x2e, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e,
|
||||
0x64, 0x12, 0x4b, 0x0a, 0x13, 0x72, 0x65, 0x6c, 0x65, 0x76, 0x61, 0x6e, 0x74, 0x5f, 0x61, 0x74,
|
||||
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a,
|
||||
0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x72, 0x65, 0x73,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x52, 0x12, 0x72, 0x65, 0x6c, 0x65,
|
||||
0x76, 0x61, 0x6e, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1c,
|
||||
0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x15, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x1a, 0x52, 0x0a, 0x0e,
|
||||
0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
|
||||
0x1a, 0x4d, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74,
|
||||
0x72, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x20, 0x0a,
|
||||
0x04, 0x61, 0x74, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66,
|
||||
0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x61, 0x74, 0x74, 0x72, 0x22,
|
||||
0x69, 0x0a, 0x07, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79,
|
||||
0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2c,
|
||||
0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14,
|
||||
0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09,
|
||||
0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0xc0, 0x02, 0x0a, 0x06, 0x43,
|
||||
0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x41,
|
||||
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a,
|
||||
0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e,
|
||||
0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x16, 0x62,
|
||||
0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f,
|
||||
0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66,
|
||||
0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x14, 0x62, 0x65, 0x66, 0x6f, 0x72,
|
||||
0x65, 0x53, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12,
|
||||
0x40, 0x0a, 0x15, 0x61, 0x66, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69,
|
||||
0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c,
|
||||
0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x13, 0x61, 0x66,
|
||||
0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68,
|
||||
0x73, 0x12, 0x2f, 0x0a, 0x09, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x49, 0x6d,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x09, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69,
|
||||
0x6e, 0x67, 0x12, 0x29, 0x0a, 0x10, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f,
|
||||
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x65,
|
||||
0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xd3, 0x02,
|
||||
0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e,
|
||||
0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72,
|
||||
0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x22, 0x0a, 0x0d,
|
||||
0x70, 0x72, 0x65, 0x76, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0e, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x65, 0x76, 0x52, 0x75, 0x6e, 0x41, 0x64, 0x64, 0x72,
|
||||
0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18,
|
||||
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x4b, 0x65,
|
||||
0x79, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x26, 0x0a,
|
||||
0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e,
|
||||
0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63,
|
||||
0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
|
||||
0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12,
|
||||
0x37, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x70, 0x6c,
|
||||
0x61, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c,
|
||||
0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65,
|
||||
0x64, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x24, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
||||
0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61,
|
||||
0x73, 0x6f, 0x6e, 0x22, 0x68, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x68, 0x61,
|
||||
0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e,
|
||||
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12,
|
||||
0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0xfc, 0x03,
|
||||
0x0a, 0x0c, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x33,
|
||||
0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x74,
|
||||
0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c,
|
||||
0x74, 0x73, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b,
|
||||
0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x61, 0x64,
|
||||
0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||
0x41, 0x64, 0x64, 0x72, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68,
|
||||
0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x62, 0x6a,
|
||||
0x65, 0x63, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x66, 0x70,
|
||||
0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73,
|
||||
0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x6f,
|
||||
0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x1a, 0x8f, 0x01, 0x0a, 0x0c, 0x4f, 0x62, 0x6a, 0x65, 0x63,
|
||||
0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63,
|
||||
0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62,
|
||||
0x6a, 0x65, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61,
|
||||
0x6e, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x53,
|
||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a,
|
||||
0x10, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65,
|
||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12,
|
||||
0x08, 0x0a, 0x04, 0x50, 0x41, 0x53, 0x53, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49,
|
||||
0x4c, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x22, 0x5c,
|
||||
0x0a, 0x0a, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0f, 0x0a, 0x0b,
|
||||
0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a,
|
||||
0x08, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f,
|
||||
0x55, 0x54, 0x50, 0x55, 0x54, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x09, 0x0a,
|
||||
0x05, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x50, 0x55,
|
||||
0x54, 0x5f, 0x56, 0x41, 0x52, 0x49, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x04, 0x22, 0x28, 0x0a, 0x0c,
|
||||
0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07,
|
||||
0x6d, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d,
|
||||
0x73, 0x67, 0x70, 0x61, 0x63, 0x6b, 0x22, 0xa5, 0x01, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12,
|
||||
0x27, 0x0a, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11,
|
||||
0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x2e, 0x53, 0x74, 0x65,
|
||||
0x70, 0x52, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x74, 0x0a, 0x04, 0x53, 0x74, 0x65, 0x70,
|
||||
0x12, 0x27, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72,
|
||||
0x69, 0x62, 0x75, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x65, 0x6c, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14,
|
||||
0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b,
|
||||
0x65, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x1b,
|
||||
0x0a, 0x09, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x2a, 0x31, 0x0a, 0x04, 0x4d,
|
||||
0x6f, 0x64, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12,
|
||||
0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c,
|
||||
0x52, 0x45, 0x46, 0x52, 0x45, 0x53, 0x48, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x2a, 0x7c,
|
||||
0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50,
|
||||
0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08,
|
||||
0x0a, 0x04, 0x52, 0x45, 0x41, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41,
|
||||
0x54, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x05,
|
||||
0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f,
|
||||
0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x52, 0x45, 0x41,
|
||||
0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x07,
|
||||
0x12, 0x0a, 0x0a, 0x06, 0x46, 0x4f, 0x52, 0x47, 0x45, 0x54, 0x10, 0x08, 0x2a, 0xc8, 0x03, 0x0a,
|
||||
0x1c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
|
||||
0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x08, 0x0a,
|
||||
0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x50, 0x4c, 0x41,
|
||||
0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x54, 0x41, 0x49, 0x4e, 0x54,
|
||||
0x45, 0x44, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f,
|
||||
0x42, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d,
|
||||
0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f,
|
||||
0x43, 0x41, 0x4e, 0x4e, 0x4f, 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12,
|
||||
0x25, 0x0a, 0x21, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53,
|
||||
0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4f,
|
||||
0x4e, 0x46, 0x49, 0x47, 0x10, 0x04, 0x12, 0x23, 0x0a, 0x1f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45,
|
||||
0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x57, 0x52, 0x4f, 0x4e, 0x47, 0x5f, 0x52,
|
||||
0x45, 0x50, 0x45, 0x54, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x44,
|
||||
0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x4f,
|
||||
0x55, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x06, 0x12, 0x1b, 0x0a, 0x17, 0x44,
|
||||
0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x45, 0x41,
|
||||
0x43, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x07, 0x12, 0x1c, 0x0a, 0x18, 0x44, 0x45, 0x4c, 0x45,
|
||||
0x54, 0x45, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f,
|
||||
0x44, 0x55, 0x4c, 0x45, 0x10, 0x08, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43,
|
||||
0x45, 0x5f, 0x42, 0x59, 0x5f, 0x54, 0x52, 0x49, 0x47, 0x47, 0x45, 0x52, 0x53, 0x10, 0x09, 0x12,
|
||||
0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f,
|
||||
0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x0a,
|
||||
0x12, 0x23, 0x0a, 0x1f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45, 0x43, 0x41, 0x55, 0x53, 0x45,
|
||||
0x5f, 0x44, 0x45, 0x50, 0x45, 0x4e, 0x44, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x50, 0x45, 0x4e, 0x44,
|
||||
0x49, 0x4e, 0x47, 0x10, 0x0b, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x42, 0x45,
|
||||
0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x4e, 0x45, 0x53, 0x54,
|
||||
0x45, 0x44, 0x10, 0x0d, 0x12, 0x21, 0x0a, 0x1d, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x42,
|
||||
0x45, 0x43, 0x41, 0x55, 0x53, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x54,
|
||||
0x41, 0x52, 0x47, 0x45, 0x54, 0x10, 0x0c, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x6f, 0x66, 0x75, 0x2f, 0x6f,
|
||||
0x70, 0x65, 0x6e, 0x74, 0x6f, 0x66, 0x75, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
|
||||
0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
|
||||
0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -66,6 +66,11 @@ message Plan {
|
||||
// configuration.
|
||||
repeated string target_addrs = 5;
|
||||
|
||||
// An unordered set of exclude addresses to exclude when applying. If no
|
||||
// target addresses are present, the plan applies to the whole
|
||||
// configuration.
|
||||
repeated string exclude_addrs = 6;
|
||||
|
||||
// An unordered set of force-replace addresses to include when applying.
|
||||
// This must match the set of addresses that was used when creating the
|
||||
// plan, or else applying the plan will fail when it reaches a different
|
||||
|
@ -46,6 +46,7 @@ type Plan struct {
|
||||
Changes *Changes
|
||||
DriftedResources []*ResourceInstanceChangeSrc
|
||||
TargetAddrs []addrs.Targetable
|
||||
ExcludeAddrs []addrs.Targetable
|
||||
ForceReplaceAddrs []addrs.AbsResourceInstance
|
||||
Backend Backend
|
||||
|
||||
|
@ -223,6 +223,14 @@ func readTfplan(r io.Reader) (*plans.Plan, error) {
|
||||
plan.TargetAddrs = append(plan.TargetAddrs, target.Subject)
|
||||
}
|
||||
|
||||
for _, rawExcludeAddr := range rawPlan.ExcludeAddrs {
|
||||
exclude, diags := addrs.ParseTargetStr(rawExcludeAddr)
|
||||
if diags.HasErrors() {
|
||||
return nil, fmt.Errorf("plan contains invalid exclude address %q: %w", exclude, diags.Err())
|
||||
}
|
||||
plan.ExcludeAddrs = append(plan.ExcludeAddrs, exclude.Subject)
|
||||
}
|
||||
|
||||
for _, rawReplaceAddr := range rawPlan.ForceReplaceAddrs {
|
||||
addr, diags := addrs.ParseAbsResourceInstanceStr(rawReplaceAddr)
|
||||
if diags.HasErrors() {
|
||||
@ -610,6 +618,10 @@ func writeTfplan(plan *plans.Plan, w io.Writer) error {
|
||||
rawPlan.TargetAddrs = append(rawPlan.TargetAddrs, targetAddr.String())
|
||||
}
|
||||
|
||||
for _, excludeAddr := range plan.ExcludeAddrs {
|
||||
rawPlan.ExcludeAddrs = append(rawPlan.ExcludeAddrs, excludeAddr.String())
|
||||
}
|
||||
|
||||
for _, replaceAddr := range plan.ForceReplaceAddrs {
|
||||
rawPlan.ForceReplaceAddrs = append(rawPlan.ForceReplaceAddrs, replaceAddr.String())
|
||||
}
|
||||
|
@ -93,14 +93,14 @@ func (c *Context) Apply(plan *plans.Plan, config *configs.Config) (*states.State
|
||||
newState.PruneResourceHusks()
|
||||
}
|
||||
|
||||
if len(plan.TargetAddrs) > 0 {
|
||||
if len(plan.TargetAddrs) > 0 || len(plan.ExcludeAddrs) > 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Applied changes may be incomplete",
|
||||
`The plan was created with the -target option in effect, so some changes requested in the configuration may have been ignored and the output values may not be fully updated. Run the following command to verify that no other changes are pending:
|
||||
`The plan was created with the -target or the -exclude option in effect, so some changes requested in the configuration may have been ignored and the output values may not be fully updated. Run the following command to verify that no other changes are pending:
|
||||
tofu plan
|
||||
|
||||
Note that the -target option is not suitable for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
Note that the -target and -exclude options are not suitable for routine use, and are provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
))
|
||||
}
|
||||
|
||||
@ -181,6 +181,7 @@ func (c *Context) applyGraph(plan *plans.Plan, config *configs.Config, validate
|
||||
RootVariableValues: variables,
|
||||
Plugins: c.plugins,
|
||||
Targets: plan.TargetAddrs,
|
||||
Excludes: plan.ExcludeAddrs,
|
||||
ForceReplace: plan.ForceReplaceAddrs,
|
||||
Operation: operation,
|
||||
ExternalReferences: plan.ExternalReferences,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7149,52 +7149,51 @@ func TestContext2Apply_targetedDestroy(t *testing.T) {
|
||||
m := testModule(t, "destroy-targeted")
|
||||
p := testProvider("aws")
|
||||
p.PlanResourceChangeFn = testDiffFn
|
||||
p.ApplyResourceChangeFn = testApplyFn
|
||||
|
||||
state := states.NewState()
|
||||
root := state.EnsureModule(addrs.RootModuleInstance)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.a").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"bar"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
||||
)
|
||||
root.SetOutputValue("out", cty.StringVal("bar"), false)
|
||||
var state *states.State
|
||||
{
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
||||
child.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.b").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"i-bcd345"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
||||
)
|
||||
// First plan and apply a create operation
|
||||
if diags := ctx.Validate(m); diags.HasErrors() {
|
||||
t.Fatalf("validate errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
||||
assertNoErrors(t, diags)
|
||||
|
||||
if diags := ctx.Validate(m); diags.HasErrors() {
|
||||
t.Fatalf("validate errors: %s", diags.Err())
|
||||
state, diags = ctx.Apply(plan, m)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("apply err: %s", diags.Err())
|
||||
}
|
||||
}
|
||||
|
||||
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.DestroyMode,
|
||||
Targets: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "a",
|
||||
),
|
||||
},
|
||||
})
|
||||
assertNoErrors(t, diags)
|
||||
{
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
state, diags = ctx.Apply(plan, m)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("diags: %s", diags.Err())
|
||||
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.DestroyMode,
|
||||
Targets: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "a",
|
||||
),
|
||||
},
|
||||
})
|
||||
assertNoErrors(t, diags)
|
||||
|
||||
state, diags = ctx.Apply(plan, m)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("diags: %s", diags.Err())
|
||||
}
|
||||
}
|
||||
|
||||
mod := state.RootModule()
|
||||
@ -7223,10 +7222,10 @@ func TestContext2Apply_targetedDestroy(t *testing.T) {
|
||||
t.Fatalf("expected 1 outputs, got: %#v", mod.OutputValues)
|
||||
}
|
||||
|
||||
// the module instance should remain
|
||||
// the module instance should not remain
|
||||
mod = state.Module(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
||||
if len(mod.Resources) != 1 {
|
||||
t.Fatalf("expected 1 resources, got: %#v", mod.Resources)
|
||||
if mod != nil {
|
||||
t.Fatalf("expected child module to not exist in state, but it does")
|
||||
}
|
||||
}
|
||||
|
||||
@ -7554,7 +7553,8 @@ func TestContext2Apply_targetedModuleUnrelatedOutputs(t *testing.T) {
|
||||
p.ApplyResourceChangeFn = testApplyFn
|
||||
|
||||
state := states.NewState()
|
||||
_ = state.EnsureModule(addrs.RootModuleInstance.Child("child2", addrs.NoKey))
|
||||
child1 := state.EnsureModule(addrs.RootModuleInstance.Child("child1", addrs.NoKey))
|
||||
child1.SetOutputValue("instance_id", cty.StringVal("something"), false)
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
@ -7643,6 +7643,7 @@ func TestContext2Apply_targetedResourceOrphanModule(t *testing.T) {
|
||||
m := testModule(t, "apply-targeted-resource-orphan-module")
|
||||
p := testProvider("aws")
|
||||
p.PlanResourceChangeFn = testDiffFn
|
||||
p.ApplyResourceChangeFn = testApplyFn
|
||||
|
||||
state := states.NewState()
|
||||
child := state.EnsureModule(addrs.RootModuleInstance.Child("parent", addrs.NoKey))
|
||||
@ -7650,7 +7651,7 @@ func TestContext2Apply_targetedResourceOrphanModule(t *testing.T) {
|
||||
mustResourceInstanceAddr("aws_instance.bar").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"type":"aws_instance"}`),
|
||||
AttrsJSON: []byte(`{"id":"abc","type":"aws_instance"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
||||
)
|
||||
@ -7671,9 +7672,24 @@ func TestContext2Apply_targetedResourceOrphanModule(t *testing.T) {
|
||||
})
|
||||
assertNoErrors(t, diags)
|
||||
|
||||
if _, diags := ctx.Apply(plan, m); diags.HasErrors() {
|
||||
state, diags = ctx.Apply(plan, m)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("apply errors: %s", diags.Err())
|
||||
|
||||
}
|
||||
|
||||
checkStateString(t, state, `
|
||||
aws_instance.foo:
|
||||
ID = foo
|
||||
provider = provider["registry.opentofu.org/hashicorp/aws"]
|
||||
type = aws_instance
|
||||
|
||||
module.parent:
|
||||
aws_instance.bar:
|
||||
ID = abc
|
||||
provider = provider["registry.opentofu.org/hashicorp/aws"]
|
||||
type = aws_instance
|
||||
`)
|
||||
}
|
||||
|
||||
func TestContext2Apply_unknownAttribute(t *testing.T) {
|
||||
|
@ -67,6 +67,16 @@ type PlanOpts struct {
|
||||
// warnings as part of the planning result.
|
||||
Targets []addrs.Targetable
|
||||
|
||||
// If Excludes has a non-zero length then it activates targeted planning
|
||||
// mode, where OpenTofu will take actions only for resource instances
|
||||
// that are not mentioned in this set and are not dependent on targets
|
||||
// mentioned in this set.
|
||||
//
|
||||
// Targeted planning mode is intended for exceptional use only,
|
||||
// and so populating this field will cause OpenTofu to generate extra
|
||||
// warnings as part of the planning result.
|
||||
Excludes []addrs.Targetable
|
||||
|
||||
// ForceReplace is a set of resource instance addresses whose corresponding
|
||||
// objects should be forced planned for replacement if the provider's
|
||||
// plan would otherwise have been to either update the object in-place or
|
||||
@ -186,13 +196,13 @@ func (c *Context) Plan(config *configs.Config, prevRunState *states.State, opts
|
||||
varDiags := checkInputVariables(config.Module.Variables, opts.SetVariables)
|
||||
diags = diags.Append(varDiags)
|
||||
|
||||
if len(opts.Targets) > 0 {
|
||||
if len(opts.Targets) > 0 || len(opts.Excludes) > 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Resource targeting is in effect",
|
||||
`You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
`You are creating a plan with either the -target option or the -exclude option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
|
||||
The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
The -target and -exclude options are not for routine use, and are provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
))
|
||||
}
|
||||
|
||||
@ -241,6 +251,7 @@ The -target option is not for routine use, and is provided only for exceptional
|
||||
if plan != nil {
|
||||
plan.VariableValues = varVals
|
||||
plan.TargetAddrs = opts.Targets
|
||||
plan.ExcludeAddrs = opts.Excludes
|
||||
} else if !diags.HasErrors() {
|
||||
panic("nil plan but no errors")
|
||||
}
|
||||
@ -460,7 +471,7 @@ func (c *Context) destroyPlan(config *configs.Config, prevRunState *states.State
|
||||
return destroyPlan, diags
|
||||
}
|
||||
|
||||
func (c *Context) prePlanFindAndApplyMoves(config *configs.Config, prevRunState *states.State, targets []addrs.Targetable) ([]refactoring.MoveStatement, refactoring.MoveResults) {
|
||||
func (c *Context) prePlanFindAndApplyMoves(config *configs.Config, prevRunState *states.State) ([]refactoring.MoveStatement, refactoring.MoveResults) {
|
||||
explicitMoveStmts := refactoring.FindMoveStatements(config)
|
||||
implicitMoveStmts := refactoring.ImpliedMoveStatements(config, prevRunState, explicitMoveStmts)
|
||||
var moveStmts []refactoring.MoveStatement
|
||||
@ -473,11 +484,17 @@ func (c *Context) prePlanFindAndApplyMoves(config *configs.Config, prevRunState
|
||||
return moveStmts, moveResults
|
||||
}
|
||||
|
||||
func (c *Context) prePlanVerifyTargetedMoves(moveResults refactoring.MoveResults, targets []addrs.Targetable) tfdiags.Diagnostics {
|
||||
if len(targets) < 1 {
|
||||
return nil // the following only matters when targeting
|
||||
func (c *Context) prePlanVerifyTargetedMoves(moveResults refactoring.MoveResults, targets []addrs.Targetable, excludes []addrs.Targetable) tfdiags.Diagnostics {
|
||||
if len(targets) > 0 {
|
||||
return c.prePlanVerifyMovesWithTargetFlag(moveResults, targets)
|
||||
}
|
||||
if len(excludes) > 0 {
|
||||
return c.prePlanVerifyMovesWithExcludeFlag(moveResults, excludes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Context) prePlanVerifyMovesWithTargetFlag(moveResults refactoring.MoveResults, targets []addrs.Targetable) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
var excluded []addrs.AbsResourceInstance
|
||||
@ -541,6 +558,70 @@ func (c *Context) prePlanVerifyTargetedMoves(moveResults refactoring.MoveResults
|
||||
return diags
|
||||
}
|
||||
|
||||
func (c *Context) prePlanVerifyMovesWithExcludeFlag(moveResults refactoring.MoveResults, excludes []addrs.Targetable) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
var excluded []addrs.AbsResourceInstance
|
||||
for _, result := range moveResults.Changes.Values() {
|
||||
fromExcluded := false
|
||||
toExcluded := false
|
||||
for _, excludeAddr := range excludes {
|
||||
if excludeAddr.TargetContains(result.From) {
|
||||
fromExcluded = true
|
||||
}
|
||||
if excludeAddr.TargetContains(result.To) {
|
||||
toExcluded = true
|
||||
}
|
||||
}
|
||||
if fromExcluded {
|
||||
excluded = append(excluded, result.From)
|
||||
}
|
||||
if toExcluded {
|
||||
excluded = append(excluded, result.To)
|
||||
}
|
||||
}
|
||||
if len(excluded) > 0 {
|
||||
sort.Slice(excluded, func(i, j int) bool {
|
||||
return excluded[i].Less(excluded[j])
|
||||
})
|
||||
|
||||
var listBuf strings.Builder
|
||||
var prevResourceAddr addrs.AbsResource
|
||||
for _, instAddr := range excluded {
|
||||
// Targeting generally ends up selecting whole resources rather
|
||||
// than individual instances, because we don't factor in
|
||||
// individual instances until DynamicExpand, so we're going to
|
||||
// always show whole resource addresses here, excluding any
|
||||
// instance keys. (This also neatly avoids dealing with the
|
||||
// different quoting styles required for string instance keys
|
||||
// on different shells, which is handy.)
|
||||
//
|
||||
// To avoid showing duplicates when we have multiple instances
|
||||
// of the same resource, we'll remember the most recent
|
||||
// resource we rendered in prevResource, which is sufficient
|
||||
// because we sorted the list of instance addresses above, and
|
||||
// our sort order always groups together instances of the same
|
||||
// resource.
|
||||
resourceAddr := instAddr.ContainingResource()
|
||||
if resourceAddr.Equal(prevResourceAddr) {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&listBuf, "\n -exclude=%q", resourceAddr.String())
|
||||
prevResourceAddr = resourceAddr
|
||||
}
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Moved resource instances excluded by targeting",
|
||||
fmt.Sprintf(
|
||||
"Resource instances in your current state have moved to new addresses in the latest configuration. OpenTofu must include those resource instances while planning in order to ensure a correct result, but your -exclude=... options exclude some of those resource instances.\n\nTo create a valid plan, either remove your -exclude=... options altogether or just specifically remove the following options:%s\n\nNote that removing these options may include further additional resource instances in your plan, in order to respect object dependencies.",
|
||||
listBuf.String(),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func (c *Context) postPlanValidateMoves(config *configs.Config, stmts []refactoring.MoveStatement, allInsts instances.Set) tfdiags.Diagnostics {
|
||||
return refactoring.ValidateMoves(stmts, config, allInsts)
|
||||
}
|
||||
@ -662,11 +743,11 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, o
|
||||
log.Printf("[DEBUG] Building and walking plan graph for %s", opts.Mode)
|
||||
|
||||
prevRunState = prevRunState.DeepCopy() // don't modify the caller's object when we process the moves
|
||||
moveStmts, moveResults := c.prePlanFindAndApplyMoves(config, prevRunState, opts.Targets)
|
||||
moveStmts, moveResults := c.prePlanFindAndApplyMoves(config, prevRunState)
|
||||
|
||||
// If resource targeting is in effect then it might conflict with the
|
||||
// move result.
|
||||
diags = diags.Append(c.prePlanVerifyTargetedMoves(moveResults, opts.Targets))
|
||||
diags = diags.Append(c.prePlanVerifyTargetedMoves(moveResults, opts.Targets, opts.Excludes))
|
||||
if diags.HasErrors() {
|
||||
// We'll return early here, because if we have any moved resource
|
||||
// instances excluded by targeting then planning is likely to encounter
|
||||
@ -765,6 +846,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
Excludes: opts.Excludes,
|
||||
ForceReplace: opts.ForceReplace,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
preDestroyRefresh: opts.PreDestroyRefresh,
|
||||
@ -778,16 +860,16 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
||||
return graph, walkPlan, diags
|
||||
case plans.RefreshOnlyMode:
|
||||
graph, diags := (&PlanGraphBuilder{
|
||||
Config: config,
|
||||
State: prevRunState,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
skipPlanChanges: true, // this activates "refresh only" mode.
|
||||
Operation: walkPlan,
|
||||
ExternalReferences: opts.ExternalReferences,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
Config: config,
|
||||
State: prevRunState,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
Excludes: opts.Excludes,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
skipPlanChanges: true, // this activates "refresh only" mode.
|
||||
Operation: walkPlan,
|
||||
ExternalReferences: opts.ExternalReferences,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
return graph, walkPlan, diags
|
||||
case plans.DestroyMode:
|
||||
@ -797,6 +879,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
Excludes: opts.Excludes,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
Operation: walkPlanDestroy,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
|
@ -1574,9 +1574,9 @@ func TestContext2Plan_movedResourceUntargeted(t *testing.T) {
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Resource targeting is in effect",
|
||||
`You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
`You are creating a plan with either the -target option or the -exclude option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
|
||||
The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
The -target and -exclude options are not for routine use, and are provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
),
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
@ -1614,9 +1614,9 @@ Note that adding these options may include further additional resource instances
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Resource targeting is in effect",
|
||||
`You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
`You are creating a plan with either the -target option or the -exclude option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
|
||||
The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
The -target and -exclude options are not for routine use, and are provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
),
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
@ -1654,9 +1654,9 @@ Note that adding these options may include further additional resource instances
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Resource targeting is in effect",
|
||||
`You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
`You are creating a plan with either the -target option or the -exclude option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
|
||||
The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
The -target and -exclude options are not for routine use, and are provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
),
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
@ -1702,9 +1702,167 @@ Note that adding these options may include further additional resource instances
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Resource targeting is in effect",
|
||||
`You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
`You are creating a plan with either the -target option or the -exclude option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
|
||||
The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
The -target and -exclude options are not for routine use, and are provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
),
|
||||
// ...but now we have no error about test_object.a
|
||||
}.ForRPC()
|
||||
|
||||
if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
}
|
||||
})
|
||||
t.Run("excluding instance A", func(t *testing.T) {
|
||||
_, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
// NOTE: addrA isn't excluded, and it's pending move to addrB
|
||||
// and so this plan request is invalid.
|
||||
addrA,
|
||||
},
|
||||
})
|
||||
diags.Sort()
|
||||
|
||||
// We're semi-abusing "ForRPC" here just to get diagnostics that are
|
||||
// more easily comparable than the various different diagnostics types
|
||||
// tfdiags uses internally. The RPC-friendly diagnostics are also
|
||||
// comparison-friendly, by discarding all of the dynamic type information.
|
||||
gotDiags := diags.ForRPC()
|
||||
wantDiags := tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Resource targeting is in effect",
|
||||
`You are creating a plan with either the -target option or the -exclude option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
|
||||
The -target and -exclude options are not for routine use, and are provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
),
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Moved resource instances excluded by targeting",
|
||||
`Resource instances in your current state have moved to new addresses in the latest configuration. OpenTofu must include those resource instances while planning in order to ensure a correct result, but your -exclude=... options exclude some of those resource instances.
|
||||
|
||||
To create a valid plan, either remove your -exclude=... options altogether or just specifically remove the following options:
|
||||
-exclude="test_object.a"
|
||||
|
||||
Note that removing these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
|
||||
),
|
||||
}.ForRPC()
|
||||
|
||||
if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
}
|
||||
})
|
||||
t.Run("excluding instance B", func(t *testing.T) {
|
||||
_, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrB,
|
||||
// NOTE: addrB is excluded here, and it's pending move from
|
||||
// addrA and so this plan request is invalid.
|
||||
},
|
||||
})
|
||||
diags.Sort()
|
||||
|
||||
// We're semi-abusing "ForRPC" here just to get diagnostics that are
|
||||
// more easily comparable than the various different diagnostics types
|
||||
// tfdiags uses internally. The RPC-friendly diagnostics are also
|
||||
// comparison-friendly, by discarding all of the dynamic type information.
|
||||
gotDiags := diags.ForRPC()
|
||||
wantDiags := tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Resource targeting is in effect",
|
||||
`You are creating a plan with either the -target option or the -exclude option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
|
||||
The -target and -exclude options are not for routine use, and are provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
),
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Moved resource instances excluded by targeting",
|
||||
`Resource instances in your current state have moved to new addresses in the latest configuration. OpenTofu must include those resource instances while planning in order to ensure a correct result, but your -exclude=... options exclude some of those resource instances.
|
||||
|
||||
To create a valid plan, either remove your -exclude=... options altogether or just specifically remove the following options:
|
||||
-exclude="test_object.b"
|
||||
|
||||
Note that removing these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
|
||||
),
|
||||
}.ForRPC()
|
||||
|
||||
if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
}
|
||||
})
|
||||
t.Run("excluding both addresses", func(t *testing.T) {
|
||||
_, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
// NOTE: both addrA nor addrB are excluded here, but there's
|
||||
// a pending move between them and so this is invalid.
|
||||
addrA,
|
||||
addrB,
|
||||
},
|
||||
})
|
||||
diags.Sort()
|
||||
|
||||
// We're semi-abusing "ForRPC" here just to get diagnostics that are
|
||||
// more easily comparable than the various different diagnostics types
|
||||
// tfdiags uses internally. The RPC-friendly diagnostics are also
|
||||
// comparison-friendly, by discarding all of the dynamic type information.
|
||||
gotDiags := diags.ForRPC()
|
||||
wantDiags := tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Resource targeting is in effect",
|
||||
`You are creating a plan with either the -target option or the -exclude option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
|
||||
The -target and -exclude options are not for routine use, and are provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
),
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Moved resource instances excluded by targeting",
|
||||
`Resource instances in your current state have moved to new addresses in the latest configuration. OpenTofu must include those resource instances while planning in order to ensure a correct result, but your -exclude=... options exclude some of those resource instances.
|
||||
|
||||
To create a valid plan, either remove your -exclude=... options altogether or just specifically remove the following options:
|
||||
-exclude="test_object.a"
|
||||
-exclude="test_object.b"
|
||||
|
||||
Note that removing these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
|
||||
),
|
||||
}.ForRPC()
|
||||
|
||||
if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
|
||||
t.Errorf("wrong diagnostics\n%s", diff)
|
||||
}
|
||||
})
|
||||
t.Run("without excluding either instance", func(t *testing.T) {
|
||||
// The error messages in the other subtests above suggest removing
|
||||
// addresses to the set of excludes. This additional test makes sure that
|
||||
// following that advice actually leads to a valid result.
|
||||
_, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
mustResourceInstanceAddr("test_object.unrelated"),
|
||||
// This time we're excluding neither address,
|
||||
// to get the same effect an end-user would get if following
|
||||
// the advice in our error message in the other subtests.
|
||||
},
|
||||
})
|
||||
diags.Sort()
|
||||
|
||||
// We're semi-abusing "ForRPC" here just to get diagnostics that are
|
||||
// more easily comparable than the various different diagnostics types
|
||||
// tfdiags uses internally. The RPC-friendly diagnostics are also
|
||||
// comparison-friendly, by discarding all of the dynamic type information.
|
||||
gotDiags := diags.ForRPC()
|
||||
wantDiags := tfdiags.Diagnostics{
|
||||
// Still get the warning about the -target option...
|
||||
tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Resource targeting is in effect",
|
||||
`You are creating a plan with either the -target option or the -exclude option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
|
||||
|
||||
The -target and -exclude options are not for routine use, and are provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
|
||||
),
|
||||
// ...but now we have no error about test_object.a
|
||||
}.ForRPC()
|
||||
@ -1759,7 +1917,7 @@ resource "test_object" "b" {
|
||||
},
|
||||
})
|
||||
|
||||
_, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Targets: []addrs.Targetable{
|
||||
addrA,
|
||||
@ -1767,6 +1925,79 @@ resource "test_object" "b" {
|
||||
})
|
||||
//
|
||||
assertNoErrors(t, diags)
|
||||
|
||||
if len(plan.Changes.Resources) != 1 {
|
||||
t.Fatalf("expected 1 resource change, but got %d", len(plan.Changes.Resources))
|
||||
}
|
||||
|
||||
instPlan := plan.Changes.ResourceInstance(addrA)
|
||||
if instPlan == nil {
|
||||
t.Fatalf("expected plan for %s; but got none", addrA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_excludedResourceSchemaChange(t *testing.T) {
|
||||
// an excluded resource which requires a schema migration should not
|
||||
// block planning due external changes in the plan.
|
||||
addrA := mustResourceInstanceAddr("test_object.a")
|
||||
addrB := mustResourceInstanceAddr("test_object.b")
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
resource "test_object" "a" {
|
||||
}
|
||||
resource "test_object" "b" {
|
||||
}`,
|
||||
})
|
||||
|
||||
state := states.BuildState(func(s *states.SyncState) {
|
||||
s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{}`),
|
||||
Status: states.ObjectReady,
|
||||
}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
|
||||
s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{
|
||||
// old_list is no longer in the schema
|
||||
AttrsJSON: []byte(`{"old_list":["used to be","a list here"]}`),
|
||||
Status: states.ObjectReady,
|
||||
}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
|
||||
})
|
||||
|
||||
p := simpleMockProvider()
|
||||
|
||||
// external changes trigger a "drift report", but because test_object.b was
|
||||
// excluded, the state was not fixed to match the schema and cannot be
|
||||
// deocded for the report.
|
||||
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
||||
var resp providers.ReadResourceResponse
|
||||
obj := req.PriorState.AsValueMap()
|
||||
// test_number changed externally
|
||||
obj["test_number"] = cty.NumberIntVal(1)
|
||||
resp.NewState = cty.ObjectVal(obj)
|
||||
return resp
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrB,
|
||||
},
|
||||
})
|
||||
//
|
||||
assertNoErrors(t, diags)
|
||||
|
||||
if len(plan.Changes.Resources) != 1 {
|
||||
t.Fatalf("expected 1 resource change, but got %d", len(plan.Changes.Resources))
|
||||
}
|
||||
|
||||
instPlan := plan.Changes.ResourceInstance(addrA)
|
||||
if instPlan == nil {
|
||||
t.Fatalf("expected plan for %s; but got none", addrA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_movedResourceRefreshOnly(t *testing.T) {
|
||||
|
@ -4068,6 +4068,57 @@ func TestContext2Plan_targeted(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// All exclude flag tests in this file are inspired by a counterpart target flag test
|
||||
// Usually that test exists right before the exclude flag test
|
||||
|
||||
func TestContext2Plan_excluded(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted")
|
||||
p := testProvider("aws")
|
||||
p.PlanResourceChangeFn = testDiffFn
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(addrs.ManagedResourceMode, "aws_instance", "foo"),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors: %s", diags.Err())
|
||||
}
|
||||
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
||||
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),
|
||||
"type": cty.UnknownVal(cty.String),
|
||||
}), ric.After)
|
||||
default:
|
||||
t.Fatal("unknown instance:", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that targeting a module properly plans any inputs that depend
|
||||
// on another module.
|
||||
func TestContext2Plan_targetedCrossModule(t *testing.T) {
|
||||
@ -4123,6 +4174,33 @@ func TestContext2Plan_targetedCrossModule(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test that excluding a module properly plans and excludes any
|
||||
// dependent modules.
|
||||
func TestContext2Plan_excludedCrossModule(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted-cross-module")
|
||||
p := testProvider("aws")
|
||||
p.PlanResourceChangeFn = testDiffFn
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Child("A", addrs.NoKey),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
if len(plan.Changes.Resources) != 0 {
|
||||
t.Fatal("expected 0 changes, got", len(plan.Changes.Resources))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_targetedModuleWithProvider(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted-module-with-provider")
|
||||
p := testProvider("null")
|
||||
@ -4173,6 +4251,56 @@ func TestContext2Plan_targetedModuleWithProvider(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_excludedModuleWithProvider(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted-module-with-provider")
|
||||
p := testProvider("null")
|
||||
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
||||
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{},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("null"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Child("child1", addrs.NoKey),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
schema := p.GetProviderSchemaResponse.ResourceTypes["null_resource"].Block
|
||||
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() != "module.child2.null_resource.foo" {
|
||||
t.Fatalf("unexpected resource: %s", ric.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_targetedOrphan(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted-orphan")
|
||||
p := testProvider("aws")
|
||||
@ -4238,6 +4366,71 @@ func TestContext2Plan_targetedOrphan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_excludedOrphan(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted-orphan")
|
||||
p := testProvider("aws")
|
||||
|
||||
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.opentofu.org/hashicorp/aws"]`),
|
||||
)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.nottargeted").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
||||
)
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.DestroyMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "nottargeted",
|
||||
),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
||||
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.orphan":
|
||||
if res.Action != plans.Delete {
|
||||
t.Fatalf("resource %s should be destroyed", ric.Addr)
|
||||
}
|
||||
default:
|
||||
t.Fatal("unknown instance:", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/hashicorp/terraform/issues/2538
|
||||
func TestContext2Plan_targetedModuleOrphan(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted-module-orphan")
|
||||
@ -4301,6 +4494,68 @@ func TestContext2Plan_targetedModuleOrphan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_excludedModuleOrphan(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted-module-orphan")
|
||||
p := testProvider("aws")
|
||||
|
||||
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.opentofu.org/hashicorp/aws"]`),
|
||||
)
|
||||
child.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.nottargeted").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
||||
)
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.DestroyMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Child("child", addrs.NoKey).Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "nottargeted",
|
||||
),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
||||
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() != "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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted-module-untargeted-variable")
|
||||
p := testProvider("aws")
|
||||
@ -4356,9 +4611,64 @@ func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_excludedModuleUntargetedVariable(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted-module-untargeted-variable")
|
||||
p := testProvider("aws")
|
||||
p.PlanResourceChangeFn = testDiffFn
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Excludes: []addrs.Targetable{
|
||||
// Exclude green instance, which should also exclude the dependent green module
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "green",
|
||||
),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
||||
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)
|
||||
}
|
||||
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{
|
||||
"id": cty.UnknownVal(cty.String),
|
||||
"type": cty.UnknownVal(cty.String),
|
||||
}), 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),
|
||||
"type": cty.UnknownVal(cty.String),
|
||||
}), ric.After)
|
||||
default:
|
||||
t.Fatal("unknown instance:", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that outputs missing references due to targeting are removed from
|
||||
// the graph.
|
||||
func TestContext2Plan_outputContainsTargetedResource(t *testing.T) {
|
||||
func TestContext2Plan_outputContainsUntargetedResource(t *testing.T) {
|
||||
m := testModule(t, "plan-untargeted-resource-output")
|
||||
p := testProvider("aws")
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
@ -4367,13 +4677,14 @@ func TestContext2Plan_outputContainsTargetedResource(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Targets: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Child("mod", addrs.NoKey).Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "a",
|
||||
),
|
||||
},
|
||||
})
|
||||
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("err: %s", diags)
|
||||
}
|
||||
@ -4386,6 +4697,77 @@ func TestContext2Plan_outputContainsTargetedResource(t *testing.T) {
|
||||
if got, want := diags[0].Description().Summary, "Resource targeting is in effect"; got != want {
|
||||
t.Errorf("wrong diagnostic summary %#v; want %#v", got, want)
|
||||
}
|
||||
|
||||
if len(plan.Changes.Outputs) != 0 {
|
||||
t.Fatalf("expected 0 output changes, but got %d", len(plan.Changes.Outputs))
|
||||
}
|
||||
|
||||
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
||||
ty := schema.ImpliedType()
|
||||
|
||||
res := plan.Changes.Resources[0]
|
||||
ric, err := res.Decode(ty)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ric.Addr.String() != "module.mod.aws_instance.a[0]" {
|
||||
t.Fatalf("unexpected resource :%s", ric.Addr)
|
||||
}
|
||||
if res.Action != plans.Create {
|
||||
t.Fatalf("resource %s should be deleted", ric.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_outputContainsExcludedResource(t *testing.T) {
|
||||
m := testModule(t, "plan-untargeted-resource-output")
|
||||
p := testProvider("aws")
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Child("mod", addrs.NoKey).Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "b",
|
||||
),
|
||||
},
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if len(plan.Changes.Outputs) != 0 {
|
||||
t.Fatalf("expected 0 output changes, but got %d", len(plan.Changes.Outputs))
|
||||
}
|
||||
|
||||
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
||||
ty := schema.ImpliedType()
|
||||
|
||||
res := plan.Changes.Resources[0]
|
||||
ric, err := res.Decode(ty)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ric.Addr.String() != "module.mod.aws_instance.a[0]" {
|
||||
t.Fatalf("unexpected resource :%s", ric.Addr)
|
||||
}
|
||||
if res.Action != plans.Create {
|
||||
t.Fatalf("resource %s should be deleted", ric.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/hashicorp/terraform/issues/4515
|
||||
@ -4442,6 +4824,60 @@ func TestContext2Plan_targetedOverTen(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/hashicorp/terraform/issues/4515 - Making sure it doesn't happen with exclude flag
|
||||
func TestContext2Plan_excludedOverTen(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted-over-ten")
|
||||
p := testProvider("aws")
|
||||
p.PlanResourceChangeFn = testDiffFn
|
||||
|
||||
state := states.NewState()
|
||||
root := state.EnsureModule(addrs.RootModuleInstance)
|
||||
for i := 0; i < 13; i++ {
|
||||
key := fmt.Sprintf("aws_instance.foo[%d]", i)
|
||||
id := fmt.Sprintf("i-abc%d", i)
|
||||
attrs := fmt.Sprintf(`{"id":"%s","type":"aws_instance"}`, id)
|
||||
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr(key).Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(attrs),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
|
||||
)
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(1),
|
||||
),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
||||
ty := schema.ImpliedType()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_provider(t *testing.T) {
|
||||
m := testModule(t, "plan-provider")
|
||||
p := testProvider("aws")
|
||||
@ -5975,6 +6411,72 @@ resource "aws_instance" "foo" {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_excludeExpandedAddress(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")
|
||||
|
||||
excludes := []addrs.Targetable{}
|
||||
exclude, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo[0]")
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.ErrWithWarnings())
|
||||
}
|
||||
excludes = append(excludes, exclude.Subject)
|
||||
|
||||
exclude, diags = addrs.ParseTargetStr("module.mod[2]")
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.ErrWithWarnings())
|
||||
}
|
||||
excludes = append(excludes, exclude.Subject)
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: excludes,
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.ErrWithWarnings())
|
||||
}
|
||||
|
||||
expected := map[string]plans.Action{
|
||||
// the whole mod[0], which was not excluded
|
||||
`module.mod[0].aws_instance.foo[0]`: plans.Create,
|
||||
`module.mod[0].aws_instance.foo[1]`: plans.Create,
|
||||
// the unexcluded mod[1] instance
|
||||
`module.mod[1].aws_instance.foo[1]`: plans.Create,
|
||||
// the whole of mod[2] was excluded
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_targetResourceInModuleInstance(t *testing.T) {
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
@ -6030,6 +6532,62 @@ resource "aws_instance" "foo" {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_excludeResourceInModuleInstance(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")
|
||||
|
||||
exclude, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo")
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.ErrWithWarnings())
|
||||
}
|
||||
|
||||
excludes := []addrs.Targetable{exclude.Subject}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: excludes,
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.ErrWithWarnings())
|
||||
}
|
||||
|
||||
expected := map[string]plans.Action{
|
||||
// the unexcluded instances from mod[0] and mod[2]
|
||||
`module.mod[0].aws_instance.foo`: plans.Create,
|
||||
`module.mod[2].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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_moduleRefIndex(t *testing.T) {
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
@ -6265,6 +6823,57 @@ func TestContext2Plan_targetedModuleInstance(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_excludedModuleInstance(t *testing.T) {
|
||||
m := testModule(t, "plan-targeted")
|
||||
p := testProvider("aws")
|
||||
p.PlanResourceChangeFn = testDiffFn
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "bar",
|
||||
),
|
||||
addrs.RootModuleInstance.Child("mod", addrs.IntKey(0)),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors: %s", diags.Err())
|
||||
}
|
||||
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Block
|
||||
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":
|
||||
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),
|
||||
"type": cty.UnknownVal(cty.String),
|
||||
}), ric.After)
|
||||
default:
|
||||
t.Fatal("unknown instance:", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_dataRefreshedInPlan(t *testing.T) {
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
|
@ -291,6 +291,95 @@ func TestContext2Refresh_targeted(t *testing.T) {
|
||||
}
|
||||
|
||||
expected := []string{"vpc-abc123", "i-abc123"}
|
||||
sort.Strings(expected)
|
||||
sort.Strings(refreshedResources)
|
||||
if !reflect.DeepEqual(refreshedResources, expected) {
|
||||
t.Fatalf("expected: %#v, got: %#v", expected, refreshedResources)
|
||||
}
|
||||
}
|
||||
|
||||
// All exclude flag tests in this file are inspired by a counterpart target flag test
|
||||
// Usually that test exists right before the exclude flag test
|
||||
|
||||
func TestContext2Refresh_excluded(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
||||
Provider: &configschema.Block{},
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_elb": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
"instances": {
|
||||
Type: cty.Set(cty.String),
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_instance": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
"vpc_id": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_vpc": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// This test uses the same setup as TestContext2Refresh_targeted, but here the resources that should be refreshed
|
||||
// are aws_vpc.metoo and aws_instance.notme. These are resources that are not aws_instance.me or dependent on it
|
||||
state := states.NewState()
|
||||
root := state.EnsureModule(addrs.RootModuleInstance)
|
||||
testSetResourceInstanceCurrent(root, "aws_vpc.metoo", `{"id":"vpc-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_instance.notme", `{"id":"i-bcd345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_instance.me", `{"id":"i-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_elb.meneither", `{"id":"lb-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
|
||||
m := testModule(t, "refresh-targeted")
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
refreshedResources := make([]string, 0, 2)
|
||||
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
||||
refreshedResources = append(refreshedResources, req.PriorState.GetAttr("id").AsString())
|
||||
return providers.ReadResourceResponse{
|
||||
NewState: req.PriorState,
|
||||
}
|
||||
}
|
||||
|
||||
_, diags := ctx.Refresh(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "me",
|
||||
),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("refresh errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
expected := []string{"vpc-abc123", "i-bcd345"}
|
||||
sort.Strings(expected)
|
||||
sort.Strings(refreshedResources)
|
||||
if !reflect.DeepEqual(refreshedResources, expected) {
|
||||
t.Fatalf("expected: %#v, got: %#v", expected, refreshedResources)
|
||||
}
|
||||
@ -386,6 +475,97 @@ func TestContext2Refresh_targetedCount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Refresh_excludedCount(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
||||
Provider: &configschema.Block{},
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_elb": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
"instances": {
|
||||
Type: cty.Set(cty.String),
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_instance": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
"vpc_id": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_vpc": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// This test uses the same setup as TestContext2Refresh_targetedCount, but here the resources that should be
|
||||
// refreshed are aws_vpc.metoo and aws_instance.notme. These are resources that are not aws_instance.me or
|
||||
// dependent on it
|
||||
state := states.NewState()
|
||||
root := state.EnsureModule(addrs.RootModuleInstance)
|
||||
testSetResourceInstanceCurrent(root, "aws_vpc.metoo", `{"id":"vpc-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_instance.notme", `{"id":"i-bcd345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_instance.me[0]", `{"id":"i-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_instance.me[1]", `{"id":"i-cde567"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_instance.me[2]", `{"id":"i-cde789"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_elb.meneither", `{"id":"lb-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
|
||||
m := testModule(t, "refresh-targeted-count")
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
refreshedResources := make([]string, 0, 2)
|
||||
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
||||
refreshedResources = append(refreshedResources, req.PriorState.GetAttr("id").AsString())
|
||||
return providers.ReadResourceResponse{
|
||||
NewState: req.PriorState,
|
||||
}
|
||||
}
|
||||
|
||||
_, diags := ctx.Refresh(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "me",
|
||||
),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("refresh errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
// Target didn't specify index, so we should exclude all instances of aws_instance.me
|
||||
expected := []string{
|
||||
"vpc-abc123",
|
||||
"i-bcd345",
|
||||
}
|
||||
sort.Strings(expected)
|
||||
sort.Strings(refreshedResources)
|
||||
if !reflect.DeepEqual(refreshedResources, expected) {
|
||||
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", refreshedResources, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Refresh_targetedCountIndex(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
||||
@ -463,6 +643,95 @@ func TestContext2Refresh_targetedCountIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
expected := []string{"vpc-abc123", "i-abc123"}
|
||||
sort.Strings(expected)
|
||||
sort.Strings(refreshedResources)
|
||||
if !reflect.DeepEqual(refreshedResources, expected) {
|
||||
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", refreshedResources, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Refresh_excludedCountIndex(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
||||
Provider: &configschema.Block{},
|
||||
ResourceTypes: map[string]*configschema.Block{
|
||||
"aws_elb": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
"instances": {
|
||||
Type: cty.Set(cty.String),
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_instance": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
"vpc_id": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_vpc": {
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// This test uses the same setup as TestContext2Refresh_targetedCountIndex, but here the resources that should be
|
||||
// refreshed are aws_vpc.metoo, aws_instance.notme, aws_instance.me[1] and aws_instance.me[2]. These are resources
|
||||
// that are not aws_instance.me[0] or dependent on it
|
||||
state := states.NewState()
|
||||
root := state.EnsureModule(addrs.RootModuleInstance)
|
||||
testSetResourceInstanceCurrent(root, "aws_vpc.metoo", `{"id":"vpc-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_instance.notme", `{"id":"i-bcd345"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_instance.me[0]", `{"id":"i-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_instance.me[1]", `{"id":"i-cde567"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_instance.me[2]", `{"id":"i-cde789"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
testSetResourceInstanceCurrent(root, "aws_elb.meneither", `{"id":"lb-abc123"}`, `provider["registry.opentofu.org/hashicorp/aws"]`)
|
||||
|
||||
m := testModule(t, "refresh-targeted-count")
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
refreshedResources := make([]string, 0, 2)
|
||||
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
|
||||
refreshedResources = append(refreshedResources, req.PriorState.GetAttr("id").AsString())
|
||||
return providers.ReadResourceResponse{
|
||||
NewState: req.PriorState,
|
||||
}
|
||||
}
|
||||
|
||||
_, diags := ctx.Refresh(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "me", addrs.IntKey(0),
|
||||
),
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("refresh errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
expected := []string{"vpc-abc123", "i-bcd345", "i-cde567", "i-cde789"}
|
||||
sort.Strings(expected)
|
||||
sort.Strings(refreshedResources)
|
||||
if !reflect.DeepEqual(refreshedResources, expected) {
|
||||
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", refreshedResources, expected)
|
||||
}
|
||||
|
@ -46,6 +46,12 @@ type ApplyGraphBuilder struct {
|
||||
// outputs should go into the diff so that this is unnecessary.
|
||||
Targets []addrs.Targetable
|
||||
|
||||
// Excludes are resources to exclude. This is only required to make sure
|
||||
// unnecessary outputs aren't included in the apply graph. The plan
|
||||
// builder successfully handles targeting resources. In the future,
|
||||
// outputs should go into the diff so that this is unnecessary.
|
||||
Excludes []addrs.Targetable
|
||||
|
||||
// ForceReplace are the resource instance addresses that the user
|
||||
// requested to force replacement for when creating the plan, if any.
|
||||
// The apply step refers to these as part of verifying that the planned
|
||||
@ -192,7 +198,7 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
||||
&pruneUnusedNodesTransformer{},
|
||||
|
||||
// Target
|
||||
&TargetsTransformer{Targets: b.Targets},
|
||||
&TargetingTransformer{Targets: b.Targets, Excludes: b.Excludes},
|
||||
|
||||
// Close opened plugin connections
|
||||
&CloseProviderTransformer{},
|
||||
|
@ -456,7 +456,42 @@ func TestApplyGraphBuilder_targetModule(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
testGraphNotContains(t, g, "module.child1.output.instance_id")
|
||||
testGraphNotContains(t, g, "test_object.foo")
|
||||
}
|
||||
|
||||
func TestApplyGraphBuilder_excludeModule(t *testing.T) {
|
||||
changes := &plans.Changes{
|
||||
Resources: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: mustResourceInstanceAddr("test_object.foo"),
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Update,
|
||||
},
|
||||
},
|
||||
{
|
||||
Addr: mustResourceInstanceAddr("module.child2.test_object.foo"),
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Update,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b := &ApplyGraphBuilder{
|
||||
Config: testModule(t, "graph-builder-apply-target-module"),
|
||||
Changes: changes,
|
||||
Plugins: simpleMockPluginLibrary(),
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
|
||||
},
|
||||
}
|
||||
|
||||
g, err := b.Build(addrs.RootModuleInstance)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
testGraphNotContains(t, g, "mod.child2.test_object.foo")
|
||||
}
|
||||
|
||||
// Ensure that an update resulting from the removal of a resource happens after
|
||||
|
@ -46,6 +46,9 @@ type PlanGraphBuilder struct {
|
||||
// Targets are resources to target
|
||||
Targets []addrs.Targetable
|
||||
|
||||
// Excludes are resources to exclude
|
||||
Excludes []addrs.Targetable
|
||||
|
||||
// ForceReplace are resource instances where if we would normally have
|
||||
// generated a NoOp or Update action then we'll force generating a replace
|
||||
// action instead. Create and Delete actions are not affected.
|
||||
@ -226,7 +229,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||
&attachDataResourceDependsOnTransformer{},
|
||||
|
||||
// DestroyEdgeTransformer is only required during a plan so that the
|
||||
// TargetsTransformer can determine which nodes to keep in the graph.
|
||||
// TargetingTransformer can determine which nodes to keep in the graph.
|
||||
&DestroyEdgeTransformer{
|
||||
Operation: b.Operation,
|
||||
},
|
||||
@ -236,7 +239,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||
},
|
||||
|
||||
// Target
|
||||
&TargetsTransformer{Targets: b.Targets},
|
||||
&TargetingTransformer{Targets: b.Targets, Excludes: b.Excludes},
|
||||
|
||||
// Detect when create_before_destroy must be forced on for a particular
|
||||
// node due to dependency edges, to avoid graph cycles during apply.
|
||||
|
@ -195,6 +195,27 @@ func TestPlanGraphBuilder_targetModule(t *testing.T) {
|
||||
testGraphNotContains(t, g, "module.child1.test_object.foo")
|
||||
}
|
||||
|
||||
func TestPlanGraphBuilder_excludeModule(t *testing.T) {
|
||||
b := &PlanGraphBuilder{
|
||||
Config: testModule(t, "graph-builder-plan-target-module-provider"),
|
||||
Plugins: simpleMockPluginLibrary(),
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Child("child1", addrs.NoKey),
|
||||
},
|
||||
Operation: walkPlan,
|
||||
}
|
||||
|
||||
g, err := b.Build(addrs.RootModuleInstance)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("Graph: %s", g.String())
|
||||
|
||||
testGraphNotContains(t, g, `module.child1.provider["registry.opentofu.org/hashicorp/test"]`)
|
||||
testGraphNotContains(t, g, "module.child1.test_object.foo")
|
||||
}
|
||||
|
||||
func TestPlanGraphBuilder_forEach(t *testing.T) {
|
||||
awsProvider := mockProviderWithResourceTypeSchema("aws_instance", simpleTestSchema())
|
||||
|
||||
|
@ -69,6 +69,9 @@ type NodeAbstractResource struct {
|
||||
// Set from GraphNodeTargetable
|
||||
Targets []addrs.Targetable
|
||||
|
||||
// Set from GraphNodeTargetable
|
||||
Excludes []addrs.Targetable
|
||||
|
||||
// Set from AttachDataResourceDependsOn
|
||||
dependsOn []addrs.ConfigResource
|
||||
forceDependsOn bool
|
||||
@ -394,6 +397,11 @@ func (n *NodeAbstractResource) SetTargets(targets []addrs.Targetable) {
|
||||
n.Targets = targets
|
||||
}
|
||||
|
||||
// GraphNodeTargetable
|
||||
func (n *NodeAbstractResource) SetExcludes(excludes []addrs.Targetable) {
|
||||
n.Excludes = excludes
|
||||
}
|
||||
|
||||
// graphNodeAttachDataResourceDependsOn
|
||||
func (n *NodeAbstractResource) AttachDataResourceDependsOn(deps []addrs.ConfigResource, force bool) {
|
||||
n.dependsOn = deps
|
||||
|
@ -413,7 +413,7 @@ func (n *nodeExpandPlannableResource) resourceInstanceSubgraph(ctx EvalContext,
|
||||
&AttachStateTransformer{State: state},
|
||||
|
||||
// Targeting
|
||||
&TargetsTransformer{Targets: n.Targets},
|
||||
&TargetingTransformer{Targets: n.Targets, Excludes: n.Excludes},
|
||||
|
||||
// Connect references so ordering is correct
|
||||
&ReferenceTransformer{},
|
||||
|
@ -1,6 +1,10 @@
|
||||
# This resource was previously "created" and the fixture represents
|
||||
# it being destroyed subsequently
|
||||
# These resources were previously "created" and the fixture represents
|
||||
# them being destroyed subsequently
|
||||
|
||||
/*resource "aws_instance" "orphan" {*/
|
||||
/*foo = "bar"*/
|
||||
/*}*/
|
||||
#resource "aws_instance" "orphan" {
|
||||
# foo = "bar"
|
||||
#}
|
||||
|
||||
#resource "aws_instance" "nottargeted" {
|
||||
# foo = "bar"
|
||||
#}
|
||||
|
@ -4,5 +4,5 @@ module "mod" {
|
||||
|
||||
|
||||
resource "aws_instance" "c" {
|
||||
name = "${module.mod.output}"
|
||||
foo = "${module.mod.output}"
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ func TestCloseProviderTransformer_withTargets(t *testing.T) {
|
||||
&MissingProviderTransformer{},
|
||||
&ProviderTransformer{},
|
||||
&CloseProviderTransformer{},
|
||||
&TargetsTransformer{
|
||||
&TargetingTransformer{
|
||||
Targets: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "something", "else",
|
||||
@ -166,6 +166,36 @@ func TestCloseProviderTransformer_withTargets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseProviderTransformer_withExcludes(t *testing.T) {
|
||||
mod := testModule(t, "transform-provider-basic")
|
||||
|
||||
g := testProviderTransformerGraph(t, mod)
|
||||
transforms := []GraphTransformer{
|
||||
&MissingProviderTransformer{},
|
||||
&ProviderTransformer{},
|
||||
&CloseProviderTransformer{},
|
||||
&TargetingTransformer{
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "web",
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tr := range transforms {
|
||||
if err := tr.Transform(g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(``)
|
||||
if actual != expected {
|
||||
t.Fatalf("expected:%s\n\ngot:\n\n%s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingProviderTransformer(t *testing.T) {
|
||||
mod := testModule(t, "transform-provider-missing")
|
||||
|
||||
|
@ -13,44 +13,52 @@ import (
|
||||
)
|
||||
|
||||
// GraphNodeTargetable is an interface for graph nodes to implement when they
|
||||
// need to be told about incoming targets. This is useful for nodes that need
|
||||
// to respect targets as they dynamically expand. Note that the list of targets
|
||||
// provided will contain every target provided, and each implementing graph
|
||||
// node must filter this list to targets considered relevant.
|
||||
// need to be told about incoming targets or excluded targets. This is useful for
|
||||
// nodes that need to respect targets and excludes as they dynamically expand.
|
||||
// Note that the lists of targets and excludes provided will contain every target
|
||||
// or every exclude provided, and each implementing graph node must filter this
|
||||
// list to targets considered relevant.
|
||||
type GraphNodeTargetable interface {
|
||||
SetTargets([]addrs.Targetable)
|
||||
SetExcludes([]addrs.Targetable)
|
||||
}
|
||||
|
||||
// TargetsTransformer is a GraphTransformer that, when the user specifies a
|
||||
// list of resources to target, limits the graph to only those resources and
|
||||
// their dependencies.
|
||||
type TargetsTransformer struct {
|
||||
// TargetingTransformer is a GraphTransformer that, when the user specifies a
|
||||
// list of resources to target, or a list of resources to exclude, limits the
|
||||
// graph to only those resources and their dependencies (or in the case of
|
||||
// excludes - limits the graph to all resources that are not excluded or not
|
||||
// dependent on excluded resources).
|
||||
type TargetingTransformer struct {
|
||||
// List of targeted resource names specified by the user
|
||||
Targets []addrs.Targetable
|
||||
// List of excluded resource names specified by the user
|
||||
Excludes []addrs.Targetable
|
||||
}
|
||||
|
||||
func (t *TargetsTransformer) Transform(g *Graph) error {
|
||||
func (t *TargetingTransformer) Transform(g *Graph) error {
|
||||
var targetedNodes dag.Set
|
||||
if len(t.Targets) > 0 {
|
||||
targetedNodes, err := t.selectTargetedNodes(g, t.Targets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetedNodes = t.selectTargetedNodes(g, t.Targets)
|
||||
} else if len(t.Excludes) > 0 {
|
||||
targetedNodes = t.removeExcludedNodes(g, t.Excludes)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, v := range g.Vertices() {
|
||||
if !targetedNodes.Include(v) {
|
||||
log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v))
|
||||
g.Remove(v)
|
||||
}
|
||||
for _, v := range g.Vertices() {
|
||||
if !targetedNodes.Include(v) {
|
||||
log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v))
|
||||
g.Remove(v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns a set of targeted nodes. A targeted node is either addressed
|
||||
// directly, address indirectly via its container, or it's a dependency of a
|
||||
// targeted node.
|
||||
func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (dag.Set, error) {
|
||||
// selectTargetedNodes goes over a list of resource and modules targeted with a -target flag, and returns a set of
|
||||
// targeted nodes. A targeted node is either addressed directly, address indirectly via its container, or it's a
|
||||
// dependency of a targeted node.
|
||||
func (t *TargetingTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) dag.Set {
|
||||
targetedNodes := make(dag.Set)
|
||||
|
||||
vertices := g.Vertices()
|
||||
@ -73,10 +81,118 @@ func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targeta
|
||||
}
|
||||
}
|
||||
|
||||
targetedOutputNodes := t.getTargetedOutputNodes(targetedNodes, g)
|
||||
for _, outputNode := range targetedOutputNodes {
|
||||
targetedNodes.Add(outputNode)
|
||||
}
|
||||
|
||||
return targetedNodes
|
||||
}
|
||||
|
||||
func (t *TargetingTransformer) getTargetableNodeResourceAddr(v dag.Vertex) addrs.Targetable {
|
||||
switch r := v.(type) {
|
||||
case GraphNodeResourceInstance:
|
||||
return r.ResourceInstanceAddr()
|
||||
case GraphNodeConfigResource:
|
||||
return r.ResourceAddr()
|
||||
default:
|
||||
// Only resource and resource instance nodes can be targeted.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// removeExcludedNodes goes over a list of excluded resources and modules, and returns a set of targeted nodes to be
|
||||
// used for resource targeting. An excluded resource is either addressed directly, addressed indirectly via its
|
||||
// container, or it's dependent on an excluded node. The rest are the targeted nodes used for resource targeting
|
||||
func (t *TargetingTransformer) removeExcludedNodes(g *Graph, excludes []addrs.Targetable) dag.Set {
|
||||
targetedNodes := make(dag.Set)
|
||||
excludedNodes := make(dag.Set)
|
||||
targetableNodes := make(dag.Set)
|
||||
|
||||
vertices := g.Vertices()
|
||||
|
||||
// Step 1: Find all excluded targetable nodes, and their descendants
|
||||
for _, v := range vertices {
|
||||
vertexAddr := t.getTargetableNodeResourceAddr(v)
|
||||
if vertexAddr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
targetableNodes.Add(v)
|
||||
|
||||
nodeExcluded := t.nodeIsExcluded(vertexAddr, excludes)
|
||||
if nodeExcluded {
|
||||
excludedNodes.Add(v)
|
||||
}
|
||||
|
||||
if nodeExcluded || t.nodeDescendantsExcluded(vertexAddr, excludes) {
|
||||
deps, _ := g.Descendents(v)
|
||||
for _, d := range deps {
|
||||
// In general, we'd like to exclude any descendant targetable node of the current node.
|
||||
// We exclude any resource dependent on this resource (which is more general than resources dependent
|
||||
// on the resource instance, but is in-line with how -target works).
|
||||
//
|
||||
// The exception to this is when excluding a specific instance of a resource that has multiple instances.
|
||||
// During apply, the specific instance tofu.NodeApplyableResourceInstance would be dependent on the
|
||||
// resource tofu.nodeExpandApplyableResource.
|
||||
// Since we do not want to exclude all resource instances (other than the ones that we've explicitly
|
||||
// excluded), we should only exclude dependents whose target is not contained in the current node.
|
||||
depVertexAddr := t.getTargetableNodeResourceAddr(d)
|
||||
if depVertexAddr != nil && !vertexAddr.TargetContains(depVertexAddr) {
|
||||
excludedNodes.Add(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Of the targetable nodes that were not excluded, build the graph similarly to -target
|
||||
for _, v := range targetableNodes {
|
||||
if !excludedNodes.Include(v) {
|
||||
targetedNodes.Add(v)
|
||||
|
||||
// We inform nodes that ask about the list of excludes - helps for nodes
|
||||
// that need to dynamically expand. Note that this only occurs for nodes
|
||||
// that are targetable and we didn't exclude
|
||||
if tn, ok := v.(GraphNodeTargetable); ok {
|
||||
tn.SetExcludes(excludes)
|
||||
}
|
||||
|
||||
deps, _ := g.Ancestors(v)
|
||||
for _, d := range deps {
|
||||
targetedNodes.Add(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Add outputs
|
||||
targetedOutputNodes := t.getTargetedOutputNodes(targetedNodes, g)
|
||||
for _, outputNode := range targetedOutputNodes {
|
||||
targetedNodes.Add(outputNode)
|
||||
}
|
||||
|
||||
return targetedNodes
|
||||
}
|
||||
|
||||
func (t *TargetingTransformer) getTargetedOutputNodes(targetedNodes dag.Set, graph *Graph) dag.Set {
|
||||
// It is expected that outputs which are only derived from targeted
|
||||
// resources are also updated. While we don't include any other possible
|
||||
// side effects from the targeted nodes, these are added because outputs
|
||||
// cannot be targeted on their own.
|
||||
//
|
||||
// Note: This behaviour has some quirks, as there are specific cases where
|
||||
// you would think an output should not be updated, but it is
|
||||
// For example, when there's a module call with an input that is dependent
|
||||
// on a root resource, and only the root resource is targeted, any output
|
||||
// that depends on a module output might be updated, if said module output
|
||||
// does not depend on any resource of the module itself.
|
||||
// Right now, we will not change this behaviour, as this has been the
|
||||
// behaviour for quite a while. A possible fix could be a more detailed
|
||||
// analysis of the outputs, and making sure that module outputs are only
|
||||
// referenced if any of the targeted nodes is in said module
|
||||
|
||||
targetedOutputNodes := make(dag.Set)
|
||||
vertices := graph.Vertices()
|
||||
|
||||
// Start by finding the root module output nodes themselves
|
||||
for _, v := range vertices {
|
||||
// outputs are all temporary value types
|
||||
@ -93,7 +209,7 @@ func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targeta
|
||||
|
||||
// If this output is descended only from targeted resources, then we
|
||||
// will keep it
|
||||
deps, _ := g.Ancestors(v)
|
||||
deps, _ := graph.Ancestors(v)
|
||||
found := 0
|
||||
for _, d := range deps {
|
||||
switch d.(type) {
|
||||
@ -115,17 +231,65 @@ func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targeta
|
||||
|
||||
if found > 0 {
|
||||
// we found an output we can keep; add it, and all it's dependencies
|
||||
targetedNodes.Add(v)
|
||||
targetedOutputNodes.Add(v)
|
||||
for _, d := range deps {
|
||||
targetedNodes.Add(d)
|
||||
targetedOutputNodes.Add(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targetedNodes, nil
|
||||
return targetedOutputNodes
|
||||
}
|
||||
|
||||
func (t *TargetsTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetable) bool {
|
||||
func (t *TargetingTransformer) nodeIsExcluded(vertexAddr addrs.Targetable, excludes []addrs.Targetable) bool {
|
||||
for _, excludeAddr := range excludes {
|
||||
if excludeAddr.TargetContains(vertexAddr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *TargetingTransformer) nodeDescendantsExcluded(vertexAddr addrs.Targetable, excludes []addrs.Targetable) bool {
|
||||
for _, excludeAddr := range excludes {
|
||||
// The behaviour here is a bit different from targets.
|
||||
// Before expansion - We'd like to only exclude resources that were excluded by module or resource.
|
||||
// If the excluded target is an AbsResourceInstance, then we'd want to skip exclude until we expand the resource
|
||||
// After expansion - We'd like to exclude any vertex that contains the exclude address
|
||||
// Since before expansion the vertexAddr is without an index, then if the excludeAddr is an instance, it will
|
||||
// only contain vertexAddr if its key is NoKey
|
||||
// So - a simple TargetContains here should be enough, both before and after expansion
|
||||
|
||||
if _, ok := vertexAddr.(addrs.ConfigResource); ok {
|
||||
// Before expansion happens, we only have nodes that know their
|
||||
// ConfigResource address. We need to take the more specific
|
||||
// target addresses and generalize them in order to compare with a
|
||||
// ConfigResource.
|
||||
//
|
||||
// If the excluded target, in is generalized form, contains the vertex address, then we know that we could remove the descendants
|
||||
// even if we don't remove the node itself from the graph. However, this could cause cases where too many resources are excluded.
|
||||
// For example, with -exclude=null_resource.a[1], and a null_resource.b[*] for which each instance depends on a single null_resource.a instance,
|
||||
// all null_resource.b instances will be excluded. This is not accurate, but is in line with -target today, which over-targets dependencies
|
||||
switch target := excludeAddr.(type) {
|
||||
case addrs.AbsResourceInstance:
|
||||
excludeAddr = target.ContainingResource().Config()
|
||||
case addrs.AbsResource:
|
||||
excludeAddr = target.Config()
|
||||
case addrs.ModuleInstance:
|
||||
excludeAddr = target.Module()
|
||||
}
|
||||
}
|
||||
|
||||
if excludeAddr.TargetContains(vertexAddr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *TargetingTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetable) bool {
|
||||
var vertexAddr addrs.Targetable
|
||||
switch r := v.(type) {
|
||||
case GraphNodeResourceInstance:
|
||||
|
@ -38,7 +38,7 @@ func TestTargetsTransformer(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
transform := &TargetsTransformer{
|
||||
transform := &TargetingTransformer{
|
||||
Targets: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "me",
|
||||
@ -63,6 +63,64 @@ aws_vpc.me
|
||||
}
|
||||
}
|
||||
|
||||
func TestTargetsTransformerExclude(t *testing.T) {
|
||||
mod := testModule(t, "transform-targets-basic")
|
||||
|
||||
g := Graph{Path: addrs.RootModuleInstance}
|
||||
{
|
||||
tf := &ConfigTransformer{Config: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &AttachResourceConfigTransformer{Config: mod}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &ReferenceTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &TargetingTransformer{
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "me",
|
||||
),
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_vpc", "notme",
|
||||
),
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_subnet", "notme",
|
||||
),
|
||||
addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "notme",
|
||||
),
|
||||
},
|
||||
}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(`
|
||||
aws_subnet.me
|
||||
aws_vpc.me
|
||||
aws_vpc.me
|
||||
`)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\nexpected:\n%s\n\ngot:\n%s\n", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTargetsTransformer_downstream(t *testing.T) {
|
||||
mod := testModule(t, "transform-targets-downstream")
|
||||
|
||||
@ -103,7 +161,7 @@ func TestTargetsTransformer_downstream(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
transform := &TargetsTransformer{
|
||||
transform := &TargetingTransformer{
|
||||
Targets: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.
|
||||
Child("child", addrs.NoKey).
|
||||
@ -135,7 +193,77 @@ output.grandchild_id (expand)
|
||||
}
|
||||
}
|
||||
|
||||
// This tests the TargetsTransformer targeting a whole module,
|
||||
func TestTargetsTransformer_downstreamExclude(t *testing.T) {
|
||||
mod := testModule(t, "transform-targets-downstream")
|
||||
|
||||
g := Graph{Path: addrs.RootModuleInstance}
|
||||
{
|
||||
transform := &ConfigTransformer{Config: mod}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("%T failed: %s", transform, err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &AttachResourceConfigTransformer{Config: mod}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("%T failed: %s", transform, err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &AttachResourceConfigTransformer{Config: mod}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("%T failed: %s", transform, err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &OutputTransformer{Config: mod}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("%T failed: %s", transform, err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &ReferenceTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &TargetingTransformer{
|
||||
Excludes: []addrs.Targetable{
|
||||
addrs.RootModuleInstance.Resource(addrs.ManagedResourceMode, "aws_instance", "foo"),
|
||||
addrs.RootModuleInstance.
|
||||
Child("child", addrs.NoKey).
|
||||
Resource(addrs.ManagedResourceMode, "aws_instance", "foo"),
|
||||
},
|
||||
}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("%T failed: %s", transform, err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
// Even though we only asked to exclude all resources in root and child, only including the grandchild resource
|
||||
// all of the outputs that descend from it are also targeted.
|
||||
expected := strings.TrimSpace(`
|
||||
module.child.module.grandchild.aws_instance.foo
|
||||
module.child.module.grandchild.output.id (expand)
|
||||
module.child.module.grandchild.aws_instance.foo
|
||||
module.child.output.grandchild_id (expand)
|
||||
module.child.module.grandchild.output.id (expand)
|
||||
output.grandchild_id (expand)
|
||||
module.child.output.grandchild_id (expand)
|
||||
`)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\nexpected:\n%s\n\ngot:\n%s\n", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
// This tests the TargetingTransformer targeting a whole module,
|
||||
// rather than a resource within a module instance.
|
||||
func TestTargetsTransformer_wholeModule(t *testing.T) {
|
||||
mod := testModule(t, "transform-targets-downstream")
|
||||
@ -177,7 +305,7 @@ func TestTargetsTransformer_wholeModule(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
transform := &TargetsTransformer{
|
||||
transform := &TargetingTransformer{
|
||||
Targets: []addrs.Targetable{
|
||||
addrs.RootModule.
|
||||
Child("child").
|
||||
|
@ -123,6 +123,14 @@ In addition to alternate [planning modes](#planning-modes), there are several op
|
||||
Use `-target=ADDRESS` in exceptional circumstances only, such as recovering from mistakes or working around OpenTofu limitations. Refer to [Resource Targeting](#resource-targeting) for more details.
|
||||
:::
|
||||
|
||||
- `-exclude=ADDRESS` - Instructs OpenTofu to focus its planning efforts only
|
||||
on resource instances which do not match the given excluded address, and that
|
||||
do not depend on any such resources or modules that were excluded.
|
||||
|
||||
:::note
|
||||
Use `-exclude=ADDRESS` in exceptional circumstances only, such as recovering from mistakes or working around OpenTofu limitations. Refer to [Resource Targeting](#resource-targeting) for more details.
|
||||
:::
|
||||
|
||||
- `-var 'NAME=VALUE'` - Sets a value for a single
|
||||
[input variable](../../language/values/variables.mdx) declared in the
|
||||
root module of the configuration. Use this option multiple times to set
|
||||
@ -217,8 +225,18 @@ input variables, see
|
||||
|
||||
### Resource Targeting
|
||||
|
||||
You can use the `-target` option to focus OpenTofu's attention on only a
|
||||
subset of resources.
|
||||
You can use the `-target` or the `-exclude` option to trigger resource targeting,
|
||||
focusing OpenTofu's attention on only a subset of resources.
|
||||
Using the `-target` option will focus OpenTofu's attention only on resources and
|
||||
module that are directly targeted, or are dependencies of the target.
|
||||
Using the `-exclude` option will focus OpenTofu's attention only on resources and
|
||||
modules that are not directly excluded, and are not dependent on an excluded resource
|
||||
or module.
|
||||
|
||||
You can use multiple `-target` flags in order to target multiple resources and modules,
|
||||
and you can use multiple `-exclude` flags in order to exclude multiple resource and
|
||||
modules. You cannot use both `-target` and `-exclude` flags together.
|
||||
|
||||
You can use [resource address syntax](../../cli/state/resource-addressing.mdx)
|
||||
to specify the constraint. OpenTofu interprets the resource address as follows:
|
||||
|
||||
@ -238,18 +256,14 @@ to specify the constraint. OpenTofu interprets the resource address as follows:
|
||||
select all instances of all resources that belong to that module instance
|
||||
and all of its child module instances.
|
||||
|
||||
Once OpenTofu has selected one or more resource instances that you've directly
|
||||
targeted, it will also then extend the selection to include all other objects
|
||||
that those selections depend on either directly or indirectly.
|
||||
|
||||
This targeting capability is provided for exceptional circumstances, such
|
||||
as recovering from mistakes or working around OpenTofu limitations. It
|
||||
is _not recommended_ to use `-target` for routine operations, since this can
|
||||
lead to undetected configuration drift and confusion about how the true state
|
||||
of resources relates to configuration.
|
||||
is _not recommended_ to use `-target` or `-exclude` for routine operations, since
|
||||
this can lead to undetected configuration drift and confusion about how the true
|
||||
state of resources relates to configuration.
|
||||
|
||||
Instead of using `-target` as a means to operate on isolated portions of very
|
||||
large configurations, prefer instead to break large configurations into
|
||||
Instead of using `-target` or `-exclude` as a means to operate on isolated portions
|
||||
of very large configurations, prefer instead to break large configurations into
|
||||
several smaller configurations that can each be independently applied.
|
||||
[Data sources](../../language/data-sources/index.mdx) can be used to access
|
||||
information about resources created in other configurations, allowing
|
||||
|
@ -98,7 +98,7 @@ This object has two attributes:
|
||||
|
||||
The keys of the map (or all the values in the case of a set of strings) must
|
||||
be _known values_, or you will get an error message that `for_each` has dependencies
|
||||
that cannot be determined before apply, and a `-target` may be needed.
|
||||
that cannot be determined before apply, and a `-target`/`-exclude` may be needed.
|
||||
|
||||
`for_each` keys cannot be the result (or rely on the result of) of impure functions,
|
||||
including `uuid`, `bcrypt`, or `timestamp`, as their evaluation is deferred during the
|
||||
|
@ -90,7 +90,7 @@ round trip time for each resource is hundreds of milliseconds. On top of this,
|
||||
cloud providers almost always have API rate limiting so OpenTofu can only
|
||||
request a certain number of resources in a period of time. Larger users
|
||||
of OpenTofu make heavy use of the `-refresh=false` flag as well as the
|
||||
`-target` flag in order to work around this. In these scenarios, the cached
|
||||
`-target`/`-exclude` flags in order to work around this. In these scenarios, the cached
|
||||
state is treated as the record of truth.
|
||||
|
||||
## Syncing
|
||||
|
Loading…
Reference in New Issue
Block a user