diff --git a/hack/make-rules/test-cmd.sh b/hack/make-rules/test-cmd.sh index 5db88f33f17..edd3cd4a4b3 100755 --- a/hack/make-rules/test-cmd.sh +++ b/hack/make-rules/test-cmd.sh @@ -2168,6 +2168,8 @@ __EOF__ kubectl rollout undo deployment nginx "${kube_flags[@]}" # Check that the new replica set (nginx-618515232) has all old revisions stored in an annotation kubectl get rs nginx-618515232 -o yaml | grep "deployment.kubernetes.io/revision-history: 1,3" + # Check that trying to watch the status of a superseded revision returns an error + ! kubectl rollout status deployment/nginx --revision=3 # Clean up kubectl delete deployment nginx "${kube_flags[@]}" diff --git a/pkg/controller/deployment/util/deployment_util.go b/pkg/controller/deployment/util/deployment_util.go index 7362489e592..24c59ffdd98 100644 --- a/pkg/controller/deployment/util/deployment_util.go +++ b/pkg/controller/deployment/util/deployment_util.go @@ -27,11 +27,13 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/annotations" + "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/integer" intstrutil "k8s.io/kubernetes/pkg/util/intstr" @@ -117,9 +119,13 @@ func LastRevision(allRSs []*extensions.ReplicaSet) int64 { return secMax } -// Revision returns the revision number of the input replica set -func Revision(rs *extensions.ReplicaSet) (int64, error) { - v, ok := rs.Annotations[RevisionAnnotation] +// Revision returns the revision number of the input object. +func Revision(obj runtime.Object) (int64, error) { + acc, err := meta.Accessor(obj) + if err != nil { + return 0, err + } + v, ok := acc.GetAnnotations()[RevisionAnnotation] if !ok { return 0, nil } diff --git a/pkg/kubectl/cmd/rollout/rollout_history.go b/pkg/kubectl/cmd/rollout/rollout_history.go index af8cbe85b24..b759ee92aae 100644 --- a/pkg/kubectl/cmd/rollout/rollout_history.go +++ b/pkg/kubectl/cmd/rollout/rollout_history.go @@ -68,6 +68,9 @@ func RunHistory(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []st return cmdutil.UsageError(cmd, "Required resource not specified.") } revision := cmdutil.GetFlagInt64(cmd, "revision") + if revision < 0 { + return fmt.Errorf("revision must be a positive integer: %v", revision) + } mapper, typer := f.Object() diff --git a/pkg/kubectl/cmd/rollout/rollout_status.go b/pkg/kubectl/cmd/rollout/rollout_status.go index 84b217a0128..c738face420 100644 --- a/pkg/kubectl/cmd/rollout/rollout_status.go +++ b/pkg/kubectl/cmd/rollout/rollout_status.go @@ -32,11 +32,14 @@ import ( var ( status_long = dedent.Dedent(` - Show the status of the newest rollout. + Show the status of the rollout. - By default 'rollout status' will watch the status of the newest rollout + By default 'rollout status' will watch the status of the latest rollout until it's done. If you don't want to wait for the rollout to finish then - you can use --watch=false.`) + you can use --watch=false. Note that if a new rollout starts in-between, then + 'rollout status' will continue watching the latest revision. If you want to + pin to a specific revision and abort if it is rolled over by another revision, + use --revision=N where N is the revision you need to watch for.`) status_example = dedent.Dedent(` # Watch the rollout status of a deployment kubectl rollout status deployment/nginx`) @@ -50,7 +53,7 @@ func NewCmdRolloutStatus(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "status (TYPE NAME | TYPE/NAME) [flags]", - Short: "Show the status of newest rollout", + Short: "Show the status of the rollout", Long: status_long, Example: status_example, Run: func(cmd *cobra.Command, args []string) { @@ -62,7 +65,8 @@ func NewCmdRolloutStatus(f *cmdutil.Factory, out io.Writer) *cobra.Command { usage := "identifying the resource to get from a server." cmdutil.AddFilenameOptionFlags(cmd, options, usage) - cmd.Flags().BoolP("watch", "w", true, "Watch the status of the newest rollout until it's done.") + cmd.Flags().BoolP("watch", "w", true, "Watch the status of the rollout until it's done.") + cmd.Flags().Int64("revision", 0, "Pin to a specific revision for showing its status. Defaults to 0 (last revision).") return cmd } @@ -114,8 +118,13 @@ func RunStatus(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []str return err } + revision := cmdutil.GetFlagInt64(cmd, "revision") + if revision < 0 { + return fmt.Errorf("revision must be a positive integer: %v", revision) + } + // check if deployment's has finished the rollout - status, done, err := statusViewer.Status(cmdNamespace, info.Name) + status, done, err := statusViewer.Status(cmdNamespace, info.Name, revision) if err != nil { return err } @@ -140,7 +149,7 @@ func RunStatus(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []str return intr.Run(func() error { _, err := watch.Until(0, w, func(e watch.Event) (bool, error) { // print deployment's status - status, done, err := statusViewer.Status(cmdNamespace, info.Name) + status, done, err := statusViewer.Status(cmdNamespace, info.Name, revision) if err != nil { return false, err } diff --git a/pkg/kubectl/rollout_status.go b/pkg/kubectl/rollout_status.go index b65e5ada68d..b719cb3baf6 100644 --- a/pkg/kubectl/rollout_status.go +++ b/pkg/kubectl/rollout_status.go @@ -23,11 +23,12 @@ import ( "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned" + "k8s.io/kubernetes/pkg/controller/deployment/util" ) -// StatusViewer provides an interface for resources that provides rollout status. +// StatusViewer provides an interface for resources that have rollout status. type StatusViewer interface { - Status(namespace, name string) (string, bool, error) + Status(namespace, name string, revision int64) (string, bool, error) } func StatusViewerFor(kind unversioned.GroupKind, c internalclientset.Interface) (StatusViewer, error) { @@ -43,11 +44,20 @@ type DeploymentStatusViewer struct { } // Status returns a message describing deployment status, and a bool value indicating if the status is considered done -func (s *DeploymentStatusViewer) Status(namespace, name string) (string, bool, error) { +func (s *DeploymentStatusViewer) Status(namespace, name string, revision int64) (string, bool, error) { deployment, err := s.c.Deployments(namespace).Get(name) if err != nil { return "", false, err } + if revision > 0 { + deploymentRev, err := util.Revision(deployment) + if err != nil { + return "", false, fmt.Errorf("cannot get the revision of deployment %q: %v", deployment.Name, err) + } + if revision != deploymentRev { + return "", false, fmt.Errorf("desired revision (%d) is different from the running revision (%d)", revision, deploymentRev) + } + } if deployment.Generation <= deployment.Status.ObservedGeneration { if deployment.Status.UpdatedReplicas < deployment.Spec.Replicas { return fmt.Sprintf("Waiting for rollout to finish: %d out of %d new replicas have been updated...\n", deployment.Status.UpdatedReplicas, deployment.Spec.Replicas), false, nil @@ -58,7 +68,7 @@ func (s *DeploymentStatusViewer) Status(namespace, name string) (string, bool, e if deployment.Status.AvailableReplicas < deployment.Status.UpdatedReplicas { return fmt.Sprintf("Waiting for rollout to finish: %d of %d updated replicas are available...\n", deployment.Status.AvailableReplicas, deployment.Status.UpdatedReplicas), false, nil } - return fmt.Sprintf("deployment %s successfully rolled out\n", name), true, nil + return fmt.Sprintf("deployment %q successfully rolled out\n", name), true, nil } return fmt.Sprintf("Waiting for deployment spec update to be observed...\n"), false, nil } diff --git a/pkg/kubectl/rollout_status_test.go b/pkg/kubectl/rollout_status_test.go index 46daa42f0a4..1261f0c2cee 100644 --- a/pkg/kubectl/rollout_status_test.go +++ b/pkg/kubectl/rollout_status_test.go @@ -85,7 +85,7 @@ func TestDeploymentStatusViewerStatus(t *testing.T) { UnavailableReplicas: 0, }, - msg: "deployment foo successfully rolled out\n", + msg: "deployment \"foo\" successfully rolled out\n", done: true, }, { @@ -119,7 +119,7 @@ func TestDeploymentStatusViewerStatus(t *testing.T) { } client := fake.NewSimpleClientset(d).Extensions() dsv := &DeploymentStatusViewer{c: client} - msg, done, err := dsv.Status("bar", "foo") + msg, done, err := dsv.Status("bar", "foo", 0) if err != nil { t.Fatalf("DeploymentStatusViewer.Status(): %v", err) }