Skip to content

Commit 7be21cf

Browse files
committed
Add validation of inventory-id for name strategy if inventory object already exists
1 parent ac40271 commit 7be21cf

File tree

6 files changed

+132
-16
lines changed

6 files changed

+132
-16
lines changed

pkg/apply/applier.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,29 @@ func (a *Applier) prepareObjects(localInv inventory.InventoryInfo, localObjs []*
9797
if err := inventory.ValidateNoInventory(localObjs); err != nil {
9898
return nil, err
9999
}
100+
101+
// If the inventory uses the Name strategy and an inventory ID is provided,
102+
// verify that the existing inventory object (if there is one) has an ID
103+
// label that matches.
104+
if localInv.Strategy() == inventory.NameStrategy && localInv.ID() != "" {
105+
invObjs, err := a.invClient.GetClusterInventoryObjs(localInv)
106+
if err != nil {
107+
return nil, err
108+
}
109+
110+
if len(invObjs) > 1 {
111+
panic(fmt.Errorf("found %d inv objects with Name strategy", len(invObjs)))
112+
}
113+
114+
if len(invObjs) == 1 {
115+
invObj := invObjs[0]
116+
val := invObj.GetLabels()[common.InventoryLabel]
117+
if val != localInv.ID() {
118+
return nil, fmt.Errorf("inventory-id of inventory object in cluster doesn't match provided id %q", localInv.ID())
119+
}
120+
}
121+
}
122+
100123
// Ensures the namespace exists before applying the inventory object into it.
101124
if invNamespace := inventoryNamespaceInSet(localInv, localObjs); invNamespace != nil {
102125
klog.V(4).Infof("applier prepareObjects applying namespace %s", invNamespace.GetName())

pkg/inventory/fake-inventory-client.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,7 @@ func (fic *FakeInventoryClient) GetClusterInventoryInfo(inv InventoryInfo) (*uns
8989
func (fic *FakeInventoryClient) UpdateLabels(inv InventoryInfo, labels map[string]string) error {
9090
return nil
9191
}
92+
93+
func (fic *FakeInventoryClient) GetClusterInventoryObjs(inv InventoryInfo) ([]*unstructured.Unstructured, error) {
94+
return []*unstructured.Unstructured{}, nil
95+
}

pkg/inventory/inventory-client.go

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ type InventoryClient interface {
4646
GetClusterInventoryInfo(inv InventoryInfo) (*unstructured.Unstructured, error)
4747
// UpdateLabels updates the labels of the cluster inventory object if it exists.
4848
UpdateLabels(InventoryInfo, map[string]string) error
49+
// GetInventoryObjs looks up the inventory objects from the cluster.
50+
GetClusterInventoryObjs(inv InventoryInfo) ([]*unstructured.Unstructured, error)
4951
}
5052

5153
// ClusterInventoryClient is a concrete implementation of the
@@ -251,20 +253,7 @@ func (cic *ClusterInventoryClient) GetClusterObjs(localInv InventoryInfo) ([]obj
251253
// TODO(seans3): Remove the special case code to merge multiple cluster inventory
252254
// objects once we've determined that this case is no longer possible.
253255
func (cic *ClusterInventoryClient) GetClusterInventoryInfo(inv InventoryInfo) (*unstructured.Unstructured, error) {
254-
if inv == nil {
255-
return nil, fmt.Errorf("inventoryInfo must be specified")
256-
}
257-
258-
var clusterInvObjects []*unstructured.Unstructured
259-
var err error
260-
switch inv.Strategy() {
261-
case NameStrategy:
262-
clusterInvObjects, err = cic.getClusterInventoryObjsByName(inv)
263-
case LabelStrategy:
264-
clusterInvObjects, err = cic.getClusterInventoryObjsByLabel(inv)
265-
default:
266-
panic(fmt.Errorf("unknown inventory strategy: %s", inv.Strategy()))
267-
}
256+
clusterInvObjects, err := cic.GetClusterInventoryObjs(inv)
268257
if err != nil {
269258
return nil, err
270259
}
@@ -359,6 +348,24 @@ func (cic *ClusterInventoryClient) UpdateLabels(inv InventoryInfo, labels map[st
359348
return cic.applyInventoryObj(obj)
360349
}
361350

351+
func (cic *ClusterInventoryClient) GetClusterInventoryObjs(inv InventoryInfo) ([]*unstructured.Unstructured, error) {
352+
if inv == nil {
353+
return nil, fmt.Errorf("inventoryInfo must be specified")
354+
}
355+
356+
var clusterInvObjects []*unstructured.Unstructured
357+
var err error
358+
switch inv.Strategy() {
359+
case NameStrategy:
360+
clusterInvObjects, err = cic.getClusterInventoryObjsByName(inv)
361+
case LabelStrategy:
362+
clusterInvObjects, err = cic.getClusterInventoryObjsByLabel(inv)
363+
default:
364+
panic(fmt.Errorf("unknown inventory strategy: %s", inv.Strategy()))
365+
}
366+
return clusterInvObjects, err
367+
}
368+
362369
// mergeClusterInventory merges the inventory of multiple inventory objects
363370
// into one inventory object, and applies it. Deletes the remaining unnecessary
364371
// inventory objects. There should be only one inventory object stored in the

test/e2e/common_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ func randomString(prefix string) string {
2727
return fmt.Sprintf("%s%s", prefix, randomSuffix)
2828
}
2929

30+
func run(ch <-chan event.Event) error {
31+
var err error
32+
for e := range ch {
33+
if e.Type == event.ErrorType {
34+
err = e.ErrorEvent.Err
35+
}
36+
}
37+
return err
38+
}
39+
3040
func runWithNoErr(ch <-chan event.Event) {
3141
runCollectNoErr(ch)
3242
}

test/e2e/e2e_test.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,13 @@ type InventoryConfig struct {
4545
InvCountVerifyFunc invCountVerifyFunc
4646
}
4747

48+
const (
49+
ConfigMapTypeInvConfig = "ConfigMap"
50+
CustomTypeInvConfig = "Custom"
51+
)
52+
4853
var inventoryConfigs = map[string]InventoryConfig{
49-
"ConfigMap (default)": {
54+
ConfigMapTypeInvConfig: {
5055
InventoryStrategy: inventory.LabelStrategy,
5156
InventoryFactoryFunc: cmInventoryManifest,
5257
InvWrapperFunc: inventory.WrapInventoryInfoObj,
@@ -55,7 +60,7 @@ var inventoryConfigs = map[string]InventoryConfig{
5560
InvSizeVerifyFunc: defaultInvSizeVerifyFunc,
5661
InvCountVerifyFunc: defaultInvCountVerifyFunc,
5762
},
58-
"Custom Type": {
63+
CustomTypeInvConfig: {
5964
InventoryStrategy: inventory.NameStrategy,
6065
InventoryFactoryFunc: customInventoryManifest,
6166
InvWrapperFunc: customprovider.WrapInventoryInfoObj,
@@ -144,6 +149,24 @@ var _ = Describe("Applier", func() {
144149
})
145150
})
146151
}
152+
153+
Context("InventoryStrategy: Name", func() {
154+
var namespace *v1.Namespace
155+
var inventoryName string
156+
157+
BeforeEach(func() {
158+
inventoryName = randomString("test-inv-")
159+
namespace = createRandomNamespace(c)
160+
})
161+
162+
AfterEach(func() {
163+
deleteNamespace(c, namespace)
164+
})
165+
166+
It("Apply with existing inventory", func() {
167+
applyWithExistingInvTest(c, inventoryConfigs[CustomTypeInvConfig], inventoryName, namespace.GetName())
168+
})
169+
})
147170
})
148171

149172
func createInventoryCRD(c client.Client) {

test/e2e/name_inv_strategy_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2021 The Kubernetes Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package e2e
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"time"
10+
11+
. "github.com/onsi/ginkgo"
12+
. "github.com/onsi/gomega"
13+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14+
"sigs.k8s.io/cli-utils/pkg/apply"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
)
17+
18+
func applyWithExistingInvTest(c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) {
19+
By("Apply first set of resources")
20+
applier := invConfig.ApplierFactoryFunc()
21+
orgInventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName)
22+
23+
orgApplyInv := invConfig.InvWrapperFunc(invConfig.InventoryFactoryFunc(inventoryName, namespaceName, orgInventoryID))
24+
25+
resources := []*unstructured.Unstructured{
26+
deploymentManifest(namespaceName),
27+
}
28+
29+
runWithNoErr(applier.Run(context.TODO(), orgApplyInv, resources, apply.Options{
30+
ReconcileTimeout: 2 * time.Minute,
31+
EmitStatusEvents: true,
32+
}))
33+
34+
By("Verify inventory")
35+
invConfig.InvSizeVerifyFunc(c, inventoryName, namespaceName, orgInventoryID, 1)
36+
37+
By("Apply second set of resources, using same inventory name but different ID")
38+
secondInventoryID := fmt.Sprintf("%s-%s-2", inventoryName, namespaceName)
39+
secondApplyInv := invConfig.InvWrapperFunc(invConfig.InventoryFactoryFunc(inventoryName, namespaceName, secondInventoryID))
40+
41+
err := run(applier.Run(context.TODO(), secondApplyInv, resources, apply.Options{
42+
ReconcileTimeout: 2 * time.Minute,
43+
EmitStatusEvents: true,
44+
}))
45+
46+
By("Verify that we get the correct error")
47+
Expect(err).To(HaveOccurred())
48+
Expect(err.Error()).To(ContainSubstring("inventory-id of inventory object in cluster doesn't match provided id"))
49+
}

0 commit comments

Comments
 (0)