1
16
17 package apiclient
18
19 import (
20 "bufio"
21 "bytes"
22 "fmt"
23 "io"
24 "strings"
25
26 "github.com/pkg/errors"
27
28 v1 "k8s.io/api/core/v1"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/runtime"
31 "k8s.io/apimachinery/pkg/runtime/schema"
32 clientset "k8s.io/client-go/kubernetes"
33 fakeclientset "k8s.io/client-go/kubernetes/fake"
34 core "k8s.io/client-go/testing"
35 bootstrapapi "k8s.io/cluster-bootstrap/token/api"
36
37 kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
38 )
39
40
41 type DryRunGetter interface {
42 HandleGetAction(core.GetAction) (bool, runtime.Object, error)
43 HandleListAction(core.ListAction) (bool, runtime.Object, error)
44 }
45
46
47 type MarshalFunc func(runtime.Object, schema.GroupVersion) ([]byte, error)
48
49
50 func DefaultMarshalFunc(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
51 return kubeadmutil.MarshalToYaml(obj, gv)
52 }
53
54
55 type DryRunClientOptions struct {
56 Writer io.Writer
57 Getter DryRunGetter
58 PrependReactors []core.Reactor
59 AppendReactors []core.Reactor
60 MarshalFunc MarshalFunc
61 PrintGETAndLIST bool
62 }
63
64
65 func GetDefaultDryRunClientOptions(drg DryRunGetter, w io.Writer) DryRunClientOptions {
66 return DryRunClientOptions{
67 Writer: w,
68 Getter: drg,
69 PrependReactors: []core.Reactor{},
70 AppendReactors: []core.Reactor{},
71 MarshalFunc: DefaultMarshalFunc,
72 PrintGETAndLIST: false,
73 }
74 }
75
76
77
78 type actionWithName interface {
79 core.Action
80 GetName() string
81 }
82
83
84
85 type actionWithObject interface {
86 core.Action
87 GetObject() runtime.Object
88 }
89
90
91 func NewDryRunClient(drg DryRunGetter, w io.Writer) clientset.Interface {
92 return NewDryRunClientWithOpts(GetDefaultDryRunClientOptions(drg, w))
93 }
94
95
96
97
98 func NewDryRunClientWithOpts(opts DryRunClientOptions) clientset.Interface {
99
100 client := fakeclientset.NewSimpleClientset()
101
102
103 defaultReactorChain := []core.Reactor{
104
105 &core.SimpleReactor{
106 Verb: "*",
107 Resource: "*",
108 Reaction: func(action core.Action) (bool, runtime.Object, error) {
109 logDryRunAction(action, opts.Writer, opts.MarshalFunc)
110
111 return false, nil, nil
112 },
113 },
114
115
116 &core.SimpleReactor{
117 Verb: "get",
118 Resource: "*",
119 Reaction: func(action core.Action) (bool, runtime.Object, error) {
120 getAction, ok := action.(core.GetAction)
121 if !ok {
122
123
124
125 actionImpl, ok := action.(core.ActionImpl)
126 if ok {
127 getAction = core.GetActionImpl{ActionImpl: actionImpl}
128 } else {
129
130 return true, nil, errors.New("can't cast get reactor event action object to GetAction interface")
131 }
132 }
133 handled, obj, err := opts.Getter.HandleGetAction(getAction)
134
135 if opts.PrintGETAndLIST {
136
137 objBytes, err := opts.MarshalFunc(obj, action.GetResource().GroupVersion())
138 if err == nil {
139 fmt.Println("[dryrun] Returning faked GET response:")
140 PrintBytesWithLinePrefix(opts.Writer, objBytes, "\t")
141 }
142 }
143
144 return handled, obj, err
145 },
146 },
147
148
149 &core.SimpleReactor{
150 Verb: "list",
151 Resource: "*",
152 Reaction: func(action core.Action) (bool, runtime.Object, error) {
153 listAction, ok := action.(core.ListAction)
154 if !ok {
155
156 return true, nil, errors.New("can't cast list reactor event action object to ListAction interface")
157 }
158 handled, objs, err := opts.Getter.HandleListAction(listAction)
159
160 if opts.PrintGETAndLIST {
161
162 objBytes, err := opts.MarshalFunc(objs, action.GetResource().GroupVersion())
163 if err == nil {
164 fmt.Println("[dryrun] Returning faked LIST response:")
165 PrintBytesWithLinePrefix(opts.Writer, objBytes, "\t")
166 }
167 }
168
169 return handled, objs, err
170 },
171 },
172
173 &core.SimpleReactor{
174 Verb: "create",
175 Resource: "*",
176 Reaction: func(action core.Action) (bool, runtime.Object, error) {
177 objAction, ok := action.(actionWithObject)
178 if obj := objAction.GetObject(); ok && obj != nil {
179 if secret, ok := obj.(*v1.Secret); ok {
180 if secret.Namespace == metav1.NamespaceSystem && strings.HasPrefix(secret.Name, bootstrapapi.BootstrapTokenSecretPrefix) {
181
182
183 return false, nil, nil
184 }
185 }
186 }
187 return successfulModificationReactorFunc(action)
188 },
189 },
190 &core.SimpleReactor{
191 Verb: "update",
192 Resource: "*",
193 Reaction: successfulModificationReactorFunc,
194 },
195 &core.SimpleReactor{
196 Verb: "delete",
197 Resource: "*",
198 Reaction: successfulModificationReactorFunc,
199 },
200 &core.SimpleReactor{
201 Verb: "delete-collection",
202 Resource: "*",
203 Reaction: successfulModificationReactorFunc,
204 },
205 &core.SimpleReactor{
206 Verb: "patch",
207 Resource: "*",
208 Reaction: successfulModificationReactorFunc,
209 },
210 }
211
212
213
214 fullReactorChain := append(opts.PrependReactors, defaultReactorChain...)
215 fullReactorChain = append(fullReactorChain, opts.AppendReactors...)
216
217
218 client.Fake.ReactionChain = append(fullReactorChain, client.Fake.ReactionChain...)
219 return client
220 }
221
222
223 func successfulModificationReactorFunc(action core.Action) (bool, runtime.Object, error) {
224 objAction, ok := action.(actionWithObject)
225 if ok {
226 return true, objAction.GetObject(), nil
227 }
228 return true, nil, nil
229 }
230
231
232 func logDryRunAction(action core.Action, w io.Writer, marshalFunc MarshalFunc) {
233
234 group := action.GetResource().Group
235 if len(group) == 0 {
236 group = "core"
237 }
238 fmt.Fprintf(w, "[dryrun] Would perform action %s on resource %q in API group \"%s/%s\"\n", strings.ToUpper(action.GetVerb()), action.GetResource().Resource, group, action.GetResource().Version)
239
240 namedAction, ok := action.(actionWithName)
241 if ok {
242 fmt.Fprintf(w, "[dryrun] Resource name: %q\n", namedAction.GetName())
243 }
244
245 objAction, ok := action.(actionWithObject)
246 if ok && objAction.GetObject() != nil {
247
248 objBytes, err := marshalFunc(objAction.GetObject(), action.GetResource().GroupVersion())
249 if err == nil {
250 fmt.Println("[dryrun] Attached object:")
251 PrintBytesWithLinePrefix(w, objBytes, "\t")
252 }
253 }
254
255 patchAction, ok := action.(core.PatchAction)
256 if ok {
257
258 fmt.Fprintf(w, "[dryrun] Attached patch:\n\t%s\n", strings.Replace(string(patchAction.GetPatch()), `\"`, `"`, -1))
259 }
260 }
261
262
263 func PrintBytesWithLinePrefix(w io.Writer, objBytes []byte, linePrefix string) {
264 scanner := bufio.NewScanner(bytes.NewReader(objBytes))
265 for scanner.Scan() {
266 fmt.Fprintf(w, "%s%s\n", linePrefix, scanner.Text())
267 }
268 }
269
View as plain text