Add count of forgotten resources to plan and apply outputs. (#2010)

Signed-off-by: Jarrett Duskey <jarrett@duskey.io>
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
Co-authored-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Jarrett Duskey 2025-02-24 14:53:32 -05:00 committed by GitHub
parent 23a26b0e58
commit ecd4dc5c61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
64 changed files with 283 additions and 27 deletions

View File

@ -8,13 +8,14 @@ UPGRADE NOTES:
NEW FEATURES:
- New builtin provider functions added ([#2306](https://github.com/opentofu/opentofu/pull/2306)) :
- `provider::terraform::decode_tfvars` - Decode a TFVars file content into an object.
- `provider::terraform::encode_tfvars` - Encode an object into a string with the same format as a TFVars file.
- `provider::terraform::encode_expr` - Encode an arbitrary expression into a string with valid OpenTofu syntax.
- `provider::terraform::decode_tfvars` - Decode a TFVars file content into an object.
- `provider::terraform::encode_tfvars` - Encode an object into a string with the same format as a TFVars file.
- `provider::terraform::encode_expr` - Encode an arbitrary expression into a string with valid OpenTofu syntax.
ENHANCEMENTS:
* OpenTofu will now recommend using `-exclude` instead of `-target`, when possible, in the error messages about unknown values in `count` and `for_each` arguments, thereby providing a more definitive workaround. ([#2154](https://github.com/opentofu/opentofu/pull/2154))
* State encryption now supports using external programs as key providers. Additionally, the PBKDF2 key provider now supports chaining via the `chain` parameter. ([#2023](https://github.com/opentofu/opentofu/pull/2023))
* Added count of forgotten resources to plan and apply outputs. ([#1956](https://github.com/opentofu/opentofu/issues/1956))
* The `element` function now accepts negative indices, which extends the existing "wrapping" model into the negative direction. In particular, choosing element `-1` selects the final element in the sequence. ([#2371](https://github.com/opentofu/opentofu/pull/2371))
* `moved` now supports moving between different types ([#2370](https://github.com/opentofu/opentofu/pull/2370))
* `moved` block can now be used to migrate from the `null_resource` to the `terraform_data` resource. ([#2481](https://github.com/opentofu/opentofu/pull/2481))

View File

@ -64,6 +64,7 @@ func (plan Plan) renderHuman(renderer Renderer, mode plans.Mode, opts ...plans.Q
willPrintResourceChanges := false
counts := make(map[plans.Action]int)
importingCount := 0
forgettingCount := 0
var changes []diff
for _, diff := range diffs.changes {
action := jsonplan.UnmarshalActions(diff.change.Change.Actions)
@ -82,6 +83,10 @@ func (plan Plan) renderHuman(renderer Renderer, mode plans.Mode, opts ...plans.Q
importingCount++
}
if action == plans.Forget {
forgettingCount++
}
// Don't count move-only changes
if action != plans.NoOp {
willPrintResourceChanges = true
@ -224,12 +229,29 @@ func (plan Plan) renderHuman(renderer Renderer, mode plans.Mode, opts ...plans.Q
}
if importingCount > 0 {
if forgettingCount > 0 {
renderer.Streams.Printf(
renderer.Colorize.Color("\n[bold]Plan:[reset] %d to import, %d to add, %d to change, %d to destroy, %d to forget.\n"),
importingCount,
counts[plans.Create]+counts[plans.DeleteThenCreate]+counts[plans.CreateThenDelete],
counts[plans.Update],
counts[plans.Delete]+counts[plans.DeleteThenCreate]+counts[plans.CreateThenDelete],
forgettingCount)
} else {
renderer.Streams.Printf(
renderer.Colorize.Color("\n[bold]Plan:[reset] %d to import, %d to add, %d to change, %d to destroy.\n"),
importingCount,
counts[plans.Create]+counts[plans.DeleteThenCreate]+counts[plans.CreateThenDelete],
counts[plans.Update],
counts[plans.Delete]+counts[plans.DeleteThenCreate]+counts[plans.CreateThenDelete])
}
} else if forgettingCount > 0 {
renderer.Streams.Printf(
renderer.Colorize.Color("\n[bold]Plan:[reset] %d to import, %d to add, %d to change, %d to destroy.\n"),
importingCount,
renderer.Colorize.Color("\n[bold]Plan:[reset] %d to add, %d to change, %d to destroy, %d to forget.\n"),
counts[plans.Create]+counts[plans.DeleteThenCreate]+counts[plans.CreateThenDelete],
counts[plans.Update],
counts[plans.Delete]+counts[plans.DeleteThenCreate]+counts[plans.CreateThenDelete])
counts[plans.Delete]+counts[plans.DeleteThenCreate]+counts[plans.CreateThenDelete],
forgettingCount)
} else {
renderer.Streams.Printf(
renderer.Colorize.Color("\n[bold]Plan:[reset] %d to add, %d to change, %d to destroy.\n"),

View File

@ -1,7 +1,7 @@
{"@level":"info","@message":"Terraform 0.15.0-dev","@module":"tofu.ui","terraform":"0.15.0-dev","type":"version","ui":"0.1.0"}
{"@level":"info","@message":"test_instance.foo: Plan to create","@module":"tofu.ui","change":{"resource":{"addr":"test_instance.foo","module":"","resource":"test_instance.foo","implied_provider":"test","resource_type":"test_instance","resource_name":"foo","resource_key":null},"action":"create"},"type":"planned_change"}
{"@level":"info","@message":"Plan: 1 to add, 0 to change, 0 to destroy.","@module":"tofu.ui","changes":{"add":1,"import":0,"change":0,"remove":0,"operation":"plan"},"type":"change_summary"}
{"@level":"info","@message":"Plan: 1 to add, 0 to change, 0 to destroy.","@module":"tofu.ui","changes":{"add":1,"import":0,"change":0,"forget":0,"remove":0,"operation":"plan"},"type":"change_summary"}
{"@level":"info","@message":"test_instance.foo: Creating...","@module":"tofu.ui","hook":{"resource":{"addr":"test_instance.foo","module":"","resource":"test_instance.foo","implied_provider":"test","resource_type":"test_instance","resource_name":"foo","resource_key":null},"action":"create"},"type":"apply_start"}
{"@level":"info","@message":"test_instance.foo: Creation complete after 0s","@module":"tofu.ui","hook":{"resource":{"addr":"test_instance.foo","module":"","resource":"test_instance.foo","implied_provider":"test","resource_type":"test_instance","resource_name":"foo","resource_key":null},"action":"create","elapsed_seconds":0},"type":"apply_complete"}
{"@level":"info","@message":"Apply complete! Resources: 1 added, 0 changed, 0 destroyed.","@module":"tofu.ui","changes":{"add":1,"import":0,"change":0,"remove":0,"operation":"apply"},"type":"change_summary"}
{"@level":"info","@message":"Apply complete! Resources: 1 added, 0 changed, 0 destroyed.","@module":"tofu.ui","changes":{"add":1,"import":0,"change":0,"forget":0,"remove":0,"operation":"apply"},"type":"change_summary"}
{"@level":"info","@message":"Outputs: 0","@module":"tofu.ui","outputs":{},"type":"outputs"}

View File

@ -2,4 +2,4 @@
{"@level":"info","@message":"data.test_data_source.a: Refreshing...","@module":"tofu.ui","hook":{"resource":{"addr":"data.test_data_source.a","module":"","resource":"data.test_data_source.a","implied_provider":"test","resource_type":"test_data_source","resource_name":"a","resource_key":null},"action":"read"},"type":"apply_start"}
{"@level":"info","@message":"data.test_data_source.a: Refresh complete after 0s [id=zzzzz]","@module":"tofu.ui","hook":{"resource":{"addr":"data.test_data_source.a","module":"","resource":"data.test_data_source.a","implied_provider":"test","resource_type":"test_data_source","resource_name":"a","resource_key":null},"action":"read","id_key":"id","id_value":"zzzzz","elapsed_seconds":0},"type":"apply_complete"}
{"@level":"info","@message":"test_instance.foo: Plan to create","@module":"tofu.ui","change":{"resource":{"addr":"test_instance.foo","module":"","resource":"test_instance.foo","implied_provider":"test","resource_type":"test_instance","resource_name":"foo","resource_key":null},"action":"create"},"type":"planned_change"}
{"@level":"info","@message":"Plan: 1 to add, 0 to change, 0 to destroy.","@module":"tofu.ui","changes":{"add":1,"import":0,"change":0,"remove":0,"operation":"plan"},"type":"change_summary"}
{"@level":"info","@message":"Plan: 1 to add, 0 to change, 0 to destroy.","@module":"tofu.ui","changes":{"add":1,"import":0,"change":0,"forget":0,"remove":0,"operation":"plan"},"type":"change_summary"}

View File

@ -69,12 +69,31 @@ func (v *ApplyHuman) ResourceCount(stateOutPath string) {
v.countHook.Removed,
)
} else if v.countHook.Imported > 0 {
if v.countHook.Forgotten > 0 {
v.view.streams.Printf(
v.view.colorize.Color("[reset][bold][green]\nApply complete! Resources: %d imported, %d added, %d changed, %d destroyed, %d forgotten.\n"),
v.countHook.Imported,
v.countHook.Added,
v.countHook.Changed,
v.countHook.Removed,
v.countHook.Forgotten,
)
} else {
v.view.streams.Printf(
v.view.colorize.Color("[reset][bold][green]\nApply complete! Resources: %d imported, %d added, %d changed, %d destroyed.\n"),
v.countHook.Imported,
v.countHook.Added,
v.countHook.Changed,
v.countHook.Removed,
)
}
} else if v.countHook.Forgotten > 0 {
v.view.streams.Printf(
v.view.colorize.Color("[reset][bold][green]\nApply complete! Resources: %d imported, %d added, %d changed, %d destroyed.\n"),
v.countHook.Imported,
v.view.colorize.Color("[reset][bold][green]\nApply complete! Resources: %d added, %d changed, %d destroyed, %d forgotten.\n"),
v.countHook.Added,
v.countHook.Changed,
v.countHook.Removed,
v.countHook.Forgotten,
)
} else {
v.view.streams.Printf(
@ -144,6 +163,7 @@ func (v *ApplyJSON) ResourceCount(stateOutPath string) {
Change: v.countHook.Changed,
Remove: v.countHook.Removed,
Import: v.countHook.Imported,
Forget: v.countHook.Forgotten,
Operation: operation,
})
}

View File

@ -105,24 +105,34 @@ func TestApplyHuman_help(t *testing.T) {
// Hooks and ResourceCount are tangled up and easiest to test together.
func TestApply_resourceCount(t *testing.T) {
testCases := map[string]struct {
destroy bool
want string
importing bool
destroy bool
want string
importing bool
forgetting bool
}{
"apply": {
false,
"Apply complete! Resources: 1 added, 2 changed, 3 destroyed.",
false,
false,
},
"destroy": {
true,
"Destroy complete! Resources: 3 destroyed.",
false,
false,
},
"import": {
false,
"Apply complete! Resources: 1 imported, 1 added, 2 changed, 3 destroyed.",
true,
false,
},
"forget": {
false,
"Apply complete! Resources: 1 added, 2 changed, 3 destroyed, 1 forgotten.",
false,
true,
},
}
@ -155,6 +165,10 @@ func TestApply_resourceCount(t *testing.T) {
count.Imported = 1
}
if tc.forgetting {
count.Forgotten = 1
}
v.ResourceCount("")
got := done(t).Stdout()

View File

@ -19,10 +19,11 @@ import (
// countHook is a hook that counts the number of resources
// added, removed, changed during the course of an apply.
type countHook struct {
Added int
Changed int
Removed int
Imported int
Added int
Changed int
Removed int
Imported int
Forgotten int
ToAdd int
ToChange int
@ -46,6 +47,7 @@ func (h *countHook) Reset() {
h.Changed = 0
h.Removed = 0
h.Imported = 0
h.Forgotten = 0
}
func (h *countHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (tofu.HookAction, error) {
@ -81,6 +83,7 @@ func (h *countHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generat
h.Removed++
case plans.Update:
h.Changed++
}
}
}
@ -119,3 +122,11 @@ func (h *countHook) PostApplyImport(addr addrs.AbsResourceInstance, importing pl
h.Imported++
return tofu.HookActionContinue, nil
}
func (h *countHook) PostApplyForget(_ addrs.AbsResourceInstance) (tofu.HookAction, error) {
h.Lock()
defer h.Unlock()
h.Forgotten++
return tofu.HookActionContinue, nil
}

View File

@ -5,7 +5,10 @@
package json
import "fmt"
import (
"fmt"
"strings"
)
type Operation string
@ -20,6 +23,7 @@ type ChangeSummary struct {
Change int `json:"change"`
Import int `json:"import"`
Remove int `json:"remove"`
Forget int `json:"forget"`
Operation Operation `json:"operation"`
}
@ -27,19 +31,34 @@ type ChangeSummary struct {
// used by Terraform Cloud and Terraform Enterprise, so the exact formats of
// these strings are important.
func (cs *ChangeSummary) String() string {
var builder strings.Builder
switch cs.Operation {
case OperationApplied:
builder.WriteString("Apply complete! Resources: ")
if cs.Import > 0 {
return fmt.Sprintf("Apply complete! Resources: %d imported, %d added, %d changed, %d destroyed.", cs.Import, cs.Add, cs.Change, cs.Remove)
builder.WriteString(fmt.Sprintf("%d imported, ", cs.Import))
}
return fmt.Sprintf("Apply complete! Resources: %d added, %d changed, %d destroyed.", cs.Add, cs.Change, cs.Remove)
builder.WriteString(fmt.Sprintf("%d added, %d changed, %d destroyed", cs.Add, cs.Change, cs.Remove))
if cs.Forget > 0 {
builder.WriteString(fmt.Sprintf(", %d forgotten.", cs.Forget))
} else {
builder.WriteString(".")
}
return builder.String()
case OperationDestroyed:
return fmt.Sprintf("Destroy complete! Resources: %d destroyed.", cs.Remove)
case OperationPlanned:
builder.WriteString("Plan: ")
if cs.Import > 0 {
return fmt.Sprintf("Plan: %d to import, %d to add, %d to change, %d to destroy.", cs.Import, cs.Add, cs.Change, cs.Remove)
builder.WriteString(fmt.Sprintf("%d to import, ", cs.Import))
}
return fmt.Sprintf("Plan: %d to add, %d to change, %d to destroy.", cs.Add, cs.Change, cs.Remove)
builder.WriteString(fmt.Sprintf("%d to add, %d to change, %d to destroy", cs.Add, cs.Change, cs.Remove))
if cs.Forget > 0 {
builder.WriteString(fmt.Sprintf(", %d to forget.", cs.Forget))
} else {
builder.WriteString(".")
}
return builder.String()
default:
return fmt.Sprintf("%s: %d add, %d change, %d destroy", cs.Operation, cs.Add, cs.Change, cs.Remove)
}

View File

@ -282,6 +282,7 @@ func TestJSONView_ChangeSummary(t *testing.T) {
"import": float64(0),
"change": float64(2),
"remove": float64(3),
"forget": float64(0),
"operation": "apply",
},
},
@ -312,6 +313,38 @@ func TestJSONView_ChangeSummaryWithImport(t *testing.T) {
"change": float64(2),
"remove": float64(3),
"import": float64(1),
"forget": float64(0),
"operation": "apply",
},
},
}
testJSONViewOutputEquals(t, done(t).Stdout(), want)
}
func TestJSONView_ChangeSummaryWithForget(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
jv := NewJSONView(NewView(streams))
jv.ChangeSummary(&viewsjson.ChangeSummary{
Add: 1,
Change: 2,
Remove: 3,
Forget: 1,
Operation: viewsjson.OperationApplied,
})
want := []map[string]interface{}{
{
"@level": "info",
"@message": "Apply complete! Resources: 1 added, 2 changed, 3 destroyed, 1 forgotten.",
"@module": "tofu.ui",
"type": "change_summary",
"changes": map[string]interface{}{
"add": float64(1),
"change": float64(2),
"remove": float64(3),
"import": float64(0),
"forget": float64(1),
"operation": "apply",
},
},

View File

@ -247,6 +247,8 @@ func (v *OperationJSON) Plan(plan *plans.Plan, schemas *tofu.Schemas) {
case plans.CreateThenDelete, plans.DeleteThenCreate:
cs.Add++
cs.Remove++
case plans.Forget:
cs.Forget++
}
if change.Action != plans.NoOp || !change.Addr.Equal(change.PrevRunAddr) || change.Importing != nil {

View File

@ -582,6 +582,7 @@ func TestOperationJSON_planNoChanges(t *testing.T) {
"add": float64(0),
"import": float64(0),
"change": float64(0),
"forget": float64(0),
"remove": float64(0),
},
},
@ -750,6 +751,7 @@ func TestOperationJSON_plan(t *testing.T) {
"add": float64(3),
"import": float64(0),
"change": float64(1),
"forget": float64(0),
"remove": float64(3),
},
},
@ -897,6 +899,7 @@ func TestOperationJSON_planWithImport(t *testing.T) {
"add": float64(1),
"import": float64(4),
"change": float64(1),
"forget": float64(0),
"remove": float64(2),
},
},
@ -1034,6 +1037,7 @@ func TestOperationJSON_planDriftWithMove(t *testing.T) {
"add": float64(0),
"import": float64(0),
"change": float64(0),
"forget": float64(0),
"remove": float64(0),
},
},
@ -1165,6 +1169,7 @@ func TestOperationJSON_planDriftWithMoveRefreshOnly(t *testing.T) {
"add": float64(0),
"import": float64(0),
"change": float64(0),
"forget": float64(0),
"remove": float64(0),
},
},
@ -1226,6 +1231,7 @@ func TestOperationJSON_planOutputChanges(t *testing.T) {
"import": float64(0),
"change": float64(0),
"remove": float64(0),
"forget": float64(0),
},
},
// Output changes

View File

@ -32,8 +32,9 @@ func (c *Context) Apply(ctx context.Context, plan *plans.Plan, config *configs.C
log.Printf("[DEBUG] Building and walking apply graph for %s plan", plan.UIMode)
var diags tfdiags.Diagnostics
if plan.Errored {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot apply failed plan",
@ -47,10 +48,30 @@ func (c *Context) Apply(ctx context.Context, plan *plans.Plan, config *configs.C
// like to show some helpful output that mirrors the way we show other changes.
if rc.Importing != nil {
for _, h := range c.hooks {
// In future, we may need to call PostApplyImport separately elsewhere in the apply
// In the future, we may need to call PostApplyImport separately elsewhere in the apply
// operation. For now, though, we'll call Pre and Post hooks together.
h.PreApplyImport(rc.Addr, *rc.Importing)
h.PostApplyImport(rc.Addr, *rc.Importing)
_, err := h.PreApplyImport(rc.Addr, *rc.Importing)
if err != nil {
return nil, diags.Append(err)
}
_, err = h.PostApplyImport(rc.Addr, *rc.Importing)
if err != nil {
return nil, diags.Append(err)
}
}
}
// Following the same logic, we want to show helpful output for forget operations as well.
if rc.Action == plans.Forget {
for _, h := range c.hooks {
_, err := h.PreApplyForget(rc.Addr)
if err != nil {
return nil, diags.Append(err)
}
_, err = h.PostApplyForget(rc.Addr)
if err != nil {
return nil, diags.Append(err)
}
}
}
}

View File

@ -92,6 +92,10 @@ type Hook interface {
PreApplyImport(addr addrs.AbsResourceInstance, importing plans.ImportingSrc) (HookAction, error)
PostApplyImport(addr addrs.AbsResourceInstance, importing plans.ImportingSrc) (HookAction, error)
// PreApplyForget and PostApplyForget are called during an apply for each forgotten resource.
PreApplyForget(addr addrs.AbsResourceInstance) (HookAction, error)
PostApplyForget(addr addrs.AbsResourceInstance) (HookAction, error)
// Stopping is called if an external signal requests that OpenTofu
// gracefully abort an operation in progress.
//
@ -188,6 +192,14 @@ func (h *NilHook) PostApplyImport(addr addrs.AbsResourceInstance, importing plan
return HookActionContinue, nil
}
func (h *NilHook) PreApplyForget(_ addrs.AbsResourceInstance) (HookAction, error) {
return HookActionContinue, nil
}
func (h *NilHook) PostApplyForget(_ addrs.AbsResourceInstance) (HookAction, error) {
return HookActionContinue, nil
}
func (*NilHook) Stopping() {
// Does nothing at all by default
}

View File

@ -133,6 +133,14 @@ type MockHook struct {
PostApplyImportReturn HookAction
PostApplyImportError error
PreApplyForgetCalled bool
PreApplyForgetReturn HookAction
PreApplyForgetError error
PostApplyForgetCalled bool
PostApplyForgetReturn HookAction
PostApplyForgetError error
StoppingCalled bool
PostStateUpdateCalled bool
@ -327,6 +335,22 @@ func (h *MockHook) PostApplyImport(addr addrs.AbsResourceInstance, importing pla
return h.PostApplyImportReturn, h.PostApplyImportError
}
func (h *MockHook) PreApplyForget(_ addrs.AbsResourceInstance) (HookAction, error) {
h.Lock()
defer h.Unlock()
h.PreApplyForgetCalled = true
return h.PreApplyForgetReturn, h.PreApplyForgetError
}
func (h *MockHook) PostApplyForget(_ addrs.AbsResourceInstance) (HookAction, error) {
h.Lock()
defer h.Unlock()
h.PostApplyForgetCalled = true
return h.PostApplyForgetReturn, h.PostApplyForgetError
}
func (h *MockHook) Stopping() {
h.Lock()
defer h.Unlock()

View File

@ -92,6 +92,14 @@ func (h *stopHook) PostApplyImport(addr addrs.AbsResourceInstance, importing pla
return h.hook()
}
func (h *stopHook) PreApplyForget(_ addrs.AbsResourceInstance) (HookAction, error) {
return h.hook()
}
func (h *stopHook) PostApplyForget(_ addrs.AbsResourceInstance) (HookAction, error) {
return h.hook()
}
func (h *stopHook) Stopping() {}
func (h *stopHook) PostStateUpdate(new *states.State) (HookAction, error) {

View File

@ -157,6 +157,20 @@ func (h *testHook) PostApplyImport(addr addrs.AbsResourceInstance, importing pla
return HookActionContinue, nil
}
func (h *testHook) PreApplyForget(addr addrs.AbsResourceInstance) (HookAction, error) {
h.mu.Lock()
defer h.mu.Unlock()
h.Calls = append(h.Calls, &testHookCall{"PreApplyForget", addr.String()})
return HookActionContinue, nil
}
func (h *testHook) PostApplyForget(addr addrs.AbsResourceInstance) (HookAction, error) {
h.mu.Lock()
defer h.mu.Unlock()
h.Calls = append(h.Calls, &testHookCall{"PostApplyForget", addr.String()})
return HookActionContinue, nil
}
func (h *testHook) Stopping() {
h.mu.Lock()
defer h.mu.Unlock()

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -5,6 +5,7 @@
"changes": {
"add": 0,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -117,6 +117,7 @@
"changes": {
"add": 0,
"change": 2,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -58,6 +58,7 @@
"changes": {
"add": 0,
"change": 0,
"forget": 0,
"import": 0,
"operation": "plan",
"remove": 1
@ -107,6 +108,7 @@
"changes": {
"add": 0,
"change": 0,
"forget": 0,
"import": 0,
"operation": "destroy",
"remove": 1

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -60,6 +60,7 @@
"changes": {
"add": 0,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 1

View File

@ -97,6 +97,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 1

View File

@ -5,6 +5,7 @@
"changes": {
"add": 0,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -126,6 +126,7 @@
"changes": {
"add": 0,
"change": 2,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -5,6 +5,7 @@
"changes": {
"add": 0,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -70,6 +70,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -60,6 +60,7 @@
"changes": {
"add": 0,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 1

View File

@ -97,6 +97,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 1

View File

@ -97,6 +97,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 1

View File

@ -97,6 +97,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 1

View File

@ -97,6 +97,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 1

View File

@ -59,6 +59,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -97,6 +97,7 @@
"changes": {
"add": 1,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 1

View File

@ -61,6 +61,7 @@
"changes": {
"add": 0,
"change": 1,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0

View File

@ -5,6 +5,7 @@
"changes": {
"add": 0,
"change": 0,
"forget": 0,
"import": 0,
"operation": "apply",
"remove": 0