...
1
16
17 package genericclioptions
18
19 import (
20 "os"
21 "path/filepath"
22 "strings"
23
24 jsonpatch "github.com/evanphx/json-patch"
25 "github.com/spf13/cobra"
26 "github.com/spf13/pflag"
27
28 "k8s.io/apimachinery/pkg/api/meta"
29 "k8s.io/apimachinery/pkg/runtime"
30 "k8s.io/apimachinery/pkg/util/json"
31 )
32
33
34 const ChangeCauseAnnotation = "kubernetes.io/change-cause"
35
36
37 type RecordFlags struct {
38
39 Record *bool
40
41 changeCause string
42 }
43
44
45
46 func (f *RecordFlags) ToRecorder() (Recorder, error) {
47 if f == nil {
48 return NoopRecorder{}, nil
49 }
50
51 shouldRecord := false
52 if f.Record != nil {
53 shouldRecord = *f.Record
54 }
55
56
57
58 if !shouldRecord {
59 return NoopRecorder{}, nil
60 }
61
62 return &ChangeCauseRecorder{
63 changeCause: f.changeCause,
64 }, nil
65 }
66
67
68 func (f *RecordFlags) Complete(cmd *cobra.Command) error {
69 if f == nil {
70 return nil
71 }
72
73 f.changeCause = parseCommandArguments(cmd)
74 return nil
75 }
76
77
78 func (f *RecordFlags) CompleteWithChangeCause(cause string) error {
79 if f == nil {
80 return nil
81 }
82
83 f.changeCause = cause
84 return nil
85 }
86
87
88
89 func (f *RecordFlags) AddFlags(cmd *cobra.Command) {
90 if f == nil {
91 return
92 }
93
94 if f.Record != nil {
95 cmd.Flags().BoolVar(f.Record, "record", *f.Record, "Record current kubectl command in the resource annotation. If set to false, do not record the command. If set to true, record the command. If not set, default to updating the existing annotation value only if one already exists.")
96 cmd.Flags().MarkDeprecated("record", "--record will be removed in the future")
97 }
98 }
99
100
101 func NewRecordFlags() *RecordFlags {
102 record := false
103
104 return &RecordFlags{
105 Record: &record,
106 }
107 }
108
109
110 type Recorder interface {
111
112 Record(runtime.Object) error
113 MakeRecordMergePatch(runtime.Object) ([]byte, error)
114 }
115
116
117 type NoopRecorder struct{}
118
119
120 func (r NoopRecorder) Record(obj runtime.Object) error {
121 return nil
122 }
123
124
125 func (r NoopRecorder) MakeRecordMergePatch(obj runtime.Object) ([]byte, error) {
126 return nil, nil
127 }
128
129
130 type ChangeCauseRecorder struct {
131 changeCause string
132 }
133
134
135
136 func (r *ChangeCauseRecorder) Record(obj runtime.Object) error {
137 accessor, err := meta.Accessor(obj)
138 if err != nil {
139 return err
140 }
141 annotations := accessor.GetAnnotations()
142 if annotations == nil {
143 annotations = make(map[string]string)
144 }
145 annotations[ChangeCauseAnnotation] = r.changeCause
146 accessor.SetAnnotations(annotations)
147 return nil
148 }
149
150
151 func (r *ChangeCauseRecorder) MakeRecordMergePatch(obj runtime.Object) ([]byte, error) {
152
153 objCopy := obj.DeepCopyObject()
154 if err := r.Record(objCopy); err != nil {
155 return nil, err
156 }
157
158 oldData, err := json.Marshal(obj)
159 if err != nil {
160 return nil, err
161 }
162 newData, err := json.Marshal(objCopy)
163 if err != nil {
164 return nil, err
165 }
166
167 return jsonpatch.CreateMergePatch(oldData, newData)
168 }
169
170
171
172
173 func parseCommandArguments(cmd *cobra.Command) string {
174 if len(os.Args) == 0 {
175 return ""
176 }
177
178 flags := ""
179 parseFunc := func(flag *pflag.Flag, value string) error {
180 flags = flags + " --" + flag.Name
181 if set, ok := flag.Annotations["classified"]; !ok || len(set) == 0 {
182 flags = flags + "=" + value
183 } else {
184 flags = flags + "=CLASSIFIED"
185 }
186 return nil
187 }
188 var err error
189 err = cmd.Flags().ParseAll(os.Args[1:], parseFunc)
190 if err != nil || !cmd.Flags().Parsed() {
191 return ""
192 }
193
194 args := ""
195 if arguments := cmd.Flags().Args(); len(arguments) > 0 {
196 args = " " + strings.Join(arguments, " ")
197 }
198
199 base := filepath.Base(os.Args[0])
200 return base + args + flags
201 }
202
View as plain text