From 8fb86a3d3b57d11f31e3aba66b607839c735004f Mon Sep 17 00:00:00 2001 From: Janet Kuo Date: Wed, 20 Jan 2016 15:48:52 -0800 Subject: [PATCH] Add kubectl rollout history --- .generated_docs | 2 + contrib/completions/bash/kubectl | 47 ++++++ docs/man/man1/kubectl-rollout-history.1 | 142 ++++++++++++++++++ docs/man/man1/kubectl-rollout.1 | 2 +- docs/user-guide/kubectl/kubectl_rollout.md | 1 + .../kubectl/kubectl_rollout_history.md | 93 ++++++++++++ .../deployment/deployment_controller.go | 14 +- pkg/kubectl/cmd/rollout/rollout.go | 2 + pkg/kubectl/cmd/rollout/rollout_history.go | 122 +++++++++++++++ pkg/kubectl/cmd/util/factory.go | 12 ++ pkg/kubectl/describe.go | 14 ++ pkg/kubectl/history.go | 139 +++++++++++++++++ pkg/util/deployment/deployment.go | 10 ++ 13 files changed, 588 insertions(+), 12 deletions(-) create mode 100644 docs/man/man1/kubectl-rollout-history.1 create mode 100644 docs/user-guide/kubectl/kubectl_rollout_history.md create mode 100644 pkg/kubectl/cmd/rollout/rollout_history.go create mode 100644 pkg/kubectl/history.go diff --git a/.generated_docs b/.generated_docs index 95a6fb76d42..0622264cd62 100644 --- a/.generated_docs +++ b/.generated_docs @@ -43,6 +43,7 @@ docs/man/man1/kubectl-port-forward.1 docs/man/man1/kubectl-proxy.1 docs/man/man1/kubectl-replace.1 docs/man/man1/kubectl-rolling-update.1 +docs/man/man1/kubectl-rollout-history.1 docs/man/man1/kubectl-rollout.1 docs/man/man1/kubectl-run.1 docs/man/man1/kubectl-scale.1 @@ -90,6 +91,7 @@ docs/user-guide/kubectl/kubectl_proxy.md docs/user-guide/kubectl/kubectl_replace.md docs/user-guide/kubectl/kubectl_rolling-update.md docs/user-guide/kubectl/kubectl_rollout.md +docs/user-guide/kubectl/kubectl_rollout_history.md docs/user-guide/kubectl/kubectl_run.md docs/user-guide/kubectl/kubectl_scale.md docs/user-guide/kubectl/kubectl_uncordon.md diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 8b4b246604b..e5235d5ccf3 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -1656,10 +1656,57 @@ _kubectl_autoscale() must_have_one_noun=() } +_kubectl_rollout_history() +{ + last_command="kubectl_rollout_history" + commands=() + + flags=() + two_word_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--filename=") + flags_with_completion+=("--filename") + flags_completion+=("__handle_filename_extension_flag json|yaml|yml") + two_word_flags+=("-f") + flags_with_completion+=("-f") + flags_completion+=("__handle_filename_extension_flag json|yaml|yml") + flags+=("--revision=") + flags+=("--alsologtostderr") + flags+=("--api-version=") + flags+=("--certificate-authority=") + flags+=("--client-certificate=") + flags+=("--client-key=") + flags+=("--cluster=") + flags+=("--context=") + flags+=("--insecure-skip-tls-verify") + flags+=("--kubeconfig=") + flags+=("--log-backtrace-at=") + flags+=("--log-dir=") + flags+=("--log-flush-frequency=") + flags+=("--logtostderr") + flags+=("--match-server-version") + flags+=("--namespace=") + flags+=("--password=") + flags+=("--server=") + two_word_flags+=("-s") + flags+=("--stderrthreshold=") + flags+=("--token=") + flags+=("--user=") + flags+=("--username=") + flags+=("--v=") + flags+=("--vmodule=") + + must_have_one_flag=() + must_have_one_noun=() +} + _kubectl_rollout() { last_command="kubectl_rollout" commands=() + commands+=("history") flags=() two_word_flags=() diff --git a/docs/man/man1/kubectl-rollout-history.1 b/docs/man/man1/kubectl-rollout-history.1 new file mode 100644 index 00000000000..b51355c2df4 --- /dev/null +++ b/docs/man/man1/kubectl-rollout-history.1 @@ -0,0 +1,142 @@ +.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" "" + + +.SH NAME +.PP +kubectl rollout history \- view rollout history + + +.SH SYNOPSIS +.PP +\fBkubectl rollout history\fP [OPTIONS] + + +.SH DESCRIPTION +.PP +view previous rollout revisions and configurations. + + +.SH OPTIONS +.PP +\fB\-f\fP, \fB\-\-filename\fP=[] + Filename, directory, or URL to a file identifying the resource to get from a server. + +.PP +\fB\-\-revision\fP=0 + See the details, including podTemplate of the revision specified + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-alsologtostderr\fP=false + log to standard error as well as files + +.PP +\fB\-\-api\-version\fP="" + The API version to use when talking to the server + +.PP +\fB\-\-certificate\-authority\fP="" + Path to a cert. file for the certificate authority. + +.PP +\fB\-\-client\-certificate\fP="" + Path to a client certificate file for TLS. + +.PP +\fB\-\-client\-key\fP="" + Path to a client key file for TLS. + +.PP +\fB\-\-cluster\fP="" + The name of the kubeconfig cluster to use + +.PP +\fB\-\-context\fP="" + The name of the kubeconfig context to use + +.PP +\fB\-\-insecure\-skip\-tls\-verify\fP=false + If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + +.PP +\fB\-\-kubeconfig\fP="" + Path to the kubeconfig file to use for CLI requests. + +.PP +\fB\-\-log\-backtrace\-at\fP=:0 + when logging hits line file:N, emit a stack trace + +.PP +\fB\-\-log\-dir\fP="" + If non\-empty, write log files in this directory + +.PP +\fB\-\-log\-flush\-frequency\fP=5s + Maximum number of seconds between log flushes + +.PP +\fB\-\-logtostderr\fP=true + log to standard error instead of files + +.PP +\fB\-\-match\-server\-version\fP=false + Require server version to match client version + +.PP +\fB\-\-namespace\fP="" + If present, the namespace scope for this CLI request. + +.PP +\fB\-\-password\fP="" + Password for basic authentication to the API server. + +.PP +\fB\-s\fP, \fB\-\-server\fP="" + The address and port of the Kubernetes API server + +.PP +\fB\-\-stderrthreshold\fP=2 + logs at or above this threshold go to stderr + +.PP +\fB\-\-token\fP="" + Bearer token for authentication to the API server. + +.PP +\fB\-\-user\fP="" + The name of the kubeconfig user to use + +.PP +\fB\-\-username\fP="" + Username for basic authentication to the API server. + +.PP +\fB\-\-v\fP=0 + log level for V logs + +.PP +\fB\-\-vmodule\fP= + comma\-separated list of pattern=N settings for file\-filtered logging + + +.SH EXAMPLE +.PP +.RS + +.nf +# View the rollout history of a deployment +$ kubectl rollout history deployment/abc + +.fi +.RE + + +.SH SEE ALSO +.PP +\fBkubectl\-rollout(1)\fP, + + +.SH HISTORY +.PP +January 2015, Originally compiled by Eric Paris (eparis at redhat dot com) based on the kubernetes source material, but hopefully they have been automatically generated since! diff --git a/docs/man/man1/kubectl-rollout.1 b/docs/man/man1/kubectl-rollout.1 index 1ea937708c7..99e74cb6ab4 100644 --- a/docs/man/man1/kubectl-rollout.1 +++ b/docs/man/man1/kubectl-rollout.1 @@ -112,7 +112,7 @@ rollout manages a deployment using subcommands .SH SEE ALSO .PP -\fBkubectl(1)\fP, +\fBkubectl(1)\fP, \fBkubectl\-rollout\-history(1)\fP, .SH HISTORY diff --git a/docs/user-guide/kubectl/kubectl_rollout.md b/docs/user-guide/kubectl/kubectl_rollout.md index f18e709708a..3e2c755dc7b 100644 --- a/docs/user-guide/kubectl/kubectl_rollout.md +++ b/docs/user-guide/kubectl/kubectl_rollout.md @@ -71,6 +71,7 @@ kubectl rollout SUBCOMMAND ### SEE ALSO * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager +* [kubectl rollout history](kubectl_rollout_history.md) - view rollout history ###### Auto generated by spf13/cobra on 20-Jan-2016 diff --git a/docs/user-guide/kubectl/kubectl_rollout_history.md b/docs/user-guide/kubectl/kubectl_rollout_history.md new file mode 100644 index 00000000000..8e22a432428 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_rollout_history.md @@ -0,0 +1,93 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +## kubectl rollout history + +view rollout history + +### Synopsis + + +view previous rollout revisions and configurations. + +``` +kubectl rollout history (TYPE NAME | TYPE/NAME) [flags] +``` + +### Examples + +``` +# View the rollout history of a deployment +$ kubectl rollout history deployment/abc +``` + +### Options + +``` + -f, --filename=[]: Filename, directory, or URL to a file identifying the resource to get from a server. + --revision=0: See the details, including podTemplate of the revision specified +``` + +### Options inherited from parent commands + +``` + --alsologtostderr[=false]: log to standard error as well as files + --api-version="": The API version to use when talking to the server + --certificate-authority="": Path to a cert. file for the certificate authority. + --client-certificate="": Path to a client certificate file for TLS. + --client-key="": Path to a client key file for TLS. + --cluster="": The name of the kubeconfig cluster to use + --context="": The name of the kubeconfig context to use + --insecure-skip-tls-verify[=false]: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + --kubeconfig="": Path to the kubeconfig file to use for CLI requests. + --log-backtrace-at=:0: when logging hits line file:N, emit a stack trace + --log-dir="": If non-empty, write log files in this directory + --log-flush-frequency=5s: Maximum number of seconds between log flushes + --logtostderr[=true]: log to standard error instead of files + --match-server-version[=false]: Require server version to match client version + --namespace="": If present, the namespace scope for this CLI request. + --password="": Password for basic authentication to the API server. + -s, --server="": The address and port of the Kubernetes API server + --stderrthreshold=2: logs at or above this threshold go to stderr + --token="": Bearer token for authentication to the API server. + --user="": The name of the kubeconfig user to use + --username="": Username for basic authentication to the API server. + --v=0: log level for V logs + --vmodule=: comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [kubectl rollout](kubectl_rollout.md) - rollout manages a deployment + +###### Auto generated by spf13/cobra on 29-Jan-2016 + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_rollout_history.md?pixel)]() + diff --git a/pkg/controller/deployment/deployment_controller.go b/pkg/controller/deployment/deployment_controller.go index f4948dd23da..ecd52bcf163 100644 --- a/pkg/controller/deployment/deployment_controller.go +++ b/pkg/controller/deployment/deployment_controller.go @@ -451,7 +451,7 @@ func (dc *DeploymentController) rollback(deployment *extensions.Deployment, toRe } } for _, rc := range allRCs { - v, err := revision(rc) + v, err := deploymentutil.Revision(rc) if err != nil { glog.V(4).Infof("Unable to extract revision from deployment's rc %q: %v", rc.Name, err) continue @@ -617,18 +617,10 @@ func (dc *DeploymentController) getNewRCAndAllOldRCs(deployment extensions.Deplo return dc.getNewRCAndMaybeFilteredOldRCs(deployment, false) } -func revision(rc *api.ReplicationController) (int64, error) { - v, ok := rc.Annotations[deploymentutil.RevisionAnnotation] - if !ok { - return 0, nil - } - return strconv.ParseInt(v, 10, 64) -} - func maxRevision(allRCs []*api.ReplicationController) int64 { max := int64(0) for _, rc := range allRCs { - if v, err := revision(rc); err != nil { + if v, err := deploymentutil.Revision(rc); err != nil { // Skip the RCs when it failed to parse their revision information glog.V(4).Infof("Error: %v. Couldn't parse revision for rc %#v, deployment controller will skip it when reconciling revisions.", err, rc) } else if v > max { @@ -642,7 +634,7 @@ func maxRevision(allRCs []*api.ReplicationController) int64 { func lastRevision(allRCs []*api.ReplicationController) int64 { max, secMax := int64(0), int64(0) for _, rc := range allRCs { - if v, err := revision(rc); err != nil { + if v, err := deploymentutil.Revision(rc); err != nil { // Skip the RCs when it failed to parse their revision information glog.V(4).Infof("Error: %v. Couldn't parse revision for rc %#v, deployment controller will skip it when reconciling revisions.", err, rc) } else if v >= max { diff --git a/pkg/kubectl/cmd/rollout/rollout.go b/pkg/kubectl/cmd/rollout/rollout.go index c09137c40fc..16e77940f28 100644 --- a/pkg/kubectl/cmd/rollout/rollout.go +++ b/pkg/kubectl/cmd/rollout/rollout.go @@ -43,5 +43,7 @@ func NewCmdRollout(f *cmdutil.Factory, out io.Writer) *cobra.Command { }, } + cmd.AddCommand(NewCmdRolloutHistory(f, out)) + return cmd } diff --git a/pkg/kubectl/cmd/rollout/rollout_history.go b/pkg/kubectl/cmd/rollout/rollout_history.go new file mode 100644 index 00000000000..f3eeb4a71af --- /dev/null +++ b/pkg/kubectl/cmd/rollout/rollout_history.go @@ -0,0 +1,122 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "fmt" + "io" + + "k8s.io/kubernetes/pkg/kubectl" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/util/errors" + + "github.com/spf13/cobra" +) + +// HistoryOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of +// referencing the cmd.Flags() +type HistoryOptions struct { + Filenames []string +} + +const ( + history_long = `view previous rollout revisions and configurations.` + history_example = `# View the rollout history of a deployment +$ kubectl rollout history deployment/abc` +) + +func NewCmdRolloutHistory(f *cmdutil.Factory, out io.Writer) *cobra.Command { + options := &HistoryOptions{} + + cmd := &cobra.Command{ + Use: "history (TYPE NAME | TYPE/NAME) [flags]", + Short: "view rollout history", + Long: history_long, + Example: history_example, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(RunHistory(f, cmd, out, args, options)) + }, + } + + cmd.Flags().Int64("revision", 0, "See the details, including podTemplate of the revision specified") + usage := "Filename, directory, or URL to a file identifying the resource to get from a server." + kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + return cmd +} + +func RunHistory(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []string, options *HistoryOptions) error { + if len(args) == 0 && len(options.Filenames) == 0 { + return cmdutil.UsageError(cmd, "Required resource not specified.") + } + revisionDetail := cmdutil.GetFlagInt64(cmd, "revision") + + mapper, typer := f.Object() + + cmdNamespace, enforceNamespace, err := f.DefaultNamespace() + if err != nil { + return err + } + + infos, err := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + NamespaceParam(cmdNamespace).DefaultNamespace(). + FilenameParam(enforceNamespace, options.Filenames...). + ResourceTypeOrNameArgs(true, args...). + Latest(). + Flatten(). + Do(). + Infos() + if err != nil { + return err + } + + errs := []error{} + for _, info := range infos { + mapping := info.ResourceMapping() + historyViewer, err := f.HistoryViewer(mapping) + if err != nil { + errs = append(errs, err) + continue + } + historyInfo, err := historyViewer.History(info.Namespace, info.Name) + if err != nil { + errs = append(errs, err) + continue + } + + formattedOutput := "" + if revisionDetail > 0 { + // Print details of a specific revision + template, ok := historyInfo.RevisionToTemplate[revisionDetail] + if !ok { + return fmt.Errorf("unable to find revision %d of %s %q", revisionDetail, mapping.Resource, info.Name) + } + fmt.Fprintf(out, "%s %q revision %d\n", mapping.Resource, info.Name, revisionDetail) + formattedOutput, err = kubectl.DescribePodTemplate(template) + } else { + // Print all revisions + formattedOutput, err = kubectl.PrintRolloutHistory(historyInfo, mapping.Resource, info.Name) + } + if err != nil { + errs = append(errs, err) + continue + } + fmt.Fprintf(out, "%s\n", formattedOutput) + } + + return errors.NewAggregate(errs) +} diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 3d52e44eba1..4ae94131ce7 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -39,6 +39,7 @@ import ( "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/extensions" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_1" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/kubectl" @@ -85,6 +86,8 @@ type Factory struct { Scaler func(mapping *meta.RESTMapping) (kubectl.Scaler, error) // Returns a Reaper for gracefully shutting down resources. Reaper func(mapping *meta.RESTMapping) (kubectl.Reaper, error) + // Returns a HistoryViewer for viewing change history + HistoryViewer func(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) // PodSelectorForObject returns the pod selector associated with the provided object PodSelectorForObject func(object runtime.Object) (string, error) // PortsForObject returns the ports associated with the provided object @@ -313,6 +316,15 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { } return kubectl.ReaperFor(mapping.GroupVersionKind.GroupKind(), client) }, + HistoryViewer: func(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) { + mappingVersion := mapping.GroupVersionKind.GroupVersion() + client, err := clients.ClientForVersion(&mappingVersion) + clientset := clientset.FromUnversionedClient(client) + if err != nil { + return nil, err + } + return kubectl.HistoryViewerFor(mapping.GroupVersionKind.GroupKind(), clientset) + }, Validator: func(validate bool, cacheDir string) (validation.Schema, error) { if validate { client, err := clients.ClientForVersion(nil) diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 65bd3c7edf2..7984b1dccac 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -885,6 +885,20 @@ func describeReplicationController(controller *api.ReplicationController, events }) } +func DescribePodTemplate(template *api.PodTemplateSpec) (string, error) { + return tabbedString(func(out io.Writer) error { + if template == nil { + fmt.Fprintf(out, "") + return nil + } + fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(template.Labels)) + fmt.Fprintf(out, "Annotations:\t%s\n", labels.FormatLabels(template.Annotations)) + fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&template.Spec)) + describeVolumes(template.Spec.Volumes, out) + return nil + }) +} + // JobDescriber generates information about a job and the pods it has created. type JobDescriber struct { client *client.Client diff --git a/pkg/kubectl/history.go b/pkg/kubectl/history.go new file mode 100644 index 00000000000..f864e535c2f --- /dev/null +++ b/pkg/kubectl/history.go @@ -0,0 +1,139 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 kubectl + +import ( + "fmt" + "io" + "sort" + "strconv" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_1" + "k8s.io/kubernetes/pkg/runtime" + deploymentutil "k8s.io/kubernetes/pkg/util/deployment" + "k8s.io/kubernetes/pkg/util/errors" +) + +const ( + ChangeCauseAnnotation = "kubernetes.io/change-cause" +) + +// HistoryViewer provides an interface for resources that can be rolled back. +type HistoryViewer interface { + History(namespace, name string) (HistoryInfo, error) +} + +func HistoryViewerFor(kind unversioned.GroupKind, c clientset.Interface) (HistoryViewer, error) { + switch kind { + case extensions.Kind("Deployment"): + return &DeploymentHistoryViewer{c}, nil + } + return nil, fmt.Errorf("no history viewer has been implemented for %q", kind) +} + +// HistoryInfo stores the mapping from revision to podTemplate; +// note that change-cause annotation should be copied to podTemplate +type HistoryInfo struct { + RevisionToTemplate map[int64]*api.PodTemplateSpec +} + +type DeploymentHistoryViewer struct { + c clientset.Interface +} + +// History returns a revision-to-RC map as the revision history of a deployment +func (h *DeploymentHistoryViewer) History(namespace, name string) (HistoryInfo, error) { + historyInfo := HistoryInfo{ + RevisionToTemplate: make(map[int64]*api.PodTemplateSpec), + } + deployment, err := h.c.Extensions().Deployments(namespace).Get(name) + if err != nil { + return historyInfo, fmt.Errorf("failed to retrieve deployment %s: %v", name, err) + } + _, allOldRCs, err := deploymentutil.GetOldRCs(*deployment, h.c) + if err != nil { + return historyInfo, fmt.Errorf("failed to retrieve old RCs from deployment %s: %v", name, err) + } + newRC, err := deploymentutil.GetNewRC(*deployment, h.c) + if err != nil { + return historyInfo, fmt.Errorf("failed to retrieve new RC from deployment %s: %v", name, err) + } + allRCs := append(allOldRCs, newRC) + for _, rc := range allRCs { + v, err := deploymentutil.Revision(rc) + if err != nil { + return historyInfo, fmt.Errorf("failed to retrieve revision out of RC %s from deployment %s: %v", rc.Name, name, err) + } + historyInfo.RevisionToTemplate[v] = rc.Spec.Template + changeCause, err := getChangeCause(rc) + if err != nil { + return historyInfo, fmt.Errorf("failed to retrieve change-cause out of RC %s from deployment %s: %v", rc.Name, name, err) + } + if len(changeCause) > 0 { + if historyInfo.RevisionToTemplate[v].Annotations == nil { + historyInfo.RevisionToTemplate[v].Annotations = make(map[string]string) + } + historyInfo.RevisionToTemplate[v].Annotations[ChangeCauseAnnotation] = changeCause + } + } + return historyInfo, nil +} + +// PrintRolloutHistory prints a formatted table of the input revision history of the deployment +func PrintRolloutHistory(historyInfo HistoryInfo, resource, name string) (string, error) { + if len(historyInfo.RevisionToTemplate) == 0 { + return fmt.Sprintf("No rollout history found in %s %q", resource, name), nil + } + // Sort the revisionToChangeCause map by revision + var revisions []string + for k := range historyInfo.RevisionToTemplate { + revisions = append(revisions, strconv.FormatInt(k, 10)) + } + sort.Strings(revisions) + + return tabbedString(func(out io.Writer) error { + fmt.Fprintf(out, "%s %q:\n", resource, name) + fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n") + errs := []error{} + for _, r := range revisions { + // Find the change-cause of revision r + r64, err := strconv.ParseInt(r, 10, 64) + if err != nil { + errs = append(errs, err) + continue + } + changeCause := historyInfo.RevisionToTemplate[r64].Annotations[ChangeCauseAnnotation] + if len(changeCause) == 0 { + changeCause = "" + } + fmt.Fprintf(out, "%s\t%s\n", r, changeCause) + } + return errors.NewAggregate(errs) + }) +} + +// getChangeCause returns the change-cause annotation of the input object +func getChangeCause(obj runtime.Object) (string, error) { + meta, err := api.ObjectMetaFor(obj) + if err != nil { + return "", err + } + return meta.Annotations[ChangeCauseAnnotation], nil +} diff --git a/pkg/util/deployment/deployment.go b/pkg/util/deployment/deployment.go index 15a346fb05c..cc7b874772f 100644 --- a/pkg/util/deployment/deployment.go +++ b/pkg/util/deployment/deployment.go @@ -18,6 +18,7 @@ package deployment import ( "fmt" + "strconv" "time" "k8s.io/kubernetes/pkg/api" @@ -202,3 +203,12 @@ func getPodsForRCs(c clientset.Interface, replicationControllers []*api.Replicat } return allPods, nil } + +// Revision returns the revision number of the input RC +func Revision(rc *api.ReplicationController) (int64, error) { + v, ok := rc.Annotations[RevisionAnnotation] + if !ok { + return 0, nil + } + return strconv.ParseInt(v, 10, 64) +}