Merge pull request #19893 from janetkuo/kubectl-rollout-history

Auto commit by PR queue bot
pull/6/head
k8s-merge-robot 2016-02-02 01:13:15 -08:00
commit 17a5058e83
13 changed files with 588 additions and 12 deletions

View File

@ -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

View File

@ -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=()

View File

@ -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!

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,93 @@
<!-- BEGIN MUNGE: UNVERSIONED_WARNING -->
<!-- BEGIN STRIP_FOR_RELEASE -->
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2>
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).
</strong>
--
<!-- END STRIP_FOR_RELEASE -->
<!-- END MUNGE: UNVERSIONED_WARNING -->
## 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
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_rollout_history.md?pixel)]()
<!-- END MUNGE: GENERATED_ANALYTICS -->

View File

@ -452,7 +452,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
@ -618,18 +618,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 {
@ -643,7 +635,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 {

View File

@ -43,5 +43,7 @@ func NewCmdRollout(f *cmdutil.Factory, out io.Writer) *cobra.Command {
},
}
cmd.AddCommand(NewCmdRolloutHistory(f, out))
return cmd
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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, "<no template>")
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

139
pkg/kubectl/history.go Normal file
View File

@ -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 = "<none>"
}
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
}

View File

@ -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)
}