Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion commands/alpha/live/plan/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,10 @@ func printText(plan *kptplanner.Plan, objs []*unstructured.Unstructured, ioStrea
printEntry(" ", action, ioStreams)
findAndPrintDiff(action.Original, action.Updated, ContentPrefix, ioStreams)
case kptplanner.Skip:
// TODO: provide more information about why the resource was skipped.
printEntryWithColor("=", print.YELLOW, action, ioStreams)
if action.SkipReason != "" {
printWithPrefix(action.SkipReason, ContentPrefix, ioStreams)
}
case kptplanner.Error:
printEntry("!", action, ioStreams)
printWithPrefix(action.Error, ContentPrefix, ioStreams)
Expand Down
64 changes: 64 additions & 0 deletions commands/alpha/live/plan/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package plan

import (
"bytes"
"testing"

kptplanner "github.com/kptdev/kpt/pkg/live/planner"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

func TestPrintTextSkip(t *testing.T) {
testCases := map[string]struct {
plan *kptplanner.Plan
expected string
}{
"skipped resource with reason": {
plan: &kptplanner.Plan{
Actions: []kptplanner.Action{
{
Type: kptplanner.Skip,
Group: "apps",
Kind: "Deployment",
Name: "foo",
Namespace: "default",
SkipReason: "some skip reason",
},
},
},
expected: "kpt will perform the following actions:\n\x1b[33m\t= apps/Deployment default/foo\n\x1b[0m\t\tsome skip reason\n\n",
},
"skipped resource without reason": {
plan: &kptplanner.Plan{
Actions: []kptplanner.Action{
{
Type: kptplanner.Skip,
Group: "apps",
Kind: "Deployment",
Name: "foo",
Namespace: "default",
},
},
},
expected: "kpt will perform the following actions:\n\x1b[33m\t= apps/Deployment default/foo\n\x1b[0m\n",
},
}

for tn := range testCases {
tc := testCases[tn]
t.Run(tn, func(t *testing.T) {
var buf bytes.Buffer
ioStreams := genericclioptions.IOStreams{
Out: &buf,
ErrOut: &buf,
}
var objs []*unstructured.Unstructured
err := printText(tc.plan, objs, ioStreams)
assert.NoError(t, err)

assert.Equal(t, tc.expected, buf.String())
})
}
}
89 changes: 46 additions & 43 deletions pkg/live/planner/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,15 @@ type Plan struct {
}

type Action struct {
Type ActionType
Group string
Kind string
Name string
Namespace string
Original *unstructured.Unstructured
Updated *unstructured.Unstructured
Error string
Type ActionType
Group string
Kind string
Name string
Namespace string
Original *unstructured.Unstructured
Updated *unstructured.Unstructured
Error string
SkipReason string
}

type Options struct {
Expand Down Expand Up @@ -195,63 +196,65 @@ func (r *ClusterPlanner) dryRunForPlan(
}

func handleApplyEvent(e event.Event, a Action) Action {
if e.ApplyEvent.Status == event.ApplySkipped {
a.Type = Skip
if e.ApplyEvent.Error != nil {
a.SkipReason = e.ApplyEvent.Error.Error()
}
return a
}
if e.ApplyEvent.Error != nil {
a.Type = Error
a.Error = e.ApplyEvent.Error.Error()
} else {
switch e.ApplyEvent.Status {
case event.ApplySkipped:
a.Type = Skip
case event.ApplySuccessful:
a.Updated = e.ApplyEvent.Resource
if a.Original != nil {
// TODO: Unclear if we should diff the full resources here. It doesn't work
// well with client-side apply as the managedFields property shows up as
// changes. It also means there is a race with controllers that might change
// the status of resources.
if reflect.DeepEqual(a.Original, a.Updated) {
a.Type = Unchanged
} else {
a.Type = Update
}
} else if e.ApplyEvent.Status == event.ApplySuccessful {
a.Updated = e.ApplyEvent.Resource
if a.Original != nil {
// TODO: Unclear if we should diff the full resources here. It doesn't work
// well with client-side apply as the managedFields property shows up as
// changes. It also means there is a race with controllers that might change
// the status of resources.
if reflect.DeepEqual(a.Original, a.Updated) {
a.Type = Unchanged
} else {
a.Type = Create
a.Type = Update
}
} else {
a.Type = Create
}
}
return a
}

func handlePruneEvent(e event.Event, a Action) Action {
if e.PruneEvent.Status == event.PruneSkipped {
a.Type = Skip
if e.PruneEvent.Error != nil {
a.SkipReason = e.PruneEvent.Error.Error()
}
return a
}
if e.PruneEvent.Error != nil {
a.Type = Error
a.Error = e.PruneEvent.Error.Error()
} else {
switch e.PruneEvent.Status {
case event.PruneSuccessful:
a.Type = Delete
// Lifecycle directives can cause resources to remain in the
// live state even if they would normally be pruned.
// TODO: Handle reason for skipped resources that has recently
// been added to the actuation library.
case event.PruneSkipped:
a.Type = Skip
}
} else if e.PruneEvent.Status == event.PruneSuccessful {
a.Type = Delete
}
return a
}

func handleDeleteEvent(e event.Event, a Action) Action {
if e.DeleteEvent.Status == event.DeleteSkipped {
a.Type = Skip
if e.DeleteEvent.Error != nil {
a.SkipReason = e.DeleteEvent.Error.Error()
}
return a
}
if e.DeleteEvent.Error != nil {
a.Type = Error
a.Error = e.DeleteEvent.Error.Error()
} else {
switch e.DeleteEvent.Status {
case event.DeleteSuccessful:
a.Type = Delete
case event.DeleteSkipped:
a.Type = Skip
}
} else if e.DeleteEvent.Status == event.DeleteSuccessful {
a.Type = Delete
}
return a
}
Expand Down
Loading