diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 59f4e1e00..00ff645a5 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -760,11 +760,11 @@ }, { "ImportPath": "k8s.io/api", - "Rev": "d9d0f5541ae1" + "Rev": "5bb35d2636ca" }, { "ImportPath": "k8s.io/apimachinery", - "Rev": "714f1137f89b" + "Rev": "5b968b2f191f" }, { "ImportPath": "k8s.io/cli-runtime", diff --git a/go.mod b/go.mod index ef8f2adf5..770407157 100644 --- a/go.mod +++ b/go.mod @@ -34,8 +34,8 @@ require ( github.com/stretchr/testify v1.4.0 golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 gopkg.in/yaml.v2 v2.2.8 - k8s.io/api v0.0.0-20200921235740-d9d0f5541ae1 - k8s.io/apimachinery v0.0.0-20200916235632-714f1137f89b + k8s.io/api v0.0.0-20200922195808-5bb35d2636ca + k8s.io/apimachinery v0.0.0-20200922195624-5b968b2f191f k8s.io/cli-runtime v0.0.0-20200915100420-3cc3835b3ec2 k8s.io/client-go v0.0.0-20200917000235-cba7285b7f29 k8s.io/component-base v0.0.0-20200911092040-c985e940ef8f @@ -48,8 +48,8 @@ require ( ) replace ( - k8s.io/api => k8s.io/api v0.0.0-20200921235740-d9d0f5541ae1 - k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200916235632-714f1137f89b + k8s.io/api => k8s.io/api v0.0.0-20200922195808-5bb35d2636ca + k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200922195624-5b968b2f191f k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20200915100420-3cc3835b3ec2 k8s.io/client-go => k8s.io/client-go v0.0.0-20200917000235-cba7285b7f29 k8s.io/code-generator => k8s.io/code-generator v0.0.0-20200904030940-4116974d9b44 diff --git a/go.sum b/go.sum index fb80af837..c3fc66966 100644 --- a/go.sum +++ b/go.sum @@ -503,8 +503,8 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.0.0-20200921235740-d9d0f5541ae1/go.mod h1:5s62z0LxtR2JsRUbtXtTNHjcz/u4iXBuX/32bK2YFx8= -k8s.io/apimachinery v0.0.0-20200916235632-714f1137f89b/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/api v0.0.0-20200922195808-5bb35d2636ca/go.mod h1:FAsg3Y/xcbFIbnyfpgARCQ8di7NxpIRZWbawQTkHTDo= +k8s.io/apimachinery v0.0.0-20200922195624-5b968b2f191f/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/cli-runtime v0.0.0-20200915100420-3cc3835b3ec2/go.mod h1:Hma9QauilauA4Azq48kPjOcZNafcVy3yUE7iiJUT8QQ= k8s.io/client-go v0.0.0-20200917000235-cba7285b7f29/go.mod h1:Plj2rfLmeMYfAMuMgA/1EGuUaDxt78tvB9yfdi6fg6A= k8s.io/code-generator v0.0.0-20200904030940-4116974d9b44/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= diff --git a/pkg/polymorphichelpers/history.go b/pkg/polymorphichelpers/history.go index 0d00f4ab0..139cc8f81 100644 --- a/pkg/polymorphichelpers/history.go +++ b/pkg/polymorphichelpers/history.go @@ -183,6 +183,18 @@ func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision in if err != nil { return "", err } + return printHistory(history, revision, func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error) { + dsOfHistory, err := applyDaemonSetHistory(ds, history) + if err != nil { + return nil, err + } + return &dsOfHistory.Spec.Template, err + }) +} + +// printHistory returns the podTemplate of the given revision if it is non-zero +// else returns the overall revisions +func printHistory(history []*appsv1.ControllerRevision, revision int64, getPodTemplate func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error)) (string, error) { historyInfo := make(map[int64]*appsv1.ControllerRevision) for _, history := range history { // TODO: for now we assume revisions don't overlap, we may need to handle it @@ -198,11 +210,11 @@ func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision in if !ok { return "", fmt.Errorf("unable to find the specified revision") } - dsOfHistory, err := applyDaemonSetHistory(ds, history) + podTemplate, err := getPodTemplate(history) if err != nil { return "", fmt.Errorf("unable to parse history %s", history.Name) } - return printTemplate(&dsOfHistory.Spec.Template) + return printTemplate(podTemplate) } // Print an overview of all Revisions @@ -233,28 +245,17 @@ type StatefulSetHistoryViewer struct { // ViewHistory returns a list of the revision history of a statefulset // TODO: this should be a describer -// TODO: needs to implement detailed revision view func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) { - _, history, err := statefulSetHistory(h.c.AppsV1(), namespace, name) + sts, history, err := statefulSetHistory(h.c.AppsV1(), namespace, name) if err != nil { return "", err } - - if len(history) <= 0 { - return "No rollout history found.", nil - } - revisions := make([]int64, 0, len(history)) - for _, revision := range history { - revisions = append(revisions, revision.Revision) - } - sliceutil.SortInts64(revisions) - - return tabbedString(func(out io.Writer) error { - fmt.Fprintf(out, "REVISION\n") - for _, r := range revisions { - fmt.Fprintf(out, "%d\n", r) + return printHistory(history, revision, func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error) { + stsOfHistory, err := applyStatefulSetHistory(sts, history) + if err != nil { + return nil, err } - return nil + return &stsOfHistory.Spec.Template, err }) } @@ -365,6 +366,24 @@ func applyDaemonSetHistory(ds *appsv1.DaemonSet, history *appsv1.ControllerRevis return result, nil } +// applyStatefulSetHistory returns a specific revision of StatefulSet by applying the given history to a copy of the given StatefulSet +func applyStatefulSetHistory(sts *appsv1.StatefulSet, history *appsv1.ControllerRevision) (*appsv1.StatefulSet, error) { + stsBytes, err := json.Marshal(sts) + if err != nil { + return nil, err + } + patched, err := strategicpatch.StrategicMergePatch(stsBytes, history.Data.Raw, sts) + if err != nil { + return nil, err + } + result := &appsv1.StatefulSet{} + err = json.Unmarshal(patched, result) + if err != nil { + return nil, err + } + return result, nil +} + // TODO: copied here until this becomes a describer func tabbedString(f func(io.Writer) error) (string, error) { out := new(tabwriter.Writer) diff --git a/pkg/polymorphichelpers/history_test.go b/pkg/polymorphichelpers/history_test.go index 8b2af5704..13b204c6c 100644 --- a/pkg/polymorphichelpers/history_test.go +++ b/pkg/polymorphichelpers/history_test.go @@ -27,6 +27,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/json" "k8s.io/client-go/kubernetes/fake" ) @@ -53,60 +54,176 @@ func TestHistoryViewerFor(t *testing.T) { func TestViewHistory(t *testing.T) { - var ( - trueVar = true - replicas = int32(1) + t.Run("for statefulSet", func(t *testing.T) { + var ( + trueVar = true + replicas = int32(1) - podStub = corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "nginx"}}}, + podStub = corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "nginx"}}}, + } + + ssStub = &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "moons", + Namespace: "default", + UID: "1993", + Labels: map[string]string{"foo": "bar"}, + }, + Spec: appsv1.StatefulSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Replicas: &replicas, Template: podStub}, + } + ) + stsRawData, err := json.Marshal(ssStub) + if err != nil { + t.Fatalf("error creating sts raw data: %v", err) } - - ssStub = &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "moons", - Namespace: "default", - UID: "1993", - Labels: map[string]string{"foo": "bar"}, - }, - Spec: appsv1.StatefulSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Replicas: &replicas, Template: podStub}, - } - - ssStub1 = &appsv1.ControllerRevision{ + ssStub1 := &appsv1.ControllerRevision{ ObjectMeta: metav1.ObjectMeta{ Name: "moons", Namespace: "default", Labels: map[string]string{"foo": "bar"}, OwnerReferences: []metav1.OwnerReference{{"apps/v1", "StatefulSet", "moons", "1993", &trueVar, nil}}, }, + Data: runtime.RawExtension{Raw: stsRawData}, TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: "apps/v1"}, Revision: 1, } - ) - fakeClientSet := fake.NewSimpleClientset(ssStub) - _, err := fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), ssStub1, metav1.CreateOptions{}) - if err != nil { - t.Fatalf("create controllerRevisions error %v occurred ", err) - } + fakeClientSet := fake.NewSimpleClientset(ssStub) + _, err = fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), ssStub1, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("create controllerRevisions error %v occurred ", err) + } - var sts = &StatefulSetHistoryViewer{ - fakeClientSet, - } + var sts = &StatefulSetHistoryViewer{ + fakeClientSet, + } - result, err := sts.ViewHistory("default", "moons", 1) - if err != nil { - t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err) - } + t.Run("should show revisions list if the revision is not specified", func(t *testing.T) { + result, err := sts.ViewHistory("default", "moons", 0) + if err != nil { + t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err) + } - expected := `REVISION -1 + expected := `REVISION CHANGE-CAUSE +1 ` - if result != expected { - t.Fatalf("unexpected output (%v was expected but got %v)", expected, result) - } + if result != expected { + t.Fatalf("unexpected output (%v was expected but got %v)", expected, result) + } + }) + t.Run("should describe the revision if revision is specified", func(t *testing.T) { + result, err := sts.ViewHistory("default", "moons", 1) + if err != nil { + t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err) + } + + expected := `Pod Template: + Labels: foo=bar + Containers: + test: + Image: nginx + Port: + Host Port: + Environment: + Mounts: + Volumes: +` + + if result != expected { + t.Fatalf("unexpected output (%v was expected but got %v)", expected, result) + } + }) + + }) + + t.Run("for daemonSet", func(t *testing.T) { + var ( + trueVar = true + podStub = corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "nginx"}}}, + } + + daemonSetStub = &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "moons", + Namespace: "default", + UID: "1993", + Labels: map[string]string{"foo": "bar"}, + }, + Spec: appsv1.DaemonSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Template: podStub}, + } + ) + + daemonSetRaw, err := json.Marshal(daemonSetStub) + if err != nil { + t.Fatalf("error creating sts raw data: %v", err) + } + daemonSetControllerRevision := &appsv1.ControllerRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "moons", + Namespace: "default", + Labels: map[string]string{"foo": "bar"}, + OwnerReferences: []metav1.OwnerReference{{"apps/v1", "DaemonSet", "moons", "1993", &trueVar, nil}}, + }, + Data: runtime.RawExtension{Raw: daemonSetRaw}, + TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: "apps/v1"}, + Revision: 1, + } + + fakeClientSet := fake.NewSimpleClientset(daemonSetStub) + _, err = fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), daemonSetControllerRevision, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("create controllerRevisions error %v occurred ", err) + } + + var daemonSetHistoryViewer = &DaemonSetHistoryViewer{ + fakeClientSet, + } + + t.Run("should show revisions list if the revision is not specified", func(t *testing.T) { + result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 0) + if err != nil { + t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err) + } + + expected := `REVISION CHANGE-CAUSE +1 +` + + if result != expected { + t.Fatalf("unexpected output (%v was expected but got %v)", expected, result) + } + }) + + t.Run("should describe the revision if revision is specified", func(t *testing.T) { + result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 1) + if err != nil { + t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err) + } + + expected := `Pod Template: + Labels: foo=bar + Containers: + test: + Image: nginx + Port: + Host Port: + Environment: + Mounts: + Volumes: +` + + if result != expected { + t.Fatalf("unexpected output (%v was expected but got %v)", expected, result) + } + }) + + }) } func TestApplyDaemonSetHistory(t *testing.T) {