mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 02:41:25 +00:00
Merge pull request #111093 from brianpursley/k-110097
Fix rollout history bug
This commit is contained in:
commit
8674ce53ff
@ -18,6 +18,7 @@ package rollout
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@ -153,6 +154,44 @@ func (o *RolloutHistoryOptions) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.PrintFlags.OutputFlagSpecified() {
|
||||||
|
printer, err := o.PrintFlags.ToPrinter()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Visit(func(info *resource.Info, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping := info.ResourceMapping()
|
||||||
|
historyViewer, err := o.HistoryViewer(o.RESTClientGetter, mapping)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
historyInfo, err := historyViewer.GetHistory(info.Namespace, info.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Revision > 0 {
|
||||||
|
printer.PrintObj(historyInfo[o.Revision], o.Out)
|
||||||
|
} else {
|
||||||
|
sortedKeys := make([]int64, 0, len(historyInfo))
|
||||||
|
for k := range historyInfo {
|
||||||
|
sortedKeys = append(sortedKeys, k)
|
||||||
|
}
|
||||||
|
sort.Slice(sortedKeys, func(i, j int) bool { return sortedKeys[i] < sortedKeys[j] })
|
||||||
|
for _, k := range sortedKeys {
|
||||||
|
printer.PrintObj(historyInfo[k], o.Out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return r.Visit(func(info *resource.Info, err error) error {
|
return r.Visit(func(info *resource.Info, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -0,0 +1,428 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 rollout
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
|
"k8s.io/client-go/rest/fake"
|
||||||
|
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||||
|
"k8s.io/kubectl/pkg/polymorphichelpers"
|
||||||
|
"k8s.io/kubectl/pkg/scheme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeHistoryViewer struct {
|
||||||
|
viewHistoryFn func(namespace, name string, revision int64) (string, error)
|
||||||
|
getHistoryFn func(namespace, name string) (map[int64]runtime.Object, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *fakeHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
|
||||||
|
return h.viewHistoryFn(namespace, name, revision)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *fakeHistoryViewer) GetHistory(namespace, name string) (map[int64]runtime.Object, error) {
|
||||||
|
return h.getHistoryFn(namespace, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupFakeHistoryViewer(t *testing.T) *fakeHistoryViewer {
|
||||||
|
fhv := &fakeHistoryViewer{
|
||||||
|
viewHistoryFn: func(namespace, name string, revision int64) (string, error) {
|
||||||
|
t.Fatalf("ViewHistory mock not implemented")
|
||||||
|
return "", nil
|
||||||
|
},
|
||||||
|
getHistoryFn: func(namespace, name string) (map[int64]runtime.Object, error) {
|
||||||
|
t.Fatalf("GetHistory mock not implemented")
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
polymorphichelpers.HistoryViewerFn = func(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (polymorphichelpers.HistoryViewer, error) {
|
||||||
|
return fhv, nil
|
||||||
|
}
|
||||||
|
return fhv
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRolloutHistory(t *testing.T) {
|
||||||
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
|
defer tf.Cleanup()
|
||||||
|
|
||||||
|
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||||
|
encoder := ns.EncoderForVersion(info.Serializer, rolloutPauseGroupVersionEncoder)
|
||||||
|
|
||||||
|
tf.Client = &RolloutPauseRESTClient{
|
||||||
|
RESTClient: &fake.RESTClient{
|
||||||
|
GroupVersion: rolloutPauseGroupVersionEncoder,
|
||||||
|
NegotiatedSerializer: ns,
|
||||||
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
|
case p == "/namespaces/test/deployments/foo" && m == "GET":
|
||||||
|
responseDeployment := &appsv1.Deployment{}
|
||||||
|
responseDeployment.Name = "foo"
|
||||||
|
body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(encoder, responseDeployment))))
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
flags map[string]string
|
||||||
|
expectedOutput string
|
||||||
|
expectedRevision int64
|
||||||
|
}{
|
||||||
|
"should display ViewHistory output for all revisions": {
|
||||||
|
expectedOutput: `deployment.apps/foo
|
||||||
|
Fake ViewHistory Output
|
||||||
|
|
||||||
|
`,
|
||||||
|
expectedRevision: int64(0),
|
||||||
|
},
|
||||||
|
"should display ViewHistory output for a single revision": {
|
||||||
|
flags: map[string]string{"revision": "2"},
|
||||||
|
expectedOutput: `deployment.apps/foo with revision #2
|
||||||
|
Fake ViewHistory Output
|
||||||
|
|
||||||
|
`,
|
||||||
|
expectedRevision: int64(2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(tt *testing.T) {
|
||||||
|
fhv := setupFakeHistoryViewer(tt)
|
||||||
|
var actualNamespace, actualName *string
|
||||||
|
var actualRevision *int64
|
||||||
|
fhv.viewHistoryFn = func(namespace, name string, revision int64) (string, error) {
|
||||||
|
actualNamespace = &namespace
|
||||||
|
actualName = &name
|
||||||
|
actualRevision = &revision
|
||||||
|
return "Fake ViewHistory Output\n", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
streams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
|
||||||
|
cmd := NewCmdRolloutHistory(tf, streams)
|
||||||
|
for k, v := range tc.flags {
|
||||||
|
cmd.Flags().Set(k, v)
|
||||||
|
}
|
||||||
|
cmd.Run(cmd, []string{"deployment/foo"})
|
||||||
|
|
||||||
|
expectedErrorOutput := ""
|
||||||
|
if errBuf.String() != expectedErrorOutput {
|
||||||
|
tt.Fatalf("expected error output: %s, but got %s", expectedErrorOutput, errBuf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.String() != tc.expectedOutput {
|
||||||
|
tt.Fatalf("expected output: %s, but got: %s", tc.expectedOutput, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedNamespace := "test"
|
||||||
|
if actualNamespace == nil || *actualNamespace != expectedNamespace {
|
||||||
|
tt.Fatalf("expected ViewHistory to have been called with namespace %s, but it was %v", expectedNamespace, *actualNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedName := "foo"
|
||||||
|
if actualName == nil || *actualName != expectedName {
|
||||||
|
tt.Fatalf("expected ViewHistory to have been called with name %s, but it was %v", expectedName, *actualName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualRevision == nil {
|
||||||
|
tt.Fatalf("expected ViewHistory to have been called with revision %d, but it was ", tc.expectedRevision)
|
||||||
|
} else if *actualRevision != tc.expectedRevision {
|
||||||
|
tt.Fatalf("expected ViewHistory to have been called with revision %d, but it was %v", tc.expectedRevision, *actualRevision)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleResourceRolloutHistory(t *testing.T) {
|
||||||
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
|
defer tf.Cleanup()
|
||||||
|
|
||||||
|
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||||
|
encoder := ns.EncoderForVersion(info.Serializer, rolloutPauseGroupVersionEncoder)
|
||||||
|
|
||||||
|
tf.Client = &RolloutPauseRESTClient{
|
||||||
|
RESTClient: &fake.RESTClient{
|
||||||
|
GroupVersion: rolloutPauseGroupVersionEncoder,
|
||||||
|
NegotiatedSerializer: ns,
|
||||||
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
|
case p == "/namespaces/test/deployments/foo" && m == "GET":
|
||||||
|
responseDeployment := &appsv1.Deployment{}
|
||||||
|
responseDeployment.Name = "foo"
|
||||||
|
body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(encoder, responseDeployment))))
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||||
|
case p == "/namespaces/test/deployments/bar" && m == "GET":
|
||||||
|
responseDeployment := &appsv1.Deployment{}
|
||||||
|
responseDeployment.Name = "bar"
|
||||||
|
body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(encoder, responseDeployment))))
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
flags map[string]string
|
||||||
|
expectedOutput string
|
||||||
|
}{
|
||||||
|
"should display ViewHistory output for all revisions": {
|
||||||
|
expectedOutput: `deployment.apps/foo
|
||||||
|
Fake ViewHistory Output
|
||||||
|
|
||||||
|
deployment.apps/bar
|
||||||
|
Fake ViewHistory Output
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"should display ViewHistory output for a single revision": {
|
||||||
|
flags: map[string]string{"revision": "2"},
|
||||||
|
expectedOutput: `deployment.apps/foo with revision #2
|
||||||
|
Fake ViewHistory Output
|
||||||
|
|
||||||
|
deployment.apps/bar with revision #2
|
||||||
|
Fake ViewHistory Output
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(tt *testing.T) {
|
||||||
|
fhv := setupFakeHistoryViewer(tt)
|
||||||
|
fhv.viewHistoryFn = func(namespace, name string, revision int64) (string, error) {
|
||||||
|
return "Fake ViewHistory Output\n", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
streams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
|
||||||
|
cmd := NewCmdRolloutHistory(tf, streams)
|
||||||
|
for k, v := range tc.flags {
|
||||||
|
cmd.Flags().Set(k, v)
|
||||||
|
}
|
||||||
|
cmd.Run(cmd, []string{"deployment/foo", "deployment/bar"})
|
||||||
|
|
||||||
|
expectedErrorOutput := ""
|
||||||
|
if errBuf.String() != expectedErrorOutput {
|
||||||
|
tt.Fatalf("expected error output: %s, but got %s", expectedErrorOutput, errBuf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.String() != tc.expectedOutput {
|
||||||
|
tt.Fatalf("expected output: %s, but got: %s", tc.expectedOutput, buf.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRolloutHistoryWithOutput(t *testing.T) {
|
||||||
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
|
defer tf.Cleanup()
|
||||||
|
|
||||||
|
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||||
|
encoder := ns.EncoderForVersion(info.Serializer, rolloutPauseGroupVersionEncoder)
|
||||||
|
|
||||||
|
tf.Client = &RolloutPauseRESTClient{
|
||||||
|
RESTClient: &fake.RESTClient{
|
||||||
|
GroupVersion: rolloutPauseGroupVersionEncoder,
|
||||||
|
NegotiatedSerializer: ns,
|
||||||
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
|
case p == "/namespaces/test/deployments/foo" && m == "GET":
|
||||||
|
responseDeployment := &appsv1.Deployment{}
|
||||||
|
responseDeployment.Name = "foo"
|
||||||
|
body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(encoder, responseDeployment))))
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
flags map[string]string
|
||||||
|
expectedOutput string
|
||||||
|
}{
|
||||||
|
"json": {
|
||||||
|
flags: map[string]string{"revision": "2", "output": "json"},
|
||||||
|
expectedOutput: `{
|
||||||
|
"kind": "ReplicaSet",
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "rev2",
|
||||||
|
"creationTimestamp": null
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"selector": null,
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"creationTimestamp": null
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"replicas": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"yaml": {
|
||||||
|
flags: map[string]string{"revision": "2", "output": "yaml"},
|
||||||
|
expectedOutput: `apiVersion: apps/v1
|
||||||
|
kind: ReplicaSet
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: rev2
|
||||||
|
spec:
|
||||||
|
selector: null
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
spec:
|
||||||
|
containers: null
|
||||||
|
status:
|
||||||
|
replicas: 0
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"yaml all revisions": {
|
||||||
|
flags: map[string]string{"output": "yaml"},
|
||||||
|
expectedOutput: `apiVersion: apps/v1
|
||||||
|
kind: ReplicaSet
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: rev1
|
||||||
|
spec:
|
||||||
|
selector: null
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
spec:
|
||||||
|
containers: null
|
||||||
|
status:
|
||||||
|
replicas: 0
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: ReplicaSet
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: rev2
|
||||||
|
spec:
|
||||||
|
selector: null
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
spec:
|
||||||
|
containers: null
|
||||||
|
status:
|
||||||
|
replicas: 0
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
flags: map[string]string{"output": "name"},
|
||||||
|
expectedOutput: `replicaset.apps/rev1
|
||||||
|
replicaset.apps/rev2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
fhv := setupFakeHistoryViewer(t)
|
||||||
|
var actualNamespace, actualName *string
|
||||||
|
fhv.getHistoryFn = func(namespace, name string) (map[int64]runtime.Object, error) {
|
||||||
|
actualNamespace = &namespace
|
||||||
|
actualName = &name
|
||||||
|
return map[int64]runtime.Object{
|
||||||
|
1: &appsv1.ReplicaSet{ObjectMeta: v1.ObjectMeta{Name: "rev1"}},
|
||||||
|
2: &appsv1.ReplicaSet{ObjectMeta: v1.ObjectMeta{Name: "rev2"}},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
streams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
|
||||||
|
cmd := NewCmdRolloutHistory(tf, streams)
|
||||||
|
for k, v := range tc.flags {
|
||||||
|
cmd.Flags().Set(k, v)
|
||||||
|
}
|
||||||
|
cmd.Run(cmd, []string{"deployment/foo"})
|
||||||
|
|
||||||
|
expectedErrorOutput := ""
|
||||||
|
if errBuf.String() != expectedErrorOutput {
|
||||||
|
t.Fatalf("expected error output: %s, but got %s", expectedErrorOutput, errBuf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.String() != tc.expectedOutput {
|
||||||
|
t.Fatalf("expected output: %s, but got: %s", tc.expectedOutput, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedNamespace := "test"
|
||||||
|
if actualNamespace == nil || *actualNamespace != expectedNamespace {
|
||||||
|
t.Fatalf("expected GetHistory to have been called with namespace %s, but it was %v", expectedNamespace, *actualNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedName := "foo"
|
||||||
|
if actualName == nil || *actualName != expectedName {
|
||||||
|
t.Fatalf("expected GetHistory to have been called with name %s, but it was %v", expectedName, *actualName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
opts := RolloutHistoryOptions{
|
||||||
|
Revision: 0,
|
||||||
|
Resources: []string{"deployment/foo"},
|
||||||
|
}
|
||||||
|
if err := opts.Validate(); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.Revision = -1
|
||||||
|
expectedError := "revision must be a positive integer: -1"
|
||||||
|
if err := opts.Validate(); err == nil {
|
||||||
|
t.Fatalf("unexpected non error")
|
||||||
|
} else if err.Error() != expectedError {
|
||||||
|
t.Fatalf("expected error %s, but got %s", expectedError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.Revision = 0
|
||||||
|
opts.Resources = []string{}
|
||||||
|
expectedError = "required resource not specified"
|
||||||
|
if err := opts.Validate(); err == nil {
|
||||||
|
t.Fatalf("unexpected non error")
|
||||||
|
} else if err.Error() != expectedError {
|
||||||
|
t.Fatalf("expected error %s, but got %s", expectedError, err.Error())
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,6 @@ import (
|
|||||||
|
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
@ -35,6 +34,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
clientappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
|
clientappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
"k8s.io/kubectl/pkg/apps"
|
"k8s.io/kubectl/pkg/apps"
|
||||||
"k8s.io/kubectl/pkg/describe"
|
"k8s.io/kubectl/pkg/describe"
|
||||||
deploymentutil "k8s.io/kubectl/pkg/util/deployment"
|
deploymentutil "k8s.io/kubectl/pkg/util/deployment"
|
||||||
@ -48,6 +48,7 @@ const (
|
|||||||
// HistoryViewer provides an interface for resources have historical information.
|
// HistoryViewer provides an interface for resources have historical information.
|
||||||
type HistoryViewer interface {
|
type HistoryViewer interface {
|
||||||
ViewHistory(namespace, name string, revision int64) (string, error)
|
ViewHistory(namespace, name string, revision int64) (string, error)
|
||||||
|
GetHistory(namespace, name string) (map[int64]runtime.Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type HistoryVisitor struct {
|
type HistoryVisitor struct {
|
||||||
@ -101,24 +102,16 @@ type DeploymentHistoryViewer struct {
|
|||||||
// ViewHistory returns a revision-to-replicaset map as the revision history of a deployment
|
// ViewHistory returns a revision-to-replicaset map as the revision history of a deployment
|
||||||
// TODO: this should be a describer
|
// TODO: this should be a describer
|
||||||
func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
|
func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
|
||||||
versionedAppsClient := h.c.AppsV1()
|
allRSs, err := getDeploymentReplicaSets(h.c.AppsV1(), namespace, name)
|
||||||
deployment, err := versionedAppsClient.Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to retrieve deployment %s: %v", name, err)
|
return "", err
|
||||||
}
|
|
||||||
_, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, versionedAppsClient)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", name, err)
|
|
||||||
}
|
|
||||||
allRSs := allOldRSs
|
|
||||||
if newRS != nil {
|
|
||||||
allRSs = append(allRSs, newRS)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
historyInfo := make(map[int64]*corev1.PodTemplateSpec)
|
historyInfo := make(map[int64]*corev1.PodTemplateSpec)
|
||||||
for _, rs := range allRSs {
|
for _, rs := range allRSs {
|
||||||
v, err := deploymentutil.Revision(rs)
|
v, err := deploymentutil.Revision(rs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
klog.Warningf("unable to get revision from replicaset %s for deployment %s in namespace %s: %v", rs.Name, name, namespace, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
historyInfo[v] = &rs.Spec.Template
|
historyInfo[v] = &rs.Spec.Template
|
||||||
@ -165,6 +158,26 @@ func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision i
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHistory returns the ReplicaSet revisions associated with a Deployment
|
||||||
|
func (h *DeploymentHistoryViewer) GetHistory(namespace, name string) (map[int64]runtime.Object, error) {
|
||||||
|
allRSs, err := getDeploymentReplicaSets(h.c.AppsV1(), namespace, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[int64]runtime.Object)
|
||||||
|
for _, rs := range allRSs {
|
||||||
|
v, err := deploymentutil.Revision(rs)
|
||||||
|
if err != nil {
|
||||||
|
klog.Warningf("unable to get revision from replicaset %s for deployment %s in namespace %s: %v", rs.Name, name, namespace, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result[v] = rs
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func printTemplate(template *corev1.PodTemplateSpec) (string, error) {
|
func printTemplate(template *corev1.PodTemplateSpec) (string, error) {
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
w := describe.NewPrefixWriter(buf)
|
w := describe.NewPrefixWriter(buf)
|
||||||
@ -192,6 +205,25 @@ func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision in
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHistory returns the revisions associated with a DaemonSet
|
||||||
|
func (h *DaemonSetHistoryViewer) GetHistory(namespace, name string) (map[int64]runtime.Object, error) {
|
||||||
|
ds, history, err := daemonSetHistory(h.c.AppsV1(), namespace, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[int64]runtime.Object)
|
||||||
|
for _, h := range history {
|
||||||
|
applied, err := applyDaemonSetHistory(ds, h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[h.Revision] = applied
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// printHistory returns the podTemplate of the given revision if it is non-zero
|
// printHistory returns the podTemplate of the given revision if it is non-zero
|
||||||
// else returns the overall revisions
|
// else returns the overall revisions
|
||||||
func printHistory(history []*appsv1.ControllerRevision, revision int64, getPodTemplate func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error)) (string, error) {
|
func printHistory(history []*appsv1.ControllerRevision, revision int64, getPodTemplate func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error)) (string, error) {
|
||||||
@ -259,6 +291,42 @@ func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHistory returns the revisions associated with a StatefulSet
|
||||||
|
func (h *StatefulSetHistoryViewer) GetHistory(namespace, name string) (map[int64]runtime.Object, error) {
|
||||||
|
sts, history, err := statefulSetHistory(h.c.AppsV1(), namespace, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[int64]runtime.Object)
|
||||||
|
for _, h := range history {
|
||||||
|
applied, err := applyStatefulSetHistory(sts, h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[h.Revision] = applied
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDeploymentReplicaSets(apps clientappsv1.AppsV1Interface, namespace, name string) ([]*appsv1.ReplicaSet, error) {
|
||||||
|
deployment, err := apps.Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve deployment %s: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, oldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, apps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newRS == nil {
|
||||||
|
return oldRSs, nil
|
||||||
|
}
|
||||||
|
return append(oldRSs, newRS), nil
|
||||||
|
}
|
||||||
|
|
||||||
// controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor
|
// controlledHistories returns all ControllerRevisions in namespace that selected by selector and owned by accessor
|
||||||
// TODO: Rename this to controllerHistory when other controllers have been upgraded
|
// TODO: Rename this to controllerHistory when other controllers have been upgraded
|
||||||
func controlledHistoryV1(
|
func controlledHistoryV1(
|
||||||
|
@ -18,6 +18,7 @@ package polymorphichelpers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/json"
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
)
|
)
|
||||||
@ -52,6 +54,140 @@ func TestHistoryViewerFor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestViewDeploymentHistory(t *testing.T) {
|
||||||
|
trueVar := true
|
||||||
|
replicas := int32(1)
|
||||||
|
|
||||||
|
deployment := &appsv1.Deployment{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "moons",
|
||||||
|
Namespace: "default",
|
||||||
|
UID: "fc7e66ad-eacc-4413-8277-e22276eacce6",
|
||||||
|
Labels: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
Spec: appsv1.DeploymentSpec{
|
||||||
|
Selector: &metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
Replicas: &replicas,
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{{
|
||||||
|
Name: "test",
|
||||||
|
Image: fmt.Sprintf("foo:1"),
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeClientSet := fake.NewSimpleClientset(deployment)
|
||||||
|
|
||||||
|
replicaSets := map[int64]*appsv1.ReplicaSet{}
|
||||||
|
var i int64
|
||||||
|
for i = 1; i < 5; i++ {
|
||||||
|
rs := &appsv1.ReplicaSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("moons-%d", i),
|
||||||
|
Namespace: "default",
|
||||||
|
UID: types.UID(fmt.Sprintf("00000000-0000-0000-0000-00000000000%d", i)),
|
||||||
|
Labels: map[string]string{"foo": "bar"},
|
||||||
|
OwnerReferences: []metav1.OwnerReference{{"apps/v1", "Deployment", deployment.Name, deployment.UID, &trueVar, nil}},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"deployment.kubernetes.io/revision": fmt.Sprintf("%d", i),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: appsv1.ReplicaSetSpec{
|
||||||
|
Selector: &metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
Replicas: &replicas,
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{{
|
||||||
|
Name: "test",
|
||||||
|
Image: fmt.Sprintf("foo:%d", i),
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 3 {
|
||||||
|
rs.ObjectMeta.Annotations[ChangeCauseAnnotation] = "foo change cause"
|
||||||
|
} else if i == 4 {
|
||||||
|
rs.ObjectMeta.Annotations[ChangeCauseAnnotation] = "bar change cause"
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeClientSet.AppsV1().ReplicaSets("default").Create(context.TODO(), rs, metav1.CreateOptions{})
|
||||||
|
replicaSets[i] = rs
|
||||||
|
}
|
||||||
|
|
||||||
|
viewer := DeploymentHistoryViewer{fakeClientSet}
|
||||||
|
|
||||||
|
t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
|
||||||
|
result, err := viewer.ViewHistory("default", "moons", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting history for Deployment moons: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `REVISION CHANGE-CAUSE
|
||||||
|
1 <none>
|
||||||
|
2 <none>
|
||||||
|
3 foo change cause
|
||||||
|
4 bar change cause
|
||||||
|
`
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should describe a single revision", func(t *testing.T) {
|
||||||
|
result, err := viewer.ViewHistory("default", "moons", 3)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting history for Deployment moons: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `Pod Template:
|
||||||
|
Labels: foo=bar
|
||||||
|
Annotations: kubernetes.io/change-cause: foo change cause
|
||||||
|
Containers:
|
||||||
|
test:
|
||||||
|
Image: foo:3
|
||||||
|
Port: <none>
|
||||||
|
Host Port: <none>
|
||||||
|
Environment: <none>
|
||||||
|
Mounts: <none>
|
||||||
|
Volumes: <none>
|
||||||
|
`
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should get history", func(t *testing.T) {
|
||||||
|
result, err := viewer.GetHistory("default", "moons")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting history for Deployment moons: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) != 4 {
|
||||||
|
t.Fatalf("unexpected history length (expected 4, got %d", len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1; i < 4; i++ {
|
||||||
|
actual, found := result[i]
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("revision %d not found in history", i)
|
||||||
|
}
|
||||||
|
expected := replicaSets[i]
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("history does not match. expected %+v, got %+v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestViewHistory(t *testing.T) {
|
func TestViewHistory(t *testing.T) {
|
||||||
|
|
||||||
t.Run("for statefulSet", func(t *testing.T) {
|
t.Run("for statefulSet", func(t *testing.T) {
|
||||||
@ -138,6 +274,25 @@ func TestViewHistory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("should get history", func(t *testing.T) {
|
||||||
|
result, err := sts.GetHistory("default", "moons")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting history for StatefulSet moons: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("unexpected history length (expected 1, got %d", len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, found := result[1]
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("revision 1 not found in history")
|
||||||
|
}
|
||||||
|
expected := ssStub
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("history does not match. expected %+v, got %+v", expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("for daemonSet", func(t *testing.T) {
|
t.Run("for daemonSet", func(t *testing.T) {
|
||||||
@ -188,7 +343,7 @@ func TestViewHistory(t *testing.T) {
|
|||||||
t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
|
t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
|
||||||
result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 0)
|
result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err)
|
t.Fatalf("error getting ViewHistory for DaemonSet moons: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := `REVISION CHANGE-CAUSE
|
expected := `REVISION CHANGE-CAUSE
|
||||||
@ -203,7 +358,7 @@ func TestViewHistory(t *testing.T) {
|
|||||||
t.Run("should describe the revision if revision is specified", func(t *testing.T) {
|
t.Run("should describe the revision if revision is specified", func(t *testing.T) {
|
||||||
result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 1)
|
result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err)
|
t.Fatalf("error getting ViewHistory for DaemonSet moons: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := `Pod Template:
|
expected := `Pod Template:
|
||||||
@ -223,6 +378,25 @@ func TestViewHistory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("should get history", func(t *testing.T) {
|
||||||
|
result, err := daemonSetHistoryViewer.GetHistory("default", "moons")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting history for DaemonSet moons: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("unexpected history length (expected 1, got %d", len(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, found := result[1]
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("revision 1 not found in history")
|
||||||
|
}
|
||||||
|
expected := daemonSetStub
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("history does not match. expected %+v, got %+v", expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +83,23 @@ run_daemonset_history_tests() {
|
|||||||
kube::test::wait_object_assert daemonset "{{range.items}}{{${image_field1:?}}}:{{end}}" "${IMAGE_DAEMONSET_R2_2}:"
|
kube::test::wait_object_assert daemonset "{{range.items}}{{${image_field1:?}}}:{{end}}" "${IMAGE_DAEMONSET_R2_2}:"
|
||||||
kube::test::get_object_assert daemonset "{{range.items}}{{${container_len:?}}}{{end}}" "2"
|
kube::test::get_object_assert daemonset "{{range.items}}{{${container_len:?}}}{{end}}" "2"
|
||||||
kube::test::wait_object_assert controllerrevisions "{{range.items}}{{${annotations_field:?}}}:{{end}}" ".*rollingupdate-daemonset-rv2.yaml --record.*"
|
kube::test::wait_object_assert controllerrevisions "{{range.items}}{{${annotations_field:?}}}:{{end}}" ".*rollingupdate-daemonset-rv2.yaml --record.*"
|
||||||
|
# Get rollout history
|
||||||
|
output_message=$(kubectl rollout history daemonset)
|
||||||
|
kube::test::if_has_string "${output_message}" "daemonset.apps/bind"
|
||||||
|
kube::test::if_has_string "${output_message}" "REVISION CHANGE-CAUSE"
|
||||||
|
kube::test::if_has_string "${output_message}" "1 kubectl apply"
|
||||||
|
kube::test::if_has_string "${output_message}" "2 kubectl apply"
|
||||||
|
# Get rollout history for a single revision
|
||||||
|
output_message=$(kubectl rollout history daemonset --revision=1)
|
||||||
|
kube::test::if_has_string "${output_message}" "daemonset.apps/bind with revision #1"
|
||||||
|
kube::test::if_has_string "${output_message}" "Pod Template:"
|
||||||
|
kube::test::if_has_string "${output_message}" "${IMAGE_PAUSE_V2}"
|
||||||
|
# Get rollout history for a different single revision
|
||||||
|
output_message=$(kubectl rollout history daemonset --revision=2)
|
||||||
|
kube::test::if_has_string "${output_message}" "daemonset.apps/bind with revision #2"
|
||||||
|
kube::test::if_has_string "${output_message}" "Pod Template:"
|
||||||
|
kube::test::if_has_string "${output_message}" "${IMAGE_DAEMONSET_R2}"
|
||||||
|
kube::test::if_has_string "${output_message}" "${IMAGE_DAEMONSET_R2_2}"
|
||||||
# Rollback to revision 1 with dry-run - should be no-op
|
# Rollback to revision 1 with dry-run - should be no-op
|
||||||
kubectl rollout undo daemonset --dry-run=client "${kube_flags[@]:?}"
|
kubectl rollout undo daemonset --dry-run=client "${kube_flags[@]:?}"
|
||||||
kubectl rollout undo daemonset --dry-run=server "${kube_flags[@]:?}"
|
kubectl rollout undo daemonset --dry-run=server "${kube_flags[@]:?}"
|
||||||
@ -93,6 +110,12 @@ run_daemonset_history_tests() {
|
|||||||
kubectl rollout undo daemonset --to-revision=1 "${kube_flags[@]:?}"
|
kubectl rollout undo daemonset --to-revision=1 "${kube_flags[@]:?}"
|
||||||
kube::test::wait_object_assert daemonset "{{range.items}}{{${image_field0:?}}}:{{end}}" "${IMAGE_PAUSE_V2}:"
|
kube::test::wait_object_assert daemonset "{{range.items}}{{${image_field0:?}}}:{{end}}" "${IMAGE_PAUSE_V2}:"
|
||||||
kube::test::get_object_assert daemonset "{{range.items}}{{${container_len:?}}}{{end}}" "1"
|
kube::test::get_object_assert daemonset "{{range.items}}{{${container_len:?}}}{{end}}" "1"
|
||||||
|
# Get rollout history
|
||||||
|
output_message=$(kubectl rollout history daemonset)
|
||||||
|
kube::test::if_has_string "${output_message}" "daemonset.apps/bind"
|
||||||
|
kube::test::if_has_string "${output_message}" "REVISION CHANGE-CAUSE"
|
||||||
|
kube::test::if_has_string "${output_message}" "2 kubectl apply"
|
||||||
|
kube::test::if_has_string "${output_message}" "3 kubectl apply"
|
||||||
# Rollback to revision 1000000 - should fail
|
# Rollback to revision 1000000 - should fail
|
||||||
output_message=$(! kubectl rollout undo daemonset --to-revision=1000000 "${kube_flags[@]:?}" 2>&1)
|
output_message=$(! kubectl rollout undo daemonset --to-revision=1000000 "${kube_flags[@]:?}" 2>&1)
|
||||||
kube::test::if_has_string "${output_message}" "unable to find specified revision"
|
kube::test::if_has_string "${output_message}" "unable to find specified revision"
|
||||||
@ -103,6 +126,12 @@ run_daemonset_history_tests() {
|
|||||||
kube::test::wait_object_assert daemonset "{{range.items}}{{${image_field0:?}}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
|
kube::test::wait_object_assert daemonset "{{range.items}}{{${image_field0:?}}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
|
||||||
kube::test::wait_object_assert daemonset "{{range.items}}{{${image_field1:?}}}:{{end}}" "${IMAGE_DAEMONSET_R2_2}:"
|
kube::test::wait_object_assert daemonset "{{range.items}}{{${image_field1:?}}}:{{end}}" "${IMAGE_DAEMONSET_R2_2}:"
|
||||||
kube::test::get_object_assert daemonset "{{range.items}}{{${container_len:?}}}{{end}}" "2"
|
kube::test::get_object_assert daemonset "{{range.items}}{{${container_len:?}}}{{end}}" "2"
|
||||||
|
# Get rollout history
|
||||||
|
output_message=$(kubectl rollout history daemonset)
|
||||||
|
kube::test::if_has_string "${output_message}" "daemonset.apps/bind"
|
||||||
|
kube::test::if_has_string "${output_message}" "REVISION CHANGE-CAUSE"
|
||||||
|
kube::test::if_has_string "${output_message}" "3 kubectl apply"
|
||||||
|
kube::test::if_has_string "${output_message}" "4 kubectl apply"
|
||||||
# Clean up
|
# Clean up
|
||||||
kubectl delete -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]:?}"
|
kubectl delete -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]:?}"
|
||||||
|
|
||||||
@ -440,6 +469,40 @@ run_deployment_tests() {
|
|||||||
kubectl delete configmap test-set-env-config "${kube_flags[@]:?}"
|
kubectl delete configmap test-set-env-config "${kube_flags[@]:?}"
|
||||||
kubectl delete secret test-set-env-secret "${kube_flags[@]:?}"
|
kubectl delete secret test-set-env-secret "${kube_flags[@]:?}"
|
||||||
|
|
||||||
|
### Get rollout history
|
||||||
|
# Pre-condition: no deployment exists
|
||||||
|
kube::test::get_object_assert deployment "{{range.items}}{{${id_field:?}}}:{{end}}" ''
|
||||||
|
# Create a deployment
|
||||||
|
kubectl create -f hack/testdata/deployment-multicontainer.yaml "${kube_flags[@]:?}"
|
||||||
|
kube::test::get_object_assert deployment "{{range.items}}{{${id_field:?}}}:{{end}}" 'nginx-deployment:'
|
||||||
|
kube::test::get_object_assert deployment "{{range.items}}{{${image_field0:?}}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:"
|
||||||
|
kube::test::get_object_assert deployment "{{range.items}}{{${image_field1:?}}}:{{end}}" "${IMAGE_PERL}:"
|
||||||
|
# Set the deployment's image
|
||||||
|
kubectl set image deployment nginx-deployment nginx="${IMAGE_DEPLOYMENT_R2}" "${kube_flags[@]:?}"
|
||||||
|
kube::test::get_object_assert deployment "{{range.items}}{{${image_field0:?}}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:"
|
||||||
|
kube::test::get_object_assert deployment "{{range.items}}{{${image_field1:?}}}:{{end}}" "${IMAGE_PERL}:"
|
||||||
|
# Get rollout history
|
||||||
|
output_message=$(kubectl rollout history deployment nginx-deployment)
|
||||||
|
kube::test::if_has_string "${output_message}" "deployment.apps/nginx-deployment"
|
||||||
|
kube::test::if_has_string "${output_message}" "REVISION CHANGE-CAUSE"
|
||||||
|
kube::test::if_has_string "${output_message}" "1 <none>"
|
||||||
|
kube::test::if_has_string "${output_message}" "2 <none>"
|
||||||
|
kube::test::if_has_not_string "${output_message}" "3 <none>"
|
||||||
|
# Get rollout history for a single revision
|
||||||
|
output_message=$(kubectl rollout history deployment nginx-deployment --revision=1)
|
||||||
|
kube::test::if_has_string "${output_message}" "deployment.apps/nginx-deployment with revision #1"
|
||||||
|
kube::test::if_has_string "${output_message}" "Pod Template:"
|
||||||
|
kube::test::if_has_string "${output_message}" "${IMAGE_DEPLOYMENT_R1}"
|
||||||
|
kube::test::if_has_string "${output_message}" "${IMAGE_PERL}"
|
||||||
|
# Get rollout history for a different single revision
|
||||||
|
output_message=$(kubectl rollout history deployment nginx-deployment --revision=2)
|
||||||
|
kube::test::if_has_string "${output_message}" "deployment.apps/nginx-deployment with revision #2"
|
||||||
|
kube::test::if_has_string "${output_message}" "Pod Template:"
|
||||||
|
kube::test::if_has_string "${output_message}" "${IMAGE_DEPLOYMENT_R2}"
|
||||||
|
kube::test::if_has_string "${output_message}" "${IMAGE_PERL}"
|
||||||
|
# Clean up
|
||||||
|
kubectl delete deployment nginx-deployment "${kube_flags[@]:?}"
|
||||||
|
|
||||||
set +o nounset
|
set +o nounset
|
||||||
set +o errexit
|
set +o errexit
|
||||||
}
|
}
|
||||||
@ -468,6 +531,23 @@ run_statefulset_history_tests() {
|
|||||||
kube::test::wait_object_assert statefulset "{{range.items}}{{${image_field1:?}}}:{{end}}" "${IMAGE_PAUSE_V2}:"
|
kube::test::wait_object_assert statefulset "{{range.items}}{{${image_field1:?}}}:{{end}}" "${IMAGE_PAUSE_V2}:"
|
||||||
kube::test::get_object_assert statefulset "{{range.items}}{{${container_len:?}}}{{end}}" "2"
|
kube::test::get_object_assert statefulset "{{range.items}}{{${container_len:?}}}{{end}}" "2"
|
||||||
kube::test::wait_object_assert controllerrevisions "{{range.items}}{{${annotations_field:?}}}:{{end}}" ".*rollingupdate-statefulset-rv2.yaml --record.*"
|
kube::test::wait_object_assert controllerrevisions "{{range.items}}{{${annotations_field:?}}}:{{end}}" ".*rollingupdate-statefulset-rv2.yaml --record.*"
|
||||||
|
# Get rollout history
|
||||||
|
output_message=$(kubectl rollout history statefulset)
|
||||||
|
kube::test::if_has_string "${output_message}" "statefulset.apps/nginx"
|
||||||
|
kube::test::if_has_string "${output_message}" "REVISION CHANGE-CAUSE"
|
||||||
|
kube::test::if_has_string "${output_message}" "1 kubectl apply"
|
||||||
|
kube::test::if_has_string "${output_message}" "2 kubectl apply"
|
||||||
|
# Get rollout history for a single revision
|
||||||
|
output_message=$(kubectl rollout history statefulset --revision=1)
|
||||||
|
kube::test::if_has_string "${output_message}" "statefulset.apps/nginx with revision #1"
|
||||||
|
kube::test::if_has_string "${output_message}" "Pod Template:"
|
||||||
|
kube::test::if_has_string "${output_message}" "${IMAGE_STATEFULSET_R1}"
|
||||||
|
# Get rollout history for a different single revision
|
||||||
|
output_message=$(kubectl rollout history statefulset --revision=2)
|
||||||
|
kube::test::if_has_string "${output_message}" "statefulset.apps/nginx with revision #2"
|
||||||
|
kube::test::if_has_string "${output_message}" "Pod Template:"
|
||||||
|
kube::test::if_has_string "${output_message}" "${IMAGE_STATEFULSET_R2}"
|
||||||
|
kube::test::if_has_string "${output_message}" "${IMAGE_PAUSE_V2}"
|
||||||
# Rollback to revision 1 with dry-run - should be no-op
|
# Rollback to revision 1 with dry-run - should be no-op
|
||||||
kubectl rollout undo statefulset --dry-run=client "${kube_flags[@]:?}"
|
kubectl rollout undo statefulset --dry-run=client "${kube_flags[@]:?}"
|
||||||
kubectl rollout undo statefulset --dry-run=server "${kube_flags[@]:?}"
|
kubectl rollout undo statefulset --dry-run=server "${kube_flags[@]:?}"
|
||||||
@ -478,6 +558,12 @@ run_statefulset_history_tests() {
|
|||||||
kubectl rollout undo statefulset --to-revision=1 "${kube_flags[@]:?}"
|
kubectl rollout undo statefulset --to-revision=1 "${kube_flags[@]:?}"
|
||||||
kube::test::wait_object_assert statefulset "{{range.items}}{{${image_field0:?}}}:{{end}}" "${IMAGE_STATEFULSET_R1}:"
|
kube::test::wait_object_assert statefulset "{{range.items}}{{${image_field0:?}}}:{{end}}" "${IMAGE_STATEFULSET_R1}:"
|
||||||
kube::test::get_object_assert statefulset "{{range.items}}{{${container_len:?}}}{{end}}" "1"
|
kube::test::get_object_assert statefulset "{{range.items}}{{${container_len:?}}}{{end}}" "1"
|
||||||
|
# Get rollout history
|
||||||
|
output_message=$(kubectl rollout history statefulset)
|
||||||
|
kube::test::if_has_string "${output_message}" "statefulset.apps/nginx"
|
||||||
|
kube::test::if_has_string "${output_message}" "REVISION CHANGE-CAUSE"
|
||||||
|
kube::test::if_has_string "${output_message}" "2 kubectl apply"
|
||||||
|
kube::test::if_has_string "${output_message}" "3 kubectl apply"
|
||||||
# Rollback to revision 1000000 - should fail
|
# Rollback to revision 1000000 - should fail
|
||||||
output_message=$(! kubectl rollout undo statefulset --to-revision=1000000 "${kube_flags[@]:?}" 2>&1)
|
output_message=$(! kubectl rollout undo statefulset --to-revision=1000000 "${kube_flags[@]:?}" 2>&1)
|
||||||
kube::test::if_has_string "${output_message}" "unable to find specified revision"
|
kube::test::if_has_string "${output_message}" "unable to find specified revision"
|
||||||
@ -488,6 +574,12 @@ run_statefulset_history_tests() {
|
|||||||
kube::test::wait_object_assert statefulset "{{range.items}}{{${image_field0:?}}}:{{end}}" "${IMAGE_STATEFULSET_R2}:"
|
kube::test::wait_object_assert statefulset "{{range.items}}{{${image_field0:?}}}:{{end}}" "${IMAGE_STATEFULSET_R2}:"
|
||||||
kube::test::wait_object_assert statefulset "{{range.items}}{{${image_field1:?}}}:{{end}}" "${IMAGE_PAUSE_V2}:"
|
kube::test::wait_object_assert statefulset "{{range.items}}{{${image_field1:?}}}:{{end}}" "${IMAGE_PAUSE_V2}:"
|
||||||
kube::test::get_object_assert statefulset "{{range.items}}{{${container_len:?}}}{{end}}" "2"
|
kube::test::get_object_assert statefulset "{{range.items}}{{${container_len:?}}}{{end}}" "2"
|
||||||
|
# Get rollout history
|
||||||
|
output_message=$(kubectl rollout history statefulset)
|
||||||
|
kube::test::if_has_string "${output_message}" "statefulset.apps/nginx"
|
||||||
|
kube::test::if_has_string "${output_message}" "REVISION CHANGE-CAUSE"
|
||||||
|
kube::test::if_has_string "${output_message}" "3 kubectl apply"
|
||||||
|
kube::test::if_has_string "${output_message}" "4 kubectl apply"
|
||||||
# Clean up - delete newest configuration
|
# Clean up - delete newest configuration
|
||||||
kubectl delete -f hack/testdata/rollingupdate-statefulset-rv2.yaml "${kube_flags[@]:?}"
|
kubectl delete -f hack/testdata/rollingupdate-statefulset-rv2.yaml "${kube_flags[@]:?}"
|
||||||
# Post-condition: no pods from statefulset controller
|
# Post-condition: no pods from statefulset controller
|
||||||
|
Loading…
Reference in New Issue
Block a user