From e112514f709548413c97a67b9718cfcb5bf20342 Mon Sep 17 00:00:00 2001 From: Dinesh Date: Sat, 21 Dec 2019 15:16:50 +0530 Subject: [PATCH 1/4] Describe sts on rollout history if the revision Signed-off-by: Dinesh Kubernetes-commit: 2749dbdc9a1f1e6947278e70daed966749d80426 --- pkg/polymorphichelpers/history.go | 44 ++++++++++++++-- pkg/polymorphichelpers/history_test.go | 69 ++++++++++++++++++-------- 2 files changed, 88 insertions(+), 25 deletions(-) diff --git a/pkg/polymorphichelpers/history.go b/pkg/polymorphichelpers/history.go index 0d00f4ab0..7baa1f0b3 100644 --- a/pkg/polymorphichelpers/history.go +++ b/pkg/polymorphichelpers/history.go @@ -233,9 +233,8 @@ 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 } @@ -243,9 +242,26 @@ func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision 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) + + historyInfo := make(map[int64]*appsv1.ControllerRevision) + for _, h := range history { + historyInfo[h.Revision] = h + } + if revision != 0 { + value, ok := historyInfo[revision] + if !ok { + return "", fmt.Errorf("unable to find the specified revision") + } + sts, err := applyStatefulSetHistory(sts, value) + if err != nil { + return "", fmt.Errorf("error unmarshelling sts: %w", err) + } + return printTemplate(&sts.Spec.Template) + } + + revisions := make([]int64, 0, len(historyInfo)) + for revision := range historyInfo { + revisions = append(revisions, revision) } sliceutil.SortInts64(revisions) @@ -365,6 +381,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..ed9f5b970 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" ) @@ -71,21 +72,25 @@ func TestViewHistory(t *testing.T) { }, Spec: appsv1.StatefulSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Replicas: &replicas, Template: podStub}, } - - 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}}, - }, - TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: "apps/v1"}, - Revision: 1, - } ) + stsRawData, err := json.Marshal(ssStub) + if err != nil { + t.Fatalf("error creating sts raw data: %v", err) + } + 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{}) + _, err = fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), ssStub1, metav1.CreateOptions{}) if err != nil { t.Fatalf("create controllerRevisions error %v occurred ", err) } @@ -94,19 +99,43 @@ func TestViewHistory(t *testing.T) { 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 + expected := `REVISION 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) + } + }) } func TestApplyDaemonSetHistory(t *testing.T) { From 88b59619ba3a7d37da5665e3f1562a365d97f814 Mon Sep 17 00:00:00 2001 From: Dinesh Date: Sun, 29 Mar 2020 21:12:49 +0530 Subject: [PATCH 2/4] dedup the printHistory logic in DaemonSetHistoryViewer,StatefulSetHistoryViewer Signed-off-by: Dinesh Kubernetes-commit: fe1e72e792c0f9f42e0ba2e3291ed0dde2cf262a --- pkg/polymorphichelpers/history.go | 49 +++++++++----------------- pkg/polymorphichelpers/history_test.go | 4 +-- 2 files changed, 18 insertions(+), 35 deletions(-) diff --git a/pkg/polymorphichelpers/history.go b/pkg/polymorphichelpers/history.go index 7baa1f0b3..f6f223b1f 100644 --- a/pkg/polymorphichelpers/history.go +++ b/pkg/polymorphichelpers/history.go @@ -183,6 +183,16 @@ 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 + }) +} + +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 +208,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 @@ -238,39 +248,12 @@ func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision if err != nil { return "", err } - - if len(history) <= 0 { - return "No rollout history found.", nil - } - - historyInfo := make(map[int64]*appsv1.ControllerRevision) - for _, h := range history { - historyInfo[h.Revision] = h - } - if revision != 0 { - value, ok := historyInfo[revision] - if !ok { - return "", fmt.Errorf("unable to find the specified revision") - } - sts, err := applyStatefulSetHistory(sts, value) + return printHistory(history, revision, func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error) { + stsOfHistory, err := applyStatefulSetHistory(sts, history) if err != nil { - return "", fmt.Errorf("error unmarshelling sts: %w", err) + return nil, err } - return printTemplate(&sts.Spec.Template) - } - - revisions := make([]int64, 0, len(historyInfo)) - for revision := range historyInfo { - revisions = append(revisions, 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 nil + return &stsOfHistory.Spec.Template, err }) } diff --git a/pkg/polymorphichelpers/history_test.go b/pkg/polymorphichelpers/history_test.go index ed9f5b970..b4d795288 100644 --- a/pkg/polymorphichelpers/history_test.go +++ b/pkg/polymorphichelpers/history_test.go @@ -105,8 +105,8 @@ func TestViewHistory(t *testing.T) { t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err) } - expected := `REVISION -1 + expected := `REVISION CHANGE-CAUSE +1 ` if result != expected { From 55c16b433f902c1394dae6cf356bedb342bfa27e Mon Sep 17 00:00:00 2001 From: Dinesh Date: Sat, 5 Sep 2020 21:43:22 +0530 Subject: [PATCH 3/4] Add commend for printHistory function Signed-off-by: Dinesh Kubernetes-commit: 548b3a784bf2d05a8cfbaf289ce257d7b71efab2 --- pkg/polymorphichelpers/history.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/polymorphichelpers/history.go b/pkg/polymorphichelpers/history.go index f6f223b1f..139cc8f81 100644 --- a/pkg/polymorphichelpers/history.go +++ b/pkg/polymorphichelpers/history.go @@ -192,6 +192,8 @@ func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision in }) } +// 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 { From b17989102ff1838b1426be786685f66617e8631a Mon Sep 17 00:00:00 2001 From: Dinesh Date: Sat, 5 Sep 2020 22:10:28 +0530 Subject: [PATCH 4/4] Add tests for daemonset view history Signed-off-by: Dinesh Kubernetes-commit: c88b5e2ef36b4d026ec5852beff404197c199797 --- pkg/polymorphichelpers/history_test.go | 206 ++++++++++++++++++------- 1 file changed, 147 insertions(+), 59 deletions(-) diff --git a/pkg/polymorphichelpers/history_test.go b/pkg/polymorphichelpers/history_test.go index b4d795288..13b204c6c 100644 --- a/pkg/polymorphichelpers/history_test.go +++ b/pkg/polymorphichelpers/history_test.go @@ -54,73 +54,74 @@ 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) - } - 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) - } - - var sts = &StatefulSetHistoryViewer{ - fakeClientSet, - } - - t.Run("should show revisions list if the revision is not specified", func(t *testing.T) { - result, err := sts.ViewHistory("default", "moons", 0) + 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 getting ViewHistory for a StatefulSets moons: %v", err) + t.Fatalf("error creating sts raw data: %v", err) + } + 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, } - expected := `REVISION CHANGE-CAUSE + 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, + } + + 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 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) - } + 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: + expected := `Pod Template: Labels: foo=bar Containers: test: @@ -132,9 +133,96 @@ func TestViewHistory(t *testing.T) { Volumes: ` - 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("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) + } + }) + }) }