/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package history import ( "bytes" "encoding/json" "fmt" "reflect" "testing" apps "k8s.io/api/apps/v1" "k8s.io/api/core/v1" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/controller" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" core "k8s.io/client-go/testing" ) func TestRealHistory_ListControllerRevisions(t *testing.T) { type testcase struct { name string parent metav1.Object selector labels.Selector revisions []*apps.ControllerRevision want map[string]bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) for i := range test.revisions { informer.Informer().GetIndexer().Add(test.revisions[i]) } history := NewHistory(client, informer.Lister()) revisions, err := history.ListControllerRevisions(test.parent, test.selector) if err != nil { t.Errorf("%s: %s", test.name, err) } got := make(map[string]bool) for i := range revisions { got[revisions[i].Name] = true } if !reflect.DeepEqual(test.want, got) { t.Errorf("%s: want %v got %v", test.name, test.want, got) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) sel1, err := metav1.LabelSelectorAsSelector(ss1.Spec.Selector) if err != nil { t.Fatal(err) } ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, nil) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, nil) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, nil) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace ss1Orphan, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 3, nil) if err != nil { t.Fatal(err) } ss1Orphan.Namespace = ss1.Namespace ss1Orphan.OwnerReferences = nil tests := []testcase{ { name: "selects none", parent: &ss1.ObjectMeta, selector: sel1, revisions: nil, want: map[string]bool{}, }, { name: "selects all", parent: &ss1.ObjectMeta, selector: sel1, revisions: []*apps.ControllerRevision{ss1Rev1, ss1Rev2}, want: map[string]bool{ss1Rev1.Name: true, ss1Rev2.Name: true}, }, { name: "doesn't select another Objects history", parent: &ss1.ObjectMeta, selector: sel1, revisions: []*apps.ControllerRevision{ss1Rev1, ss1Rev2, ss2Rev1}, want: map[string]bool{ss1Rev1.Name: true, ss1Rev2.Name: true}, }, { name: "selects orphans", parent: &ss1.ObjectMeta, selector: sel1, revisions: []*apps.ControllerRevision{ss1Rev1, ss1Rev2, ss1Orphan}, want: map[string]bool{ss1Rev1.Name: true, ss1Rev2.Name: true, ss1Orphan.Name: true}, }, } for i := range tests { testFn(&tests[i], t) } } func TestFakeHistory_ListControllerRevisions(t *testing.T) { type testcase struct { name string parent metav1.Object selector labels.Selector revisions []*apps.ControllerRevision want map[string]bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) for i := range test.revisions { informer.Informer().GetIndexer().Add(test.revisions[i]) } history := NewFakeHistory(informer) revisions, err := history.ListControllerRevisions(test.parent, test.selector) if err != nil { t.Errorf("%s: %s", test.name, err) } got := make(map[string]bool) for i := range revisions { got[revisions[i].Name] = true } if !reflect.DeepEqual(test.want, got) { t.Errorf("%s: want %v got %v", test.name, test.want, got) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) sel1, err := metav1.LabelSelectorAsSelector(ss1.Spec.Selector) if err != nil { t.Fatal(err) } ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, nil) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, nil) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, nil) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace ss1Orphan, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 3, nil) if err != nil { t.Fatal(err) } ss1Orphan.Namespace = ss1.Namespace ss1Orphan.OwnerReferences = nil tests := []testcase{ { name: "selects none", parent: &ss1.ObjectMeta, selector: sel1, revisions: nil, want: map[string]bool{}, }, { name: "selects all", parent: &ss1.ObjectMeta, selector: sel1, revisions: []*apps.ControllerRevision{ss1Rev1, ss1Rev2}, want: map[string]bool{ss1Rev1.Name: true, ss1Rev2.Name: true}, }, { name: "doesn't select another Objects history", parent: &ss1.ObjectMeta, selector: sel1, revisions: []*apps.ControllerRevision{ss1Rev1, ss1Rev2, ss2Rev1}, want: map[string]bool{ss1Rev1.Name: true, ss1Rev2.Name: true}, }, { name: "selects orphans", parent: &ss1.ObjectMeta, selector: sel1, revisions: []*apps.ControllerRevision{ss1Rev1, ss1Rev2, ss1Orphan}, want: map[string]bool{ss1Rev1.Name: true, ss1Rev2.Name: true, ss1Orphan.Name: true}, }, } for i := range tests { testFn(&tests[i], t) } } func TestRealHistory_CreateControllerRevision(t *testing.T) { type testcase struct { name string parent metav1.Object revision *apps.ControllerRevision existing []struct { parent metav1.Object revision *apps.ControllerRevision } rename bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) history := NewHistory(client, informer.Lister()) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) if err != nil { t.Fatal(err) } } // Clear collisionCount before creating the test revision collisionCount = 0 created, err := history.CreateControllerRevision(test.parent, test.revision, &collisionCount) if err != nil { t.Errorf("%s: %s", test.name, err) } if test.rename { if created.Name == test.revision.Name { t.Errorf("%s: wanted rename got %s %s", test.name, created.Name, test.revision.Name) } expectedName := ControllerRevisionName(test.parent.GetName(), HashControllerRevision(test.revision, &collisionCount)) if created.Name != expectedName { t.Errorf("%s: on name collision wanted new name %s got %s", test.name, expectedName, created.Name) } // Second name collision should have incremented collisionCount to 2 _, err = history.CreateControllerRevision(test.parent, test.revision, &collisionCount) if err != nil { t.Errorf("%s: %s", test.name, err) } if collisionCount != 2 { t.Errorf("%s: on second name collision wanted collisionCount 1 got %d", test.name, collisionCount) } } if !test.rename && created.Name != test.revision.Name { t.Errorf("%s: wanted %s got %s", test.name, test.revision.Name, created.Name) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) ss2.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace tests := []testcase{ { name: "creates new", parent: &ss1.ObjectMeta, revision: ss1Rev1, existing: nil, rename: false, }, { name: "create doesn't conflict when parents differ", parent: &ss2.ObjectMeta, revision: ss2Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, rename: false, }, { name: "create renames on conflict", parent: &ss1.ObjectMeta, revision: ss1Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, rename: true, }, } for i := range tests { testFn(&tests[i], t) } } func TestFakeHistory_CreateControllerRevision(t *testing.T) { type testcase struct { name string parent metav1.Object revision *apps.ControllerRevision existing []struct { parent metav1.Object revision *apps.ControllerRevision } rename bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) history := NewFakeHistory(informer) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) if err != nil { t.Fatal(err) } } // Clear collisionCount before creating the test revision collisionCount = 0 created, err := history.CreateControllerRevision(test.parent, test.revision, &collisionCount) if err != nil { t.Errorf("%s: %s", test.name, err) } if test.rename { if created.Name == test.revision.Name { t.Errorf("%s: wanted rename got %s %s", test.name, created.Name, test.revision.Name) } expectedName := ControllerRevisionName(test.parent.GetName(), HashControllerRevision(test.revision, &collisionCount)) if created.Name != expectedName { t.Errorf("%s: on name collision wanted new name %s got %s", test.name, expectedName, created.Name) } // Second name collision should have incremented collisionCount to 2 _, err = history.CreateControllerRevision(test.parent, test.revision, &collisionCount) if err != nil { t.Errorf("%s: %s", test.name, err) } if collisionCount != 2 { t.Errorf("%s: on second name collision wanted collisionCount 1 got %d", test.name, collisionCount) } } if !test.rename && created.Name != test.revision.Name { t.Errorf("%s: wanted %s got %s", test.name, test.revision.Name, created.Name) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) ss2.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace tests := []testcase{ { name: "creates new", parent: &ss1.ObjectMeta, revision: ss1Rev1, existing: nil, rename: false, }, { name: "create doesn't conflict when parents differ", parent: &ss2.ObjectMeta, revision: ss2Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, rename: false, }, { name: "create renames on conflict", parent: &ss1.ObjectMeta, revision: ss1Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, rename: true, }, } for i := range tests { testFn(&tests[i], t) } } func TestRealHistory_UpdateControllerRevision(t *testing.T) { conflictAttempts := 0 type testcase struct { name string revision *apps.ControllerRevision newRevision int64 existing []struct { parent metav1.Object revision *apps.ControllerRevision } reactor core.ReactionFunc err bool } conflictSuccess := func(action core.Action) (bool, runtime.Object, error) { defer func() { conflictAttempts++ }() switch action.(type) { case core.UpdateActionImpl: update := action.(core.UpdateAction) if conflictAttempts < 2 { return true, update.GetObject(), errors.NewConflict(update.GetResource().GroupResource(), "", fmt.Errorf("conflict")) } return true, update.GetObject(), nil default: return false, nil, nil } } internalError := func(action core.Action) (bool, runtime.Object, error) { switch action.(type) { case core.UpdateActionImpl: return true, nil, errors.NewInternalError(fmt.Errorf("internal error")) default: return false, nil, nil } } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) history := NewHistory(client, informer.Lister()) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) if err != nil { t.Fatal(err) } } if test.reactor != nil { client.PrependReactor("*", "*", test.reactor) } updated, err := history.UpdateControllerRevision(test.revision, test.newRevision) if !test.err && err != nil { t.Errorf("%s: %s", test.name, err) } if !test.err && updated.Revision != test.newRevision { t.Errorf("%s: got %d want %d", test.name, updated.Revision, test.newRevision) } if test.err && err == nil { t.Errorf("%s: expected error", test.name) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace tests := []testcase{ { name: "update succeeds", revision: ss1Rev1, newRevision: ss1Rev1.Revision + 1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, reactor: nil, err: false, }, { name: "update succeeds no noop", revision: ss1Rev1, newRevision: ss1Rev1.Revision, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, reactor: nil, err: false, }, { name: "update fails on error", revision: ss1Rev1, newRevision: ss1Rev1.Revision + 10, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, reactor: internalError, err: true, }, { name: "update on succeeds on conflict", revision: ss1Rev1, newRevision: ss1Rev1.Revision + 1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, reactor: conflictSuccess, err: false, }, } for i := range tests { conflictAttempts = 0 testFn(&tests[i], t) } } func TestFakeHistory_UpdateControllerRevision(t *testing.T) { type testcase struct { name string revision *apps.ControllerRevision newRevision int64 existing []struct { parent metav1.Object revision *apps.ControllerRevision } err bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) history := NewFakeHistory(informer) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) if err != nil { t.Fatal(err) } } updated, err := history.UpdateControllerRevision(test.revision, test.newRevision) if !test.err && err != nil { t.Errorf("%s: %s", test.name, err) } if !test.err && updated.Revision != test.newRevision { t.Errorf("%s: got %d want %d", test.name, updated.Revision, test.newRevision) } if test.err && err == nil { t.Errorf("%s: expected error", test.name) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace tests := []testcase{ { name: "update succeeds", revision: ss1Rev1, newRevision: ss1Rev1.Revision + 1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, err: false, }, { name: "update succeeds no noop", revision: ss1Rev1, newRevision: ss1Rev1.Revision, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, err: false, }, } for i := range tests { testFn(&tests[i], t) } } func TestRealHistory_DeleteControllerRevision(t *testing.T) { type testcase struct { name string revision *apps.ControllerRevision existing []struct { parent metav1.Object revision *apps.ControllerRevision } err bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) history := NewHistory(client, informer.Lister()) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) if err != nil { t.Fatal(err) } } err := history.DeleteControllerRevision(test.revision) if !test.err && err != nil { t.Errorf("%s: %s", test.name, err) } if test.err && err == nil { t.Errorf("%s: expected error", test.name) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) ss2.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace ss2Rev2, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 2, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev2.Namespace = ss2.Namespace tests := []testcase{ { name: "delete empty fails", revision: ss1Rev1, existing: nil, err: true, }, { name: "delete existing succeeds", revision: ss1Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, err: false, }, { name: "delete non-existing fails", revision: ss1Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss2, revision: ss2Rev1, }, { parent: ss2, revision: ss2Rev2, }, }, err: true, }, } for i := range tests { testFn(&tests[i], t) } } func TestFakeHistory_DeleteControllerRevision(t *testing.T) { type testcase struct { name string revision *apps.ControllerRevision existing []struct { parent metav1.Object revision *apps.ControllerRevision } err bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) history := NewFakeHistory(informer) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) if err != nil { t.Fatal(err) } } err := history.DeleteControllerRevision(test.revision) if !test.err && err != nil { t.Errorf("%s: %s", test.name, err) } if test.err && err == nil { t.Errorf("%s: expected error", test.name) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) ss2.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace ss2Rev2, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 2, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev2.Namespace = ss2.Namespace tests := []testcase{ { name: "delete empty fails", revision: ss1Rev1, existing: nil, err: true, }, { name: "delete existing succeeds", revision: ss1Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, err: false, }, { name: "delete non-existing fails", revision: ss1Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss2, revision: ss2Rev1, }, { parent: ss2, revision: ss2Rev2, }, }, err: true, }, } for i := range tests { testFn(&tests[i], t) } } func TestRealHistory_AdoptControllerRevision(t *testing.T) { type testcase struct { name string parent metav1.Object revision *apps.ControllerRevision existing []struct { parent metav1.Object revision *apps.ControllerRevision } err bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() client.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) { switch action := action.(type) { case core.PatchActionImpl: var found *apps.ControllerRevision for i := range test.existing { if test.revision.Name == test.existing[i].revision.Name && test.revision.Namespace == test.existing[i].revision.Namespace { found = test.existing[i].revision break } } if found == nil { return true, nil, errors.NewNotFound(apps.Resource("controllerrevisions"), test.revision.Name) } b, err := strategicpatch.StrategicMergePatch( []byte(runtime.EncodeOrDie(testapi.Apps.Codec(), test.revision)), action.GetPatch(), test.revision) if err != nil { return true, nil, err } obj, err := runtime.Decode(testapi.Apps.Codec(), b) if err != nil { return true, nil, err } patched, err := legacyscheme.Scheme.ConvertToVersion(obj, apps.SchemeGroupVersion) if err != nil { return true, nil, err } return true, patched, err default: return false, nil, nil } }) informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) history := NewHistory(client, informer.Lister()) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) if err != nil { t.Fatal(err) } } adopted, err := history.AdoptControllerRevision(test.parent, parentKind, test.revision) if !test.err && err != nil { t.Errorf("%s: %s", test.name, err) } if !test.err && !metav1.IsControlledBy(adopted, test.parent) { t.Errorf("%s: adoption failed", test.name) } if test.err && err == nil { t.Errorf("%s: expected error", test.name) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) ss2.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss1Rev2.OwnerReferences = []metav1.OwnerReference{} ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace tests := []testcase{ { name: "adopting an orphan succeeds", parent: ss1, revision: ss1Rev2, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev2, }, }, err: false, }, { name: "adopting an owned revision fails", parent: ss1, revision: ss2Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss2, revision: ss2Rev1, }, }, err: true, }, { name: "adopting a non-existent revision fails", parent: ss1, revision: ss1Rev2, existing: nil, err: true, }, } for i := range tests { testFn(&tests[i], t) } } func TestFakeHistory_AdoptControllerRevision(t *testing.T) { type testcase struct { name string parent metav1.Object parentType *metav1.TypeMeta revision *apps.ControllerRevision existing []struct { parent metav1.Object revision *apps.ControllerRevision } err bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) history := NewFakeHistory(informer) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) if err != nil { t.Fatal(err) } } adopted, err := history.AdoptControllerRevision(test.parent, parentKind, test.revision) if !test.err && err != nil { t.Errorf("%s: %s", test.name, err) } if !test.err && !metav1.IsControlledBy(adopted, test.parent) { t.Errorf("%s: adoption failed", test.name) } if test.err && err == nil { t.Errorf("%s: expected error", test.name) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) ss2.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss1Rev2.OwnerReferences = []metav1.OwnerReference{} ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace tests := []testcase{ { name: "adopting an orphan succeeds", parent: ss1, parentType: &ss1.TypeMeta, revision: ss1Rev2, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev2, }, }, err: false, }, { name: "adopting an owned revision fails", parent: ss1, parentType: &ss1.TypeMeta, revision: ss2Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss2, revision: ss2Rev1, }, }, err: true, }, { name: "adopting a non-existent revision fails", parent: ss1, parentType: &ss1.TypeMeta, revision: ss1Rev2, existing: nil, err: true, }, } for i := range tests { testFn(&tests[i], t) } } func TestRealHistory_ReleaseControllerRevision(t *testing.T) { type testcase struct { name string parent metav1.Object revision *apps.ControllerRevision existing []struct { parent metav1.Object revision *apps.ControllerRevision } err bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() client.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) { switch action := action.(type) { case core.PatchActionImpl: var found *apps.ControllerRevision for i := range test.existing { if test.revision.Name == test.existing[i].revision.Name && test.revision.Namespace == test.existing[i].revision.Namespace { found = test.existing[i].revision break } } if found == nil { return true, nil, errors.NewNotFound(apps.Resource("controllerrevisions"), test.revision.Name) } if !metav1.IsControlledBy(test.revision, test.parent) { return true, nil, errors.NewInvalid( test.revision.GroupVersionKind().GroupKind(), test.revision.Name, nil) } b, err := strategicpatch.StrategicMergePatch( []byte(runtime.EncodeOrDie(testapi.Apps.Codec(), test.revision)), action.GetPatch(), test.revision) if err != nil { return true, nil, err } obj, err := runtime.Decode(testapi.Apps.Codec(), b) if err != nil { return true, nil, err } patched, err := legacyscheme.Scheme.ConvertToVersion(obj, apps.SchemeGroupVersion) if err != nil { return true, nil, err } return true, patched, err default: return false, nil, nil } }) informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) history := NewHistory(client, informer.Lister()) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) if err != nil { t.Fatal(err) } } adopted, err := history.ReleaseControllerRevision(test.parent, test.revision) if !test.err { if err != nil { t.Errorf("%s: %s", test.name, err) } if adopted == nil { return } if metav1.IsControlledBy(adopted, test.parent) { t.Errorf("%s: release failed", test.name) } } if test.err && err == nil { t.Errorf("%s: expected error", test.name) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, nil) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, nil) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss1Rev2.OwnerReferences = []metav1.OwnerReference{} ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, nil) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace tests := []testcase{ { name: "releasing an owned revision succeeds", parent: ss1, revision: ss1Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, err: false, }, { name: "releasing an orphan succeeds", parent: ss1, revision: ss1Rev2, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev2, }, }, err: false, }, { name: "releasing a revision owned by another controller succeeds", parent: ss1, revision: ss2Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss2, revision: ss2Rev1, }, }, err: false, }, { name: "releasing a non-existent revision succeeds", parent: ss1, revision: ss1Rev1, existing: nil, err: false, }, } for i := range tests { testFn(&tests[i], t) } } func TestFakeHistory_ReleaseControllerRevision(t *testing.T) { type testcase struct { name string parent metav1.Object revision *apps.ControllerRevision existing []struct { parent metav1.Object revision *apps.ControllerRevision } err bool } testFn := func(test *testcase, t *testing.T) { client := fake.NewSimpleClientset() informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) stop := make(chan struct{}) defer close(stop) informerFactory.Start(stop) informer := informerFactory.Apps().V1().ControllerRevisions() informerFactory.WaitForCacheSync(stop) history := NewFakeHistory(informer) var collisionCount int32 for i := range test.existing { _, err := history.CreateControllerRevision(test.existing[i].parent, test.existing[i].revision, &collisionCount) if err != nil { t.Fatal(err) } } adopted, err := history.ReleaseControllerRevision(test.parent, test.revision) if !test.err { if err != nil { t.Errorf("%s: %s", test.name, err) } if adopted == nil { return } if metav1.IsControlledBy(adopted, test.parent) { t.Errorf("%s: release failed", test.name) } } if test.err && err == nil { t.Errorf("%s: expected error", test.name) } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) ss2.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss1Rev2.OwnerReferences = []metav1.OwnerReference{} ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace tests := []testcase{ { name: "releasing an owned revision succeeds", parent: ss1, revision: ss1Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev1, }, }, err: false, }, { name: "releasing an orphan succeeds", parent: ss1, revision: ss1Rev2, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss1, revision: ss1Rev2, }, }, err: false, }, { name: "releasing a revision owned by another controller succeeds", parent: ss1, revision: ss2Rev1, existing: []struct { parent metav1.Object revision *apps.ControllerRevision }{ { parent: ss2, revision: ss2Rev1, }, }, err: false, }, { name: "releasing a non-existent revision succeeds", parent: ss1, revision: ss1Rev1, existing: nil, err: false, }, } for i := range tests { testFn(&tests[i], t) } } func TestFindEqualRevisions(t *testing.T) { type testcase struct { name string revision *apps.ControllerRevision revisions []*apps.ControllerRevision want map[string]bool } testFn := func(test *testcase, t *testing.T) { found := FindEqualRevisions(test.revisions, test.revision) if len(found) != len(test.want) { t.Errorf("%s: want %d revisions found %d", test.name, len(test.want), len(found)) } for i := range found { if !test.want[found[i].Name] { t.Errorf("%s: wanted %s not found", test.name, found[i].Name) } } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss2 := newStatefulSet(3, "ss2", types.UID("ss2"), map[string]string{"goo": "car"}) ss2.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss1Rev2.OwnerReferences = []metav1.OwnerReference{} ss2Rev1, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 1, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev1.Namespace = ss2.Namespace ss2Rev2, err := NewControllerRevision(ss2, parentKind, ss2.Spec.Template.Labels, rawTemplate(&ss2.Spec.Template), 2, ss2.Status.CollisionCount) if err != nil { t.Fatal(err) } ss2Rev2.Namespace = ss2.Namespace tests := []testcase{ { name: "finds equivalent", revision: ss1Rev1, revisions: []*apps.ControllerRevision{ss1Rev1, ss2Rev1, ss2Rev2}, want: map[string]bool{ss1Rev1.Name: true}, }, { name: "finds nothing when empty", revision: ss1Rev1, revisions: nil, want: map[string]bool{}, }, { name: "finds nothing with no matches", revision: ss1Rev1, revisions: []*apps.ControllerRevision{ss2Rev2, ss2Rev1}, want: map[string]bool{}, }, } for i := range tests { testFn(&tests[i], t) } } func TestSortControllerRevisions(t *testing.T) { type testcase struct { name string revisions []*apps.ControllerRevision want []string } testFn := func(test *testcase, t *testing.T) { SortControllerRevisions(test.revisions) for i := range test.revisions { if test.revisions[i].Name != test.want[i] { t.Errorf("%s: want %s at %d got %s", test.name, test.want[i], i, test.revisions[i].Name) } } } ss1 := newStatefulSet(3, "ss1", types.UID("ss1"), map[string]string{"foo": "bar"}) ss1.Status.CollisionCount = new(int32) ss1Rev1, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 1, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev1.Namespace = ss1.Namespace ss1Rev2, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 2, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev2.Namespace = ss1.Namespace ss1Rev3, err := NewControllerRevision(ss1, parentKind, ss1.Spec.Template.Labels, rawTemplate(&ss1.Spec.Template), 3, ss1.Status.CollisionCount) if err != nil { t.Fatal(err) } ss1Rev3.Namespace = ss1.Namespace tests := []testcase{ { name: "out of order", revisions: []*apps.ControllerRevision{ss1Rev2, ss1Rev1, ss1Rev3}, want: []string{ss1Rev1.Name, ss1Rev2.Name, ss1Rev3.Name}, }, { name: "sorted", revisions: []*apps.ControllerRevision{ss1Rev1, ss1Rev2, ss1Rev3}, want: []string{ss1Rev1.Name, ss1Rev2.Name, ss1Rev3.Name}, }, { name: "reversed", revisions: []*apps.ControllerRevision{ss1Rev3, ss1Rev2, ss1Rev1}, want: []string{ss1Rev1.Name, ss1Rev2.Name, ss1Rev3.Name}, }, { name: "empty", revisions: nil, want: nil, }, } for i := range tests { testFn(&tests[i], t) } } func newStatefulSet(replicas int, name string, uid types.UID, labels map[string]string) *apps.StatefulSet { // Converting all the map-only selectors to set-based selectors. var testMatchExpressions []metav1.LabelSelectorRequirement for key, value := range labels { sel := metav1.LabelSelectorRequirement{ Key: key, Operator: metav1.LabelSelectorOpIn, Values: []string{value}, } testMatchExpressions = append(testMatchExpressions, sel) } return &apps.StatefulSet{ TypeMeta: metav1.TypeMeta{ Kind: "StatefulSet", APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: v1.NamespaceDefault, UID: uid, }, Spec: apps.StatefulSetSpec{ Selector: &metav1.LabelSelector{ // Purposely leaving MatchLabels nil, so to ensure it will break if any link // in the chain ignores the set-based MatchExpressions. MatchLabels: nil, MatchExpressions: testMatchExpressions, }, Replicas: func() *int32 { i := int32(replicas); return &i }(), Template: v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: labels, }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Name: "nginx", Image: "nginx", VolumeMounts: []v1.VolumeMount{ {Name: "datadir", MountPath: "/tmp/"}, {Name: "home", MountPath: "/home"}, }, }, }, Volumes: []v1.Volume{{ Name: "home", VolumeSource: v1.VolumeSource{ HostPath: &v1.HostPathVolumeSource{ Path: fmt.Sprintf("/tmp/%v", "home"), }, }}}, }, }, VolumeClaimTemplates: []v1.PersistentVolumeClaim{ { ObjectMeta: metav1.ObjectMeta{Name: "datadir"}, Spec: v1.PersistentVolumeClaimSpec{ Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ v1.ResourceStorage: *resource.NewQuantity(1, resource.BinarySI), }, }, }, }, }, ServiceName: "governingsvc", }, } } var parentKind = apps.SchemeGroupVersion.WithKind("StatefulSet") func rawTemplate(template *v1.PodTemplateSpec) runtime.RawExtension { buf := new(bytes.Buffer) enc := json.NewEncoder(buf) if err := enc.Encode(template); err != nil { panic(err) } return runtime.RawExtension{Raw: buf.Bytes()} }