1
16
17 package rollout
18
19 import (
20 "fmt"
21 "sort"
22
23 "github.com/spf13/cobra"
24
25 "k8s.io/cli-runtime/pkg/genericclioptions"
26 "k8s.io/cli-runtime/pkg/genericiooptions"
27 "k8s.io/cli-runtime/pkg/printers"
28 "k8s.io/cli-runtime/pkg/resource"
29 cmdutil "k8s.io/kubectl/pkg/cmd/util"
30 "k8s.io/kubectl/pkg/polymorphichelpers"
31 "k8s.io/kubectl/pkg/scheme"
32 "k8s.io/kubectl/pkg/util/completion"
33 "k8s.io/kubectl/pkg/util/i18n"
34 "k8s.io/kubectl/pkg/util/templates"
35 )
36
37 var (
38 historyLong = templates.LongDesc(i18n.T(`
39 View previous rollout revisions and configurations.`))
40
41 historyExample = templates.Examples(`
42 # View the rollout history of a deployment
43 kubectl rollout history deployment/abc
44
45 # View the details of daemonset revision 3
46 kubectl rollout history daemonset/abc --revision=3`)
47 )
48
49
50 type RolloutHistoryOptions struct {
51 PrintFlags *genericclioptions.PrintFlags
52 ToPrinter func(string) (printers.ResourcePrinter, error)
53
54 Revision int64
55
56 Builder func() *resource.Builder
57 Resources []string
58 Namespace string
59 EnforceNamespace bool
60 LabelSelector string
61
62 HistoryViewer polymorphichelpers.HistoryViewerFunc
63 RESTClientGetter genericclioptions.RESTClientGetter
64
65 resource.FilenameOptions
66 genericiooptions.IOStreams
67 }
68
69
70 func NewRolloutHistoryOptions(streams genericiooptions.IOStreams) *RolloutHistoryOptions {
71 return &RolloutHistoryOptions{
72 PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme),
73 IOStreams: streams,
74 }
75 }
76
77
78 func NewCmdRolloutHistory(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
79 o := NewRolloutHistoryOptions(streams)
80
81 validArgs := []string{"deployment", "daemonset", "statefulset"}
82
83 cmd := &cobra.Command{
84 Use: "history (TYPE NAME | TYPE/NAME) [flags]",
85 DisableFlagsInUseLine: true,
86 Short: i18n.T("View rollout history"),
87 Long: historyLong,
88 Example: historyExample,
89 ValidArgsFunction: completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs),
90 Run: func(cmd *cobra.Command, args []string) {
91 cmdutil.CheckErr(o.Complete(f, cmd, args))
92 cmdutil.CheckErr(o.Validate())
93 cmdutil.CheckErr(o.Run())
94 },
95 }
96
97 cmd.Flags().Int64Var(&o.Revision, "revision", o.Revision, "See the details, including podTemplate of the revision specified")
98 cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector)
99
100 usage := "identifying the resource to get from a server."
101 cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
102
103 o.PrintFlags.AddFlags(cmd)
104
105 return cmd
106 }
107
108
109 func (o *RolloutHistoryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
110 o.Resources = args
111
112 var err error
113 if o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace(); err != nil {
114 return err
115 }
116
117 o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
118 o.PrintFlags.NamePrintFlags.Operation = operation
119 return o.PrintFlags.ToPrinter()
120 }
121
122 o.HistoryViewer = polymorphichelpers.HistoryViewerFn
123 o.RESTClientGetter = f
124 o.Builder = f.NewBuilder
125
126 return nil
127 }
128
129
130 func (o *RolloutHistoryOptions) Validate() error {
131 if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) {
132 return fmt.Errorf("required resource not specified")
133 }
134 if o.Revision < 0 {
135 return fmt.Errorf("revision must be a positive integer: %v", o.Revision)
136 }
137
138 return nil
139 }
140
141
142 func (o *RolloutHistoryOptions) Run() error {
143
144 r := o.Builder().
145 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
146 NamespaceParam(o.Namespace).DefaultNamespace().
147 FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
148 LabelSelectorParam(o.LabelSelector).
149 ResourceTypeOrNameArgs(true, o.Resources...).
150 ContinueOnError().
151 Latest().
152 Flatten().
153 Do()
154 if err := r.Err(); err != nil {
155 return err
156 }
157
158 if o.PrintFlags.OutputFlagSpecified() {
159 printer, err := o.PrintFlags.ToPrinter()
160 if err != nil {
161 return err
162 }
163
164 return r.Visit(func(info *resource.Info, err error) error {
165 if err != nil {
166 return err
167 }
168
169 mapping := info.ResourceMapping()
170 historyViewer, err := o.HistoryViewer(o.RESTClientGetter, mapping)
171 if err != nil {
172 return err
173 }
174 historyInfo, err := historyViewer.GetHistory(info.Namespace, info.Name)
175 if err != nil {
176 return err
177 }
178
179 if o.Revision > 0 {
180 printer.PrintObj(historyInfo[o.Revision], o.Out)
181 } else {
182 sortedKeys := make([]int64, 0, len(historyInfo))
183 for k := range historyInfo {
184 sortedKeys = append(sortedKeys, k)
185 }
186 sort.Slice(sortedKeys, func(i, j int) bool { return sortedKeys[i] < sortedKeys[j] })
187 for _, k := range sortedKeys {
188 printer.PrintObj(historyInfo[k], o.Out)
189 }
190 }
191
192 return nil
193 })
194 }
195
196 return r.Visit(func(info *resource.Info, err error) error {
197 if err != nil {
198 return err
199 }
200
201 mapping := info.ResourceMapping()
202 historyViewer, err := o.HistoryViewer(o.RESTClientGetter, mapping)
203 if err != nil {
204 return err
205 }
206 historyInfo, err := historyViewer.ViewHistory(info.Namespace, info.Name, o.Revision)
207 if err != nil {
208 return err
209 }
210
211 withRevision := ""
212 if o.Revision > 0 {
213 withRevision = fmt.Sprintf("with revision #%d", o.Revision)
214 }
215
216 printer, err := o.ToPrinter(fmt.Sprintf("%s\n%s", withRevision, historyInfo))
217 if err != nil {
218 return err
219 }
220
221 return printer.PrintObj(info.Object, o.Out)
222 })
223 }
224
View as plain text