Skip to content

Commit 75cd160

Browse files
authored
Merge pull request #648 from sdowell/refactor-client-interface
chore: clean up inventory Client interface
2 parents c241e71 + 8408301 commit 75cd160

9 files changed

+129
-137
lines changed

pkg/apply/applier.go

-20
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,6 @@ func (a *Applier) prepareObjects(localInv inventory.Info, localObjs object.Unstr
6464
for _, localObj := range localObjs {
6565
inventory.AddInventoryIDAnnotation(localObj, localInv)
6666
}
67-
// If the inventory uses the Name strategy and an inventory ID is provided,
68-
// verify that the existing inventory object (if there is one) has an ID
69-
// label that matches.
70-
// TODO(seans): This inventory id validation should happen in destroy and status.
71-
if localInv.Strategy() == inventory.NameStrategy && localInv.ID() != "" {
72-
prevInvObjs, err := a.invClient.GetClusterInventoryObjs(localInv)
73-
if err != nil {
74-
return nil, nil, err
75-
}
76-
if len(prevInvObjs) > 1 {
77-
panic(fmt.Errorf("found %d inv objects with Name strategy", len(prevInvObjs)))
78-
}
79-
if len(prevInvObjs) == 1 {
80-
invObj := prevInvObjs[0]
81-
val := invObj.GetLabels()[common.InventoryLabel]
82-
if val != localInv.ID() {
83-
return nil, nil, fmt.Errorf("inventory-id of inventory object in cluster doesn't match provided id %q", localInv.ID())
84-
}
85-
}
86-
}
8767
pruneObjs, err := a.pruner.GetPruneObjs(localInv, localObjs, prune.Options{
8868
DryRunStrategy: o.DryRunStrategy,
8969
})

pkg/apply/solver/solver.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,13 @@ func (t *TaskQueueBuilder) Build(taskContext *taskrunner.TaskContext, o Options)
158158
// InvAddTask creates the inventory and adds any objects being applied
159159
klog.V(2).Infof("adding inventory add task (%d objects)", len(applyObjs))
160160
tasks = append(tasks, &task.InvAddTask{
161-
TaskName: "inventory-add-0",
162-
InvClient: t.InvClient,
163-
InvInfo: t.invInfo,
164-
Objects: applyObjs,
165-
DryRun: o.DryRunStrategy,
161+
TaskName: "inventory-add-0",
162+
InvClient: t.InvClient,
163+
DynamicClient: t.DynamicClient,
164+
Mapper: t.Mapper,
165+
InvInfo: t.invInfo,
166+
Objects: applyObjs,
167+
DryRun: o.DryRunStrategy,
166168
})
167169
}
168170

pkg/apply/solver/solver_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,8 @@ func TestTaskQueueBuilder_ApplyBuild(t *testing.T) {
803803
typedTask.Mapper = mapper
804804
case *taskrunner.WaitTask:
805805
typedTask.Mapper = mapper
806+
case *task.InvAddTask:
807+
typedTask.Mapper = mapper
806808
}
807809
}
808810

@@ -1477,6 +1479,8 @@ func TestTaskQueueBuilder_PruneBuild(t *testing.T) {
14771479
typedTask.Pruner = &prune.Pruner{}
14781480
case *taskrunner.WaitTask:
14791481
typedTask.Mapper = mapper
1482+
case *task.InvAddTask:
1483+
typedTask.Mapper = mapper
14801484
}
14811485
}
14821486

@@ -1831,6 +1835,8 @@ func TestTaskQueueBuilder_ApplyPruneBuild(t *testing.T) {
18311835
typedTask.Pruner = &prune.Pruner{}
18321836
case *taskrunner.WaitTask:
18331837
typedTask.Mapper = mapper
1838+
case *task.InvAddTask:
1839+
typedTask.Mapper = mapper
18341840
}
18351841
}
18361842

pkg/apply/task/inv_add_task.go

+51-11
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@
44
package task
55

66
import (
7+
"context"
8+
"fmt"
9+
10+
"k8s.io/apimachinery/pkg/api/meta"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
712
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
813
"k8s.io/apimachinery/pkg/runtime/schema"
14+
"k8s.io/client-go/dynamic"
915
"k8s.io/klog/v2"
16+
"k8s.io/kubectl/pkg/util"
1017
"sigs.k8s.io/cli-utils/pkg/apply/event"
1118
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
1219
"sigs.k8s.io/cli-utils/pkg/common"
@@ -22,11 +29,13 @@ var (
2229
// into the cluster. The InvAddTask should add/merge inventory references
2330
// before the actual object is applied.
2431
type InvAddTask struct {
25-
TaskName string
26-
InvClient inventory.Client
27-
InvInfo inventory.Info
28-
Objects object.UnstructuredSet
29-
DryRun common.DryRunStrategy
32+
TaskName string
33+
InvClient inventory.Client
34+
DynamicClient dynamic.Interface
35+
Mapper meta.RESTMapper
36+
InvInfo inventory.Info
37+
Objects object.UnstructuredSet
38+
DryRun common.DryRunStrategy
3039
}
3140

3241
func (i *InvAddTask) Name() string {
@@ -50,12 +59,14 @@ func (i *InvAddTask) Start(taskContext *taskrunner.TaskContext) {
5059
i.sendTaskResult(taskContext, err)
5160
return
5261
}
53-
// Ensures the namespace exists before applying the inventory object into it.
54-
if invNamespace := inventoryNamespaceInSet(i.InvInfo, i.Objects); invNamespace != nil {
55-
klog.V(4).Infof("applying inventory namespace %s", invNamespace.GetName())
56-
if err := i.InvClient.ApplyInventoryNamespace(invNamespace, i.DryRun); err != nil {
57-
i.sendTaskResult(taskContext, err)
58-
return
62+
// If the inventory is namespaced, ensure the namespace exists
63+
if i.InvInfo.Namespace() != "" {
64+
if invNamespace := inventoryNamespaceInSet(i.InvInfo, i.Objects); invNamespace != nil {
65+
if err := i.createNamespace(context.TODO(), invNamespace, i.DryRun); err != nil {
66+
err = fmt.Errorf("failed to create inventory namespace: %w", err)
67+
i.sendTaskResult(taskContext, err)
68+
return
69+
}
5970
}
6071
}
6172
klog.V(4).Infof("merging %d local objects into inventory", len(i.Objects))
@@ -90,6 +101,35 @@ func inventoryNamespaceInSet(inv inventory.Info, objs object.UnstructuredSet) *u
90101
return nil
91102
}
92103

104+
// createNamespace creates the specified namespace object
105+
func (i *InvAddTask) createNamespace(ctx context.Context, obj *unstructured.Unstructured, dryRun common.DryRunStrategy) error {
106+
if dryRun.ClientOrServerDryRun() {
107+
klog.V(4).Infof("skipped applying inventory namespace (dry-run): %s", obj.GetName())
108+
return nil
109+
}
110+
klog.V(4).Infof("applying inventory namespace: %s", obj.GetName())
111+
112+
nsObj := obj.DeepCopy()
113+
object.StripKyamlAnnotations(nsObj)
114+
if err := util.CreateApplyAnnotation(nsObj, unstructured.UnstructuredJSONScheme); err != nil {
115+
return err
116+
}
117+
118+
mapping, err := i.getMapping(obj)
119+
if err != nil {
120+
return err
121+
}
122+
123+
_, err = i.DynamicClient.Resource(mapping.Resource).Create(ctx, nsObj, metav1.CreateOptions{})
124+
return err
125+
}
126+
127+
// getMapping returns the RESTMapping for the provided resource.
128+
func (i *InvAddTask) getMapping(obj *unstructured.Unstructured) (*meta.RESTMapping, error) {
129+
gvk := obj.GroupVersionKind()
130+
return i.Mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
131+
}
132+
93133
func (i *InvAddTask) sendTaskResult(taskContext *taskrunner.TaskContext, err error) {
94134
klog.V(2).Infof("inventory add task completing (name: %q)", i.Name())
95135
taskContext.TaskChannel() <- taskrunner.TaskResult{

pkg/apply/task/inv_add_task_test.go

+47-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77
"testing"
88

99
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
10+
"k8s.io/apimachinery/pkg/runtime"
11+
clienttesting "k8s.io/client-go/testing"
12+
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
1013
"sigs.k8s.io/cli-utils/pkg/apply/cache"
1114
"sigs.k8s.io/cli-utils/pkg/apply/event"
1215
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
@@ -66,17 +69,30 @@ var obj3 = &unstructured.Unstructured{
6669
},
6770
}
6871

72+
var nsObj = &unstructured.Unstructured{
73+
Object: map[string]interface{}{
74+
"apiVersion": "v1",
75+
"kind": "Namespace",
76+
"metadata": map[string]interface{}{
77+
"name": namespace,
78+
},
79+
},
80+
}
81+
6982
const taskName = "test-inventory-task"
7083

7184
func TestInvAddTask(t *testing.T) {
7285
id1 := object.UnstructuredToObjMetadata(obj1)
7386
id2 := object.UnstructuredToObjMetadata(obj2)
7487
id3 := object.UnstructuredToObjMetadata(obj3)
88+
idNs := object.UnstructuredToObjMetadata(nsObj)
7589

7690
tests := map[string]struct {
77-
initialObjs object.ObjMetadataSet
78-
applyObjs []*unstructured.Unstructured
79-
expectedObjs object.ObjMetadataSet
91+
initialObjs object.ObjMetadataSet
92+
applyObjs []*unstructured.Unstructured
93+
expectedObjs object.ObjMetadataSet
94+
reactorError error
95+
expectCreateNamespace bool
8096
}{
8197
"no initial inventory and no apply objects; no merged inventory": {
8298
initialObjs: object.ObjMetadataSet{},
@@ -103,6 +119,12 @@ func TestInvAddTask(t *testing.T) {
103119
applyObjs: []*unstructured.Unstructured{obj2, obj3},
104120
expectedObjs: object.ObjMetadataSet{id1, id2, id3},
105121
},
122+
"namespace of inventory inside inventory": {
123+
initialObjs: object.ObjMetadataSet{},
124+
applyObjs: []*unstructured.Unstructured{nsObj},
125+
expectedObjs: object.ObjMetadataSet{idNs},
126+
expectCreateNamespace: true,
127+
},
106128
}
107129

108130
for name, tc := range tests {
@@ -111,12 +133,27 @@ func TestInvAddTask(t *testing.T) {
111133
eventChannel := make(chan event.Event)
112134
resourceCache := cache.NewResourceCacheMap()
113135
context := taskrunner.NewTaskContext(eventChannel, resourceCache)
136+
tf := cmdtesting.NewTestFactory().WithNamespace(namespace)
137+
defer tf.Cleanup()
138+
139+
createdNamespace := false
140+
tf.FakeDynamicClient.PrependReactor("create", "namespaces", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
141+
createdNamespace = true
142+
return true, nil, tc.reactorError
143+
})
144+
145+
mapper, err := tf.ToRESTMapper()
146+
if err != nil {
147+
t.Fatal(err)
148+
}
114149

115150
task := InvAddTask{
116-
TaskName: taskName,
117-
InvClient: client,
118-
InvInfo: nil,
119-
Objects: tc.applyObjs,
151+
TaskName: taskName,
152+
InvClient: client,
153+
InvInfo: localInv,
154+
Objects: tc.applyObjs,
155+
DynamicClient: tf.FakeDynamicClient,
156+
Mapper: mapper,
120157
}
121158
if taskName != task.Name() {
122159
t.Errorf("expected task name (%s), got (%s)", taskName, task.Name())
@@ -134,6 +171,9 @@ func TestInvAddTask(t *testing.T) {
134171
if !tc.expectedObjs.Equal(actual) {
135172
t.Errorf("expected merged inventory (%s), got (%s)", tc.expectedObjs, actual)
136173
}
174+
if createdNamespace != tc.expectCreateNamespace {
175+
t.Errorf("expected create namespace %v, got %v", tc.expectCreateNamespace, createdNamespace)
176+
}
137177
})
138178
}
139179
}

pkg/inventory/inventory-client.go

+14-40
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"k8s.io/client-go/dynamic"
1717
"k8s.io/klog/v2"
1818
cmdutil "k8s.io/kubectl/pkg/cmd/util"
19-
"k8s.io/kubectl/pkg/util"
2019
"sigs.k8s.io/cli-utils/pkg/apis/actuation"
2120
"sigs.k8s.io/cli-utils/pkg/common"
2221
"sigs.k8s.io/cli-utils/pkg/object"
@@ -39,12 +38,6 @@ type Client interface {
3938
Replace(inv Info, objs object.ObjMetadataSet, status []actuation.ObjectStatus, dryRun common.DryRunStrategy) error
4039
// DeleteInventoryObj deletes the passed inventory object from the APIServer.
4140
DeleteInventoryObj(inv Info, dryRun common.DryRunStrategy) error
42-
// ApplyInventoryNamespace applies the Namespace that the inventory object should be in.
43-
ApplyInventoryNamespace(invNamespace *unstructured.Unstructured, dryRun common.DryRunStrategy) error
44-
// GetClusterInventoryInfo returns the cluster inventory object.
45-
GetClusterInventoryInfo(inv Info) (*unstructured.Unstructured, error)
46-
// GetClusterInventoryObjs looks up the inventory objects from the cluster.
47-
GetClusterInventoryObjs(inv Info) (object.UnstructuredSet, error)
4841
// ListClusterInventoryObjs returns a map mapping from inventory name to a list of cluster inventory objects
4942
ListClusterInventoryObjs(ctx context.Context) (map[string]object.ObjMetadataSet, error)
5043
}
@@ -105,7 +98,7 @@ func NewClient(factory cmdutil.Factory,
10598
func (cic *ClusterClient) Merge(localInv Info, objs object.ObjMetadataSet, dryRun common.DryRunStrategy) (object.ObjMetadataSet, error) {
10699
pruneIds := object.ObjMetadataSet{}
107100
invObj := cic.invToUnstructuredFunc(localInv)
108-
clusterInv, err := cic.GetClusterInventoryInfo(localInv)
101+
clusterInv, err := cic.getClusterInventoryInfo(localInv)
109102
if err != nil {
110103
return pruneIds, err
111104
}
@@ -174,7 +167,7 @@ func (cic *ClusterClient) Replace(localInv Info, objs object.ObjMetadataSet, sta
174167
klog.V(4).Infoln("dry-run replace inventory object: not applied")
175168
return nil
176169
}
177-
clusterInv, err := cic.GetClusterInventoryInfo(localInv)
170+
clusterInv, err := cic.getClusterInventoryInfo(localInv)
178171
if err != nil {
179172
return fmt.Errorf("failed to read inventory from cluster: %w", err)
180173
}
@@ -256,7 +249,7 @@ func (cic *ClusterClient) deleteInventoryObjsByLabel(inv Info, dryRun common.Dry
256249
// an error if one occurred.
257250
func (cic *ClusterClient) GetClusterObjs(localInv Info) (object.ObjMetadataSet, error) {
258251
var objs object.ObjMetadataSet
259-
clusterInv, err := cic.GetClusterInventoryInfo(localInv)
252+
clusterInv, err := cic.getClusterInventoryInfo(localInv)
260253
if err != nil {
261254
return objs, fmt.Errorf("failed to read inventory from cluster: %w", err)
262255
}
@@ -277,8 +270,8 @@ func (cic *ClusterClient) GetClusterObjs(localInv Info) (object.ObjMetadataSet,
277270
//
278271
// TODO(seans3): Remove the special case code to merge multiple cluster inventory
279272
// objects once we've determined that this case is no longer possible.
280-
func (cic *ClusterClient) GetClusterInventoryInfo(inv Info) (*unstructured.Unstructured, error) {
281-
clusterInvObjects, err := cic.GetClusterInventoryObjs(inv)
273+
func (cic *ClusterClient) getClusterInventoryInfo(inv Info) (*unstructured.Unstructured, error) {
274+
clusterInvObjects, err := cic.getClusterInventoryObjs(inv)
282275
if err != nil {
283276
return nil, fmt.Errorf("failed to read inventory objects from cluster: %w", err)
284277
}
@@ -344,10 +337,18 @@ func (cic *ClusterClient) getClusterInventoryObjsByName(inv Info) (object.Unstru
344337
if apierrors.IsNotFound(err) {
345338
return object.UnstructuredSet{}, nil
346339
}
340+
if inv.ID() != "" {
341+
if inventoryID, err := retrieveInventoryLabel(clusterInv); err != nil {
342+
return nil, err
343+
} else if inv.ID() != inventoryID {
344+
return nil, fmt.Errorf("inventory-id of inventory object %s/%s in cluster doesn't match provided id %q",
345+
inv.Namespace(), inv.Name(), inv.ID())
346+
}
347+
}
347348
return object.UnstructuredSet{clusterInv}, nil
348349
}
349350

350-
func (cic *ClusterClient) GetClusterInventoryObjs(inv Info) (object.UnstructuredSet, error) {
351+
func (cic *ClusterClient) getClusterInventoryObjs(inv Info) (object.UnstructuredSet, error) {
351352
if inv == nil {
352353
return nil, fmt.Errorf("inventoryInfo must be specified")
353354
}
@@ -443,33 +444,6 @@ func (cic *ClusterClient) deleteInventoryObjByName(obj *unstructured.Unstructure
443444
Delete(context.TODO(), obj.GetName(), metav1.DeleteOptions{})
444445
}
445446

446-
// ApplyInventoryNamespace creates the passed namespace if it does not already
447-
// exist, or returns an error if one happened. NOTE: No error if already exists.
448-
func (cic *ClusterClient) ApplyInventoryNamespace(obj *unstructured.Unstructured, dryRun common.DryRunStrategy) error {
449-
if dryRun.ClientOrServerDryRun() {
450-
klog.V(4).Infof("dry-run apply inventory namespace (%s): not applied", obj.GetName())
451-
return nil
452-
}
453-
454-
invNamespace := obj.DeepCopy()
455-
klog.V(4).Infof("applying inventory namespace: %s", obj.GetName())
456-
object.StripKyamlAnnotations(invNamespace)
457-
if err := util.CreateApplyAnnotation(invNamespace, unstructured.UnstructuredJSONScheme); err != nil {
458-
return err
459-
}
460-
461-
mapping, err := cic.getMapping(obj)
462-
if err != nil {
463-
return err
464-
}
465-
466-
_, err = cic.dc.Resource(mapping.Resource).Create(context.TODO(), invNamespace, metav1.CreateOptions{})
467-
if apierrors.IsAlreadyExists(err) {
468-
return nil
469-
}
470-
return err
471-
}
472-
473447
// getMapping returns the RESTMapping for the provided resource.
474448
func (cic *ClusterClient) getMapping(obj *unstructured.Unstructured) (*meta.RESTMapping, error) {
475449
return cic.mapper.RESTMapping(obj.GroupVersionKind().GroupKind(), obj.GroupVersionKind().Version)

0 commit comments

Comments
 (0)