1
2
3
4 package customprovider
5
6 import (
7 "context"
8 "fmt"
9 "strings"
10
11 apierrors "k8s.io/apimachinery/pkg/api/errors"
12 "k8s.io/apimachinery/pkg/api/meta"
13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
15 "k8s.io/apimachinery/pkg/runtime/schema"
16 "k8s.io/client-go/dynamic"
17 "k8s.io/kubectl/pkg/cmd/util"
18 "sigs.k8s.io/cli-utils/pkg/apis/actuation"
19 "sigs.k8s.io/cli-utils/pkg/common"
20 "sigs.k8s.io/cli-utils/pkg/inventory"
21 "sigs.k8s.io/cli-utils/pkg/object"
22 )
23
24 var InventoryCRD = []byte(strings.TrimSpace(`
25 apiVersion: apiextensions.k8s.io/v1
26 kind: CustomResourceDefinition
27 metadata:
28 name: inventories.cli-utils.example.io
29 spec:
30 conversion:
31 strategy: None
32 group: cli-utils.example.io
33 names:
34 kind: Inventory
35 listKind: InventoryList
36 plural: inventories
37 singular: inventory
38 scope: Namespaced
39 versions:
40 - name: v1alpha1
41 schema:
42 openAPIV3Schema:
43 description: Example for cli-utils e2e tests
44 properties:
45 spec:
46 properties:
47 objects:
48 items:
49 properties:
50 group:
51 type: string
52 kind:
53 type: string
54 name:
55 type: string
56 namespace:
57 type: string
58 required:
59 - group
60 - kind
61 - name
62 - namespace
63 type: object
64 type: array
65 type: object
66 status:
67 properties:
68 objects:
69 items:
70 properties:
71 group:
72 type: string
73 kind:
74 type: string
75 name:
76 type: string
77 namespace:
78 type: string
79 strategy:
80 type: string
81 actuation:
82 type: string
83 reconcile:
84 type: string
85 required:
86 - group
87 - kind
88 - name
89 - namespace
90 - strategy
91 - actuation
92 - reconcile
93 type: object
94 type: array
95 type: object
96 type: object
97 served: true
98 storage: true
99 subresources:
100 status: {}
101 `))
102
103 var InventoryGVK = schema.GroupVersionKind{
104 Group: "cli-utils.example.io",
105 Version: "v1alpha1",
106 Kind: "Inventory",
107 }
108
109 var _ inventory.ClientFactory = CustomClientFactory{}
110
111 type CustomClientFactory struct {
112 }
113
114 func (CustomClientFactory) NewClient(factory util.Factory) (inventory.Client, error) {
115 return inventory.NewClient(factory,
116 WrapInventoryObj, invToUnstructuredFunc, inventory.StatusPolicyAll, inventory.ConfigMapGVK)
117 }
118
119 func invToUnstructuredFunc(inv inventory.Info) *unstructured.Unstructured {
120 switch invInfo := inv.(type) {
121 case *InventoryCustomType:
122 return invInfo.inv
123 default:
124 return nil
125 }
126 }
127
128 func WrapInventoryObj(obj *unstructured.Unstructured) inventory.Storage {
129 return &InventoryCustomType{inv: obj}
130 }
131
132 func WrapInventoryInfoObj(obj *unstructured.Unstructured) inventory.Info {
133 return &InventoryCustomType{inv: obj}
134 }
135
136 var _ inventory.Storage = &InventoryCustomType{}
137 var _ inventory.Info = &InventoryCustomType{}
138
139 type InventoryCustomType struct {
140 inv *unstructured.Unstructured
141 }
142
143 func (i InventoryCustomType) Namespace() string {
144 return i.inv.GetNamespace()
145 }
146
147 func (i InventoryCustomType) Name() string {
148 return i.inv.GetName()
149 }
150
151 func (i InventoryCustomType) Strategy() inventory.Strategy {
152 return inventory.NameStrategy
153 }
154
155 func (i InventoryCustomType) ID() string {
156 labels := i.inv.GetLabels()
157 id, found := labels[common.InventoryLabel]
158 if !found {
159 return ""
160 }
161 return id
162 }
163
164 func (i InventoryCustomType) Load() (object.ObjMetadataSet, error) {
165 var inv object.ObjMetadataSet
166 s, found, err := unstructured.NestedSlice(i.inv.Object, "spec", "objects")
167 if err != nil {
168 return inv, err
169 }
170 if !found {
171 return inv, nil
172 }
173 for _, item := range s {
174 m := item.(map[string]interface{})
175 namespace, _, _ := unstructured.NestedString(m, "namespace")
176 name, _, _ := unstructured.NestedString(m, "name")
177 group, _, _ := unstructured.NestedString(m, "group")
178 kind, _, _ := unstructured.NestedString(m, "kind")
179 id := object.ObjMetadata{
180 Namespace: namespace,
181 Name: name,
182 GroupKind: schema.GroupKind{
183 Group: group,
184 Kind: kind,
185 },
186 }
187 inv = append(inv, id)
188 }
189 return inv, nil
190 }
191
192 func (i InventoryCustomType) Store(objs object.ObjMetadataSet, status []actuation.ObjectStatus) error {
193 var specObjs []interface{}
194 for _, obj := range objs {
195 specObjs = append(specObjs, map[string]interface{}{
196 "group": obj.GroupKind.Group,
197 "kind": obj.GroupKind.Kind,
198 "namespace": obj.Namespace,
199 "name": obj.Name,
200 })
201 }
202 var statusObjs []interface{}
203 for _, objStatus := range status {
204 statusObjs = append(statusObjs, map[string]interface{}{
205 "group": objStatus.Group,
206 "kind": objStatus.Kind,
207 "namespace": objStatus.Namespace,
208 "name": objStatus.Name,
209 "strategy": objStatus.Strategy.String(),
210 "actuation": objStatus.Actuation.String(),
211 "reconcile": objStatus.Reconcile.String(),
212 })
213 }
214 if len(specObjs) > 0 {
215 err := unstructured.SetNestedSlice(i.inv.Object, specObjs, "spec", "objects")
216 if err != nil {
217 return err
218 }
219 } else {
220 unstructured.RemoveNestedField(i.inv.Object, "spec")
221 }
222 if len(statusObjs) > 0 {
223 err := unstructured.SetNestedSlice(i.inv.Object, statusObjs, "status", "objects")
224 if err != nil {
225 return err
226 }
227 } else {
228 unstructured.RemoveNestedField(i.inv.Object, "status")
229 }
230 return nil
231 }
232
233 func (i InventoryCustomType) GetObject() (*unstructured.Unstructured, error) {
234 return i.inv, nil
235 }
236
237
238
239 func (i InventoryCustomType) Apply(dc dynamic.Interface, mapper meta.RESTMapper, _ inventory.StatusPolicy) error {
240 invInfo, namespacedClient, err := i.getNamespacedClient(dc, mapper)
241 if err != nil {
242 return err
243 }
244
245
246 clusterObj, err := namespacedClient.Get(context.TODO(), invInfo.GetName(), metav1.GetOptions{})
247 if err != nil && !apierrors.IsNotFound(err) {
248 return err
249 }
250
251 var appliedObj *unstructured.Unstructured
252
253 if clusterObj == nil {
254
255 appliedObj, err = namespacedClient.Create(context.TODO(), invInfo, metav1.CreateOptions{})
256 } else {
257
258 appliedObj, err = namespacedClient.Update(context.TODO(), invInfo, metav1.UpdateOptions{})
259 }
260 if err != nil {
261 return err
262 }
263
264
265 invInfo.SetResourceVersion(appliedObj.GetResourceVersion())
266 _, err = namespacedClient.UpdateStatus(context.TODO(), invInfo, metav1.UpdateOptions{})
267 return err
268 }
269
270 func (i InventoryCustomType) ApplyWithPrune(dc dynamic.Interface, mapper meta.RESTMapper, _ inventory.StatusPolicy, _ object.ObjMetadataSet) error {
271 invInfo, namespacedClient, err := i.getNamespacedClient(dc, mapper)
272 if err != nil {
273 return err
274 }
275
276
277 appliedObj, err := namespacedClient.Update(context.TODO(), invInfo, metav1.UpdateOptions{})
278 if err != nil {
279 return err
280 }
281
282
283 invInfo.SetResourceVersion(appliedObj.GetResourceVersion())
284 _, err = namespacedClient.UpdateStatus(context.TODO(), invInfo, metav1.UpdateOptions{})
285 return err
286 }
287
288 func (i InventoryCustomType) getNamespacedClient(dc dynamic.Interface, mapper meta.RESTMapper) (*unstructured.Unstructured, dynamic.ResourceInterface, error) {
289 invInfo, err := i.GetObject()
290 if err != nil {
291 return nil, nil, err
292 }
293 if invInfo == nil {
294 return nil, nil, fmt.Errorf("attempting to create a nil inventory object")
295 }
296
297 mapping, err := mapper.RESTMapping(invInfo.GroupVersionKind().GroupKind(), invInfo.GroupVersionKind().Version)
298 if err != nil {
299 return nil, nil, err
300 }
301
302
303 namespacedClient := dc.Resource(mapping.Resource).Namespace(invInfo.GetNamespace())
304
305 return invInfo, namespacedClient, nil
306 }
307
View as plain text