kubectl: refactor rollout history to be more configurable

ChangeCauseAnnotation is hardcoded in PrintRolloutHistory and it needs
to be overriden since other resources that may need to be added in
`kubectl rollout history` may not use it. Instead of adding one more
method in the factory, refactor the existing HistoryViewer interface
to accomodate the change.
This commit is contained in:
Michail Kargakis 2016-06-10 18:31:29 +02:00
parent 39bfa168cd
commit ad33c5c087
2 changed files with 38 additions and 48 deletions

View File

@ -70,7 +70,7 @@ func RunHistory(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []st
if len(args) == 0 && len(options.Filenames) == 0 { if len(args) == 0 && len(options.Filenames) == 0 {
return cmdutil.UsageError(cmd, "Required resource not specified.") return cmdutil.UsageError(cmd, "Required resource not specified.")
} }
revisionDetail := cmdutil.GetFlagInt64(cmd, "revision") revision := cmdutil.GetFlagInt64(cmd, "revision")
mapper, typer := f.Object(false) mapper, typer := f.Object(false)
@ -92,7 +92,7 @@ func RunHistory(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []st
return err return err
} }
err = r.Visit(func(info *resource.Info, err error) error { return r.Visit(func(info *resource.Info, err error) error {
if err != nil { if err != nil {
return err return err
} }
@ -101,28 +101,17 @@ func RunHistory(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []st
if err != nil { if err != nil {
return err return err
} }
historyInfo, err := historyViewer.History(info.Namespace, info.Name) historyInfo, err := historyViewer.ViewHistory(info.Namespace, info.Name, revision)
if err != nil { if err != nil {
return err return err
} }
if revisionDetail > 0 { header := fmt.Sprintf("%s %q", mapping.Resource, info.Name)
// Print details of a specific revision if revision > 0 {
template, ok := historyInfo.RevisionToTemplate[revisionDetail] header = fmt.Sprintf("%s with revision #%d", header, revision)
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)
kubectl.DescribePodTemplate(template, out)
} else {
// Print all revisions
formattedOutput, printErr := kubectl.PrintRolloutHistory(historyInfo, mapping.Resource, info.Name)
if printErr != nil {
return printErr
}
fmt.Fprintf(out, "%s\n", formattedOutput)
} }
fmt.Fprintf(out, "%s\n", header)
fmt.Fprintf(out, "%s\n", historyInfo)
return nil return nil
}) })
return err
} }

View File

@ -17,6 +17,7 @@ limitations under the License.
package kubectl package kubectl
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
@ -34,9 +35,9 @@ const (
ChangeCauseAnnotation = "kubernetes.io/change-cause" ChangeCauseAnnotation = "kubernetes.io/change-cause"
) )
// HistoryViewer provides an interface for resources that can be rolled back. // HistoryViewer provides an interface for resources have historical information.
type HistoryViewer interface { type HistoryViewer interface {
History(namespace, name string) (HistoryInfo, error) ViewHistory(namespace, name string, revision int64) (string, error)
} }
func HistoryViewerFor(kind unversioned.GroupKind, c clientset.Interface) (HistoryViewer, error) { func HistoryViewerFor(kind unversioned.GroupKind, c clientset.Interface) (HistoryViewer, error) {
@ -47,68 +48,68 @@ func HistoryViewerFor(kind unversioned.GroupKind, c clientset.Interface) (Histor
return nil, fmt.Errorf("no history viewer has been implemented for %q", kind) 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 { type DeploymentHistoryViewer struct {
c clientset.Interface c clientset.Interface
} }
// History returns a revision-to-replicaset map as the revision history of a deployment // ViewHistory returns a revision-to-replicaset map as the revision history of a deployment
func (h *DeploymentHistoryViewer) History(namespace, name string) (HistoryInfo, error) { func (h *DeploymentHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
historyInfo := HistoryInfo{
RevisionToTemplate: make(map[int64]*api.PodTemplateSpec),
}
deployment, err := h.c.Extensions().Deployments(namespace).Get(name) deployment, err := h.c.Extensions().Deployments(namespace).Get(name)
if err != nil { if err != nil {
return historyInfo, fmt.Errorf("failed to retrieve deployment %s: %v", name, err) return "", fmt.Errorf("failed to retrieve deployment %s: %v", name, err)
} }
_, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, h.c) _, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, h.c)
if err != nil { if err != nil {
return historyInfo, fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", name, err) return "", fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", name, err)
} }
allRSs := allOldRSs allRSs := allOldRSs
if newRS != nil { if newRS != nil {
allRSs = append(allRSs, newRS) allRSs = append(allRSs, newRS)
} }
historyInfo := make(map[int64]*api.PodTemplateSpec)
for _, rs := range allRSs { for _, rs := range allRSs {
v, err := deploymentutil.Revision(rs) v, err := deploymentutil.Revision(rs)
if err != nil { if err != nil {
continue continue
} }
historyInfo.RevisionToTemplate[v] = &rs.Spec.Template historyInfo[v] = &rs.Spec.Template
changeCause := getChangeCause(rs) changeCause := getChangeCause(rs)
if historyInfo.RevisionToTemplate[v].Annotations == nil { if historyInfo[v].Annotations == nil {
historyInfo.RevisionToTemplate[v].Annotations = make(map[string]string) historyInfo[v].Annotations = make(map[string]string)
} }
if len(changeCause) > 0 { if len(changeCause) > 0 {
historyInfo.RevisionToTemplate[v].Annotations[ChangeCauseAnnotation] = changeCause historyInfo[v].Annotations[ChangeCauseAnnotation] = changeCause
} }
} }
return historyInfo, nil
}
// PrintRolloutHistory prints a formatted table of the input revision history of the deployment if len(historyInfo) == 0 {
func PrintRolloutHistory(historyInfo HistoryInfo, resource, name string) (string, error) { return "No rollout history found.", nil
if len(historyInfo.RevisionToTemplate) == 0 {
return fmt.Sprintf("No rollout history found in %s %q", resource, name), nil
} }
if revision > 0 {
// Print details of a specific revision
template, ok := historyInfo[revision]
if !ok {
return "", fmt.Errorf("unable to find the specified revision")
}
buf := bytes.NewBuffer([]byte{})
DescribePodTemplate(template, buf)
return buf.String(), nil
}
// Sort the revisionToChangeCause map by revision // Sort the revisionToChangeCause map by revision
revisions := make([]int64, 0, len(historyInfo.RevisionToTemplate)) revisions := make([]int64, 0, len(historyInfo))
for r := range historyInfo.RevisionToTemplate { for r := range historyInfo {
revisions = append(revisions, r) revisions = append(revisions, r)
} }
sliceutil.SortInts64(revisions) sliceutil.SortInts64(revisions)
return tabbedString(func(out io.Writer) error { return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "%s %q:\n", resource, name)
fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n") fmt.Fprintf(out, "REVISION\tCHANGE-CAUSE\n")
for _, r := range revisions { for _, r := range revisions {
// Find the change-cause of revision r // Find the change-cause of revision r
changeCause := historyInfo.RevisionToTemplate[r].Annotations[ChangeCauseAnnotation] changeCause := historyInfo[r].Annotations[ChangeCauseAnnotation]
if len(changeCause) == 0 { if len(changeCause) == 0 {
changeCause = "<none>" changeCause = "<none>"
} }