diff --git a/go.mod b/go.mod index 042cf913f..4dd84384e 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( golang.org/x/sys v0.31.0 gopkg.in/evanphx/json-patch.v4 v4.12.0 k8s.io/api v0.0.0-20250411021314-16cedc7a66ae - k8s.io/apimachinery v0.0.0-20250411020758-955939ffb819 + k8s.io/apimachinery v0.0.0-20250513120515-173776a0582d k8s.io/cli-runtime v0.0.0-20250411033212-28113218b536 k8s.io/client-go v0.0.0-20250411022055-ecbbb0606499 k8s.io/component-base v0.0.0-20250411023622-8e0b9e84850c diff --git a/go.sum b/go.sum index c99e68796..df137d033 100644 --- a/go.sum +++ b/go.sum @@ -198,8 +198,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.0.0-20250411021314-16cedc7a66ae h1:vrcVMXGw+KvS3H1ADWBP3OnVjHD8POhZ2TTOhVZSrwU= k8s.io/api v0.0.0-20250411021314-16cedc7a66ae/go.mod h1:qoOZZa+05qIrm+4Sofop9RA1fVI3J149R2j6jm9m3VQ= -k8s.io/apimachinery v0.0.0-20250411020758-955939ffb819 h1:m17pTPsx0pRmIhMKKtNBYrT1LitJNib+nLiuIc8Fvk8= -k8s.io/apimachinery v0.0.0-20250411020758-955939ffb819/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apimachinery v0.0.0-20250513120515-173776a0582d h1:buOcd3rCTo99nqYltxp0diwOlu3UosLheP/6o6MxnuY= +k8s.io/apimachinery v0.0.0-20250513120515-173776a0582d/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= k8s.io/cli-runtime v0.0.0-20250411033212-28113218b536 h1:aRNcqamTSktUBoPyeK8sAGRCFstUhOXozbXlGOpgNI4= k8s.io/cli-runtime v0.0.0-20250411033212-28113218b536/go.mod h1:uyQE9Pml9RhjjShCJsPCN7kRPJ78VRXdMVqupw70/0A= k8s.io/client-go v0.0.0-20250411022055-ecbbb0606499 h1:EeffLakm6zD0zaErZUz+i6YubAlH+1a+MFp2cgg/cIQ= diff --git a/pkg/cmd/rollout/rollout_history.go b/pkg/cmd/rollout/rollout_history.go index 34379a45a..cde8501a3 100644 --- a/pkg/cmd/rollout/rollout_history.go +++ b/pkg/cmd/rollout/rollout_history.go @@ -105,7 +105,7 @@ func NewCmdRolloutHistory(f cmdutil.Factory, streams genericiooptions.IOStreams) return cmd } -// Complete completes al the required options +// Complete completes all the required options func (o *RolloutHistoryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { o.Resources = args @@ -177,7 +177,15 @@ func (o *RolloutHistoryOptions) Run() error { } if o.Revision > 0 { - printer.PrintObj(historyInfo[o.Revision], o.Out) + // Ensure the specified revision exists before printing + revision, exists := historyInfo[o.Revision] + if !exists { + return fmt.Errorf("unable to find the specified revision") + } + + if err := printer.PrintObj(revision, o.Out); err != nil { + return err + } } else { sortedKeys := make([]int64, 0, len(historyInfo)) for k := range historyInfo { diff --git a/pkg/cmd/rollout/rollout_history_test.go b/pkg/cmd/rollout/rollout_history_test.go index 71bb4f286..1c8baff29 100644 --- a/pkg/cmd/rollout/rollout_history_test.go +++ b/pkg/cmd/rollout/rollout_history_test.go @@ -401,6 +401,88 @@ replicaset.apps/rev2 } } +func TestRolloutHistoryErrors(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 := io.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 { + revision int64 + outputFormat string + expectedError string + }{ + "get non-existing revision as yaml": { + revision: 999, + outputFormat: "yaml", + expectedError: "unable to find the specified revision", + }, + "get non-existing revision as json": { + revision: 999, + outputFormat: "json", + expectedError: "unable to find the specified revision", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + fhv := setupFakeHistoryViewer(t) + fhv.getHistoryFn = func(namespace, name string) (map[int64]runtime.Object, error) { + return map[int64]runtime.Object{ + 1: &appsv1.ReplicaSet{ObjectMeta: v1.ObjectMeta{Name: "rev1"}}, + 2: &appsv1.ReplicaSet{ObjectMeta: v1.ObjectMeta{Name: "rev2"}}, + }, nil + } + + streams := genericiooptions.NewTestIOStreamsDiscard() + o := NewRolloutHistoryOptions(streams) + + printFlags := &genericclioptions.PrintFlags{ + JSONYamlPrintFlags: &genericclioptions.JSONYamlPrintFlags{ + ShowManagedFields: true, + }, + OutputFormat: &tc.outputFormat, + OutputFlagSpecified: func() bool { + return true + }, + } + + o.PrintFlags = printFlags + o.Revision = tc.revision + + if err := o.Complete(tf, nil, []string{"deployment/foo"}); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + err := o.Run() + if err != nil && err.Error() != tc.expectedError { + t.Fatalf("expected '%s' error, but got: %v", tc.expectedError, err) + } + }) + } +} + func TestValidate(t *testing.T) { opts := RolloutHistoryOptions{ Revision: 0,