github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/cmd/helm/history.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"strconv"
    23  	"time"
    24  
    25  	"github.com/gosuri/uitable"
    26  	"github.com/spf13/cobra"
    27  
    28  	"github.com/stefanmcshane/helm/cmd/helm/require"
    29  	"github.com/stefanmcshane/helm/pkg/action"
    30  	"github.com/stefanmcshane/helm/pkg/chart"
    31  	"github.com/stefanmcshane/helm/pkg/cli/output"
    32  	"github.com/stefanmcshane/helm/pkg/release"
    33  	"github.com/stefanmcshane/helm/pkg/releaseutil"
    34  	helmtime "github.com/stefanmcshane/helm/pkg/time"
    35  )
    36  
    37  var historyHelp = `
    38  History prints historical revisions for a given release.
    39  
    40  A default maximum of 256 revisions will be returned. Setting '--max'
    41  configures the maximum length of the revision list returned.
    42  
    43  The historical release set is printed as a formatted table, e.g:
    44  
    45      $ helm history angry-bird
    46      REVISION    UPDATED                     STATUS          CHART             APP VERSION     DESCRIPTION
    47      1           Mon Oct 3 10:15:13 2016     superseded      alpine-0.1.0      1.0             Initial install
    48      2           Mon Oct 3 10:15:13 2016     superseded      alpine-0.1.0      1.0             Upgraded successfully
    49      3           Mon Oct 3 10:15:13 2016     superseded      alpine-0.1.0      1.0             Rolled back to 2
    50      4           Mon Oct 3 10:15:13 2016     deployed        alpine-0.1.0      1.0             Upgraded successfully
    51  `
    52  
    53  func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
    54  	client := action.NewHistory(cfg)
    55  	var outfmt output.Format
    56  
    57  	cmd := &cobra.Command{
    58  		Use:     "history RELEASE_NAME",
    59  		Long:    historyHelp,
    60  		Short:   "fetch release history",
    61  		Aliases: []string{"hist"},
    62  		Args:    require.ExactArgs(1),
    63  		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    64  			if len(args) != 0 {
    65  				return nil, cobra.ShellCompDirectiveNoFileComp
    66  			}
    67  			return compListReleases(toComplete, args, cfg)
    68  		},
    69  		RunE: func(cmd *cobra.Command, args []string) error {
    70  			history, err := getHistory(client, args[0])
    71  			if err != nil {
    72  				return err
    73  			}
    74  
    75  			return outfmt.Write(out, history)
    76  		},
    77  	}
    78  
    79  	f := cmd.Flags()
    80  	f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history")
    81  	bindOutputFlag(cmd, &outfmt)
    82  
    83  	return cmd
    84  }
    85  
    86  type releaseInfo struct {
    87  	Revision    int           `json:"revision"`
    88  	Updated     helmtime.Time `json:"updated"`
    89  	Status      string        `json:"status"`
    90  	Chart       string        `json:"chart"`
    91  	AppVersion  string        `json:"app_version"`
    92  	Description string        `json:"description"`
    93  }
    94  
    95  type releaseHistory []releaseInfo
    96  
    97  func (r releaseHistory) WriteJSON(out io.Writer) error {
    98  	return output.EncodeJSON(out, r)
    99  }
   100  
   101  func (r releaseHistory) WriteYAML(out io.Writer) error {
   102  	return output.EncodeYAML(out, r)
   103  }
   104  
   105  func (r releaseHistory) WriteTable(out io.Writer) error {
   106  	tbl := uitable.New()
   107  	tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "DESCRIPTION")
   108  	for _, item := range r {
   109  		tbl.AddRow(item.Revision, item.Updated.Format(time.ANSIC), item.Status, item.Chart, item.AppVersion, item.Description)
   110  	}
   111  	return output.EncodeTable(out, tbl)
   112  }
   113  
   114  func getHistory(client *action.History, name string) (releaseHistory, error) {
   115  	hist, err := client.Run(name)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	releaseutil.Reverse(hist, releaseutil.SortByRevision)
   121  
   122  	var rels []*release.Release
   123  	for i := 0; i < min(len(hist), client.Max); i++ {
   124  		rels = append(rels, hist[i])
   125  	}
   126  
   127  	if len(rels) == 0 {
   128  		return releaseHistory{}, nil
   129  	}
   130  
   131  	releaseHistory := getReleaseHistory(rels)
   132  
   133  	return releaseHistory, nil
   134  }
   135  
   136  func getReleaseHistory(rls []*release.Release) (history releaseHistory) {
   137  	for i := len(rls) - 1; i >= 0; i-- {
   138  		r := rls[i]
   139  		c := formatChartname(r.Chart)
   140  		s := r.Info.Status.String()
   141  		v := r.Version
   142  		d := r.Info.Description
   143  		a := formatAppVersion(r.Chart)
   144  
   145  		rInfo := releaseInfo{
   146  			Revision:    v,
   147  			Status:      s,
   148  			Chart:       c,
   149  			AppVersion:  a,
   150  			Description: d,
   151  		}
   152  		if !r.Info.LastDeployed.IsZero() {
   153  			rInfo.Updated = r.Info.LastDeployed
   154  
   155  		}
   156  		history = append(history, rInfo)
   157  	}
   158  
   159  	return history
   160  }
   161  
   162  func formatChartname(c *chart.Chart) string {
   163  	if c == nil || c.Metadata == nil {
   164  		// This is an edge case that has happened in prod, though we don't
   165  		// know how: https://github.com/helm/helm/issues/1347
   166  		return "MISSING"
   167  	}
   168  	return fmt.Sprintf("%s-%s", c.Name(), c.Metadata.Version)
   169  }
   170  
   171  func formatAppVersion(c *chart.Chart) string {
   172  	if c == nil || c.Metadata == nil {
   173  		// This is an edge case that has happened in prod, though we don't
   174  		// know how: https://github.com/helm/helm/issues/1347
   175  		return "MISSING"
   176  	}
   177  	return c.AppVersion()
   178  }
   179  
   180  func min(x, y int) int {
   181  	if x < y {
   182  		return x
   183  	}
   184  	return y
   185  }
   186  
   187  func compListRevisions(toComplete string, cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) {
   188  	client := action.NewHistory(cfg)
   189  
   190  	var revisions []string
   191  	if hist, err := client.Run(releaseName); err == nil {
   192  		for _, release := range hist {
   193  			appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion)
   194  			chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version)
   195  			revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", strconv.Itoa(release.Version), appVersion, chartDesc))
   196  		}
   197  		return revisions, cobra.ShellCompDirectiveNoFileComp
   198  	}
   199  	return nil, cobra.ShellCompDirectiveError
   200  }