rollout undo add dry-run implementation

This commit is contained in:
AdoHe 2016-09-08 08:02:04 +08:00
parent 85c91eb332
commit 6a68dbdac4
4 changed files with 73 additions and 11 deletions

View File

@ -1931,6 +1931,9 @@ __EOF__
# Update the deployment (revision 2) # Update the deployment (revision 2)
kubectl apply -f hack/testdata/deployment-revision2.yaml "${kube_flags[@]}" kubectl apply -f hack/testdata/deployment-revision2.yaml "${kube_flags[@]}"
kube::test::get_object_assert deployment.extensions "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:" kube::test::get_object_assert deployment.extensions "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:"
# Rollback to revision 1 with dry-run - should be no-op
kubectl rollout undo deployment nginx --to-revision=1 --dry-run=true "${kube_flags[@]}"
kube::test::get_object_assert deployment.extensions "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:"
# Rollback to revision 1 # Rollback to revision 1
kubectl rollout undo deployment nginx --to-revision=1 "${kube_flags[@]}" kubectl rollout undo deployment nginx --to-revision=1 "${kube_flags[@]}"
sleep 1 sleep 1

View File

@ -38,6 +38,7 @@ type UndoOptions struct {
Typer runtime.ObjectTyper Typer runtime.ObjectTyper
Infos []*resource.Info Infos []*resource.Info
ToRevision int64 ToRevision int64
DryRun bool
Out io.Writer Out io.Writer
Filenames []string Filenames []string
@ -52,7 +53,10 @@ var (
kubectl rollout undo deployment/abc kubectl rollout undo deployment/abc
# Rollback to deployment revision 3 # Rollback to deployment revision 3
kubectl rollout undo deployment/abc --to-revision=3`) kubectl rollout undo deployment/abc --to-revision=3
# Rollback to the previous deployment with dry-run
kubectl rollout undo --dry-run=true deployment/abc`)
) )
func NewCmdRolloutUndo(f *cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdRolloutUndo(f *cmdutil.Factory, out io.Writer) *cobra.Command {
@ -80,6 +84,7 @@ func NewCmdRolloutUndo(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().Int64("to-revision", 0, "The revision to rollback to. Default to 0 (last revision).") cmd.Flags().Int64("to-revision", 0, "The revision to rollback to. Default to 0 (last revision).")
usage := "Filename, directory, or URL to a file identifying the resource to get from a server." usage := "Filename, directory, or URL to a file identifying the resource to get from a server."
kubectl.AddJsonFilenameFlag(cmd, &opts.Filenames, usage) kubectl.AddJsonFilenameFlag(cmd, &opts.Filenames, usage)
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddRecursiveFlag(cmd, &opts.Recursive) cmdutil.AddRecursiveFlag(cmd, &opts.Recursive)
return cmd return cmd
} }
@ -92,6 +97,7 @@ func (o *UndoOptions) CompleteUndo(f *cmdutil.Factory, cmd *cobra.Command, out i
o.ToRevision = cmdutil.GetFlagInt64(cmd, "to-revision") o.ToRevision = cmdutil.GetFlagInt64(cmd, "to-revision")
o.Mapper, o.Typer = f.Object(false) o.Mapper, o.Typer = f.Object(false)
o.Out = out o.Out = out
o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run")
cmdNamespace, enforceNamespace, err := f.DefaultNamespace() cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil { if err != nil {
@ -129,7 +135,7 @@ func (o *UndoOptions) CompleteUndo(f *cmdutil.Factory, cmd *cobra.Command, out i
func (o *UndoOptions) RunUndo() error { func (o *UndoOptions) RunUndo() error {
allErrs := []error{} allErrs := []error{}
for ix, info := range o.Infos { for ix, info := range o.Infos {
result, err := o.Rollbackers[ix].Rollback(info.Object, nil, o.ToRevision) result, err := o.Rollbackers[ix].Rollback(info.Object, nil, o.ToRevision, o.DryRun)
if err != nil { if err != nil {
allErrs = append(allErrs, cmdutil.AddSourceToErr("undoing", info.Source, err)) allErrs = append(allErrs, cmdutil.AddSourceToErr("undoing", info.Source, err))
continue continue

View File

@ -631,10 +631,10 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
HistoryViewer: func(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) { HistoryViewer: func(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) {
mappingVersion := mapping.GroupVersionKind.GroupVersion() mappingVersion := mapping.GroupVersionKind.GroupVersion()
client, err := clients.ClientForVersion(&mappingVersion) client, err := clients.ClientForVersion(&mappingVersion)
clientset := clientset.FromUnversionedClient(client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
clientset := clientset.FromUnversionedClient(client)
return kubectl.HistoryViewerFor(mapping.GroupVersionKind.GroupKind(), clientset) return kubectl.HistoryViewerFor(mapping.GroupVersionKind.GroupKind(), clientset)
}, },
Rollbacker: func(mapping *meta.RESTMapping) (kubectl.Rollbacker, error) { Rollbacker: func(mapping *meta.RESTMapping) (kubectl.Rollbacker, error) {
@ -643,7 +643,8 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return kubectl.RollbackerFor(mapping.GroupVersionKind.GroupKind(), client) clientset := clientset.FromUnversionedClient(client)
return kubectl.RollbackerFor(mapping.GroupVersionKind.GroupKind(), clientset)
}, },
StatusViewer: func(mapping *meta.RESTMapping) (kubectl.StatusViewer, error) { StatusViewer: func(mapping *meta.RESTMapping) (kubectl.StatusViewer, error) {
mappingVersion := mapping.GroupVersionKind.GroupVersion() mappingVersion := mapping.GroupVersionKind.GroupVersion()

View File

@ -17,6 +17,7 @@ limitations under the License.
package kubectl package kubectl
import ( import (
"bytes"
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
@ -25,18 +26,19 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
sliceutil "k8s.io/kubernetes/pkg/util/slice"
"k8s.io/kubernetes/pkg/watch" "k8s.io/kubernetes/pkg/watch"
) )
// Rollbacker provides an interface for resources that can be rolled back. // Rollbacker provides an interface for resources that can be rolled back.
type Rollbacker interface { type Rollbacker interface {
Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64) (string, error) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error)
} }
func RollbackerFor(kind unversioned.GroupKind, c client.Interface) (Rollbacker, error) { func RollbackerFor(kind unversioned.GroupKind, c clientset.Interface) (Rollbacker, error) {
switch kind { switch kind {
case extensions.Kind("Deployment"): case extensions.Kind("Deployment"):
return &DeploymentRollbacker{c}, nil return &DeploymentRollbacker{c}, nil
@ -45,14 +47,17 @@ func RollbackerFor(kind unversioned.GroupKind, c client.Interface) (Rollbacker,
} }
type DeploymentRollbacker struct { type DeploymentRollbacker struct {
c client.Interface c clientset.Interface
} }
func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64) (string, error) { func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) {
d, ok := obj.(*extensions.Deployment) d, ok := obj.(*extensions.Deployment)
if !ok { if !ok {
return "", fmt.Errorf("passed object is not a Deployment: %#v", obj) return "", fmt.Errorf("passed object is not a Deployment: %#v", obj)
} }
if dryRun {
return simpleDryRun(d, r.c, toRevision)
}
if d.Spec.Paused { if d.Spec.Paused {
return "", fmt.Errorf("you cannot rollback a paused deployment; resume it first with 'kubectl rollout resume deployment/%s' and try again", d.Name) return "", fmt.Errorf("you cannot rollback a paused deployment; resume it first with 'kubectl rollout resume deployment/%s' and try again", d.Name)
} }
@ -66,7 +71,7 @@ func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations m
result := "" result := ""
// Get current events // Get current events
events, err := r.c.Events(d.Namespace).List(api.ListOptions{}) events, err := r.c.Core().Events(d.Namespace).List(api.ListOptions{})
if err != nil { if err != nil {
return result, err return result, err
} }
@ -75,7 +80,7 @@ func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations m
return result, err return result, err
} }
// Watch for the changes of events // Watch for the changes of events
watch, err := r.c.Events(d.Namespace).Watch(api.ListOptions{Watch: true, ResourceVersion: events.ResourceVersion}) watch, err := r.c.Core().Events(d.Namespace).Watch(api.ListOptions{Watch: true, ResourceVersion: events.ResourceVersion})
if err != nil { if err != nil {
return result, err return result, err
} }
@ -123,3 +128,50 @@ func isRollbackEvent(e *api.Event) (bool, string) {
} }
return false, "" return false, ""
} }
func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRevision int64) (string, error) {
_, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, c)
if err != nil {
return "", fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", deployment.Name, err)
}
allRSs := allOldRSs
if newRS != nil {
allRSs = append(allRSs, newRS)
}
revisionToSpec := make(map[int64]*api.PodTemplateSpec)
for _, rs := range allRSs {
v, err := deploymentutil.Revision(rs)
if err != nil {
continue
}
revisionToSpec[v] = &rs.Spec.Template
}
if len(revisionToSpec) == 0 {
return "No rollout history found.", nil
}
if toRevision > 0 {
template, ok := revisionToSpec[toRevision]
if !ok {
return "", fmt.Errorf("unable to find specified revision")
}
buf := bytes.NewBuffer([]byte{})
DescribePodTemplate(template, buf)
return buf.String(), nil
}
// Sort the revisionToSpec map by revision
revisions := make([]int64, 0, len(revisionToSpec))
for r := range revisionToSpec {
revisions = append(revisions, r)
}
sliceutil.SortInts64(revisions)
template, _ := revisionToSpec[revisions[len(revisions)-1]]
buf := bytes.NewBuffer([]byte{})
buf.WriteString("\n")
DescribePodTemplate(template, buf)
return buf.String(), nil
}