/
storage.go
174 lines (158 loc) · 5.88 KB
/
storage.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
// This file contains code for a "inventory" object which
// stores object metadata to keep track of sets of
// resources. This "inventory" object must be a ConfigMap
// and it stores the object metadata in the data field
// of the ConfigMap. By storing metadata from all applied
// objects, we can correctly prune and teardown sets
// of resources.
package inventory
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
"k8s.io/klog/v2"
"sigs.k8s.io/cli-utils/pkg/apis/actuation"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/object"
)
// The default inventory name stored in the inventory template.
const legacyInvName = "inventory"
// Storage describes methods necessary for an object which
// can persist the object metadata for pruning and other group
// operations.
type Storage interface {
// Load retrieves the set of object metadata from the inventory object
Load() (object.ObjMetadataSet, error)
// Store the set of object metadata in the inventory object. This will
// replace the metadata, spec and status.
Store(objs object.ObjMetadataSet, status []actuation.ObjectStatus) error
// GetObject returns the object that stores the inventory
GetObject() (*unstructured.Unstructured, error)
// Apply applies the inventory object. This utility function is used
// in InventoryClient.Merge and merges the metadata, spec and status.
Apply(dynamic.Interface, meta.RESTMapper, StatusPolicy) error
// ApplyWithPrune applies the inventory object with a set of pruneIDs of
// objects to be pruned (object.ObjMetadataSet). This function is used in
// InventoryClient.Replace. pruneIDs are required for enabling custom logic
// handling of multiple ResourceGroup inventories.
ApplyWithPrune(dynamic.Interface, meta.RESTMapper, StatusPolicy, object.ObjMetadataSet) error
}
// StorageFactoryFunc creates the object which implements the Inventory
// interface from the passed info object.
type StorageFactoryFunc func(*unstructured.Unstructured) Storage
// ToUnstructuredFunc returns the unstructured object for the
// given Info.
type ToUnstructuredFunc func(Info) *unstructured.Unstructured
// FindInventoryObj returns the "Inventory" object (ConfigMap with
// inventory label) if it exists, or nil if it does not exist.
func FindInventoryObj(objs object.UnstructuredSet) *unstructured.Unstructured {
for _, obj := range objs {
if IsInventoryObject(obj) {
return obj
}
}
return nil
}
// IsInventoryObject returns true if the passed object has the
// inventory label.
func IsInventoryObject(obj *unstructured.Unstructured) bool {
if obj == nil {
return false
}
inventoryLabel, err := retrieveInventoryLabel(obj)
if err == nil && len(inventoryLabel) > 0 {
return true
}
return false
}
// retrieveInventoryLabel returns the string value of the InventoryLabel
// for the passed inventory object. Returns error if the passed object is nil or
// is not a inventory object.
func retrieveInventoryLabel(obj *unstructured.Unstructured) (string, error) {
inventoryLabel, exists := obj.GetLabels()[common.InventoryLabel]
if !exists {
return "", fmt.Errorf("inventory label does not exist for inventory object: %s", common.InventoryLabel)
}
return inventoryLabel, nil
}
// ValidateNoInventory takes a set of unstructured.Unstructured objects and
// validates that no inventory object is in the input slice.
func ValidateNoInventory(objs object.UnstructuredSet) error {
invs := make(object.UnstructuredSet, 0)
for _, obj := range objs {
if IsInventoryObject(obj) {
invs = append(invs, obj)
}
}
if len(invs) == 0 {
return nil
}
return &MultipleInventoryObjError{
InventoryObjectTemplates: invs,
}
}
// splitUnstructureds takes a set of unstructured.Unstructured objects and
// splits it into one set that contains the inventory object templates and
// another one that contains the remaining resources. Returns an error if there
// there is no inventory object or more than one inventory objects.
func SplitUnstructureds(objs object.UnstructuredSet) (*unstructured.Unstructured, object.UnstructuredSet, error) {
invs := make(object.UnstructuredSet, 0)
resources := make(object.UnstructuredSet, 0)
for _, obj := range objs {
if IsInventoryObject(obj) {
invs = append(invs, obj)
} else {
resources = append(resources, obj)
}
}
var inv *unstructured.Unstructured
var err error
switch len(invs) {
case 0:
err = &NoInventoryObjError{}
case 1:
inv = invs[0]
default:
err = &MultipleInventoryObjError{InventoryObjectTemplates: invs}
}
return inv, resources, err
}
// addSuffixToName adds the passed suffix (usually a hash) as a suffix
// to the name of the passed object stored in the Info struct. Returns
// an error if name stored in the object differs from the name in
// the Info struct.
func addSuffixToName(obj *unstructured.Unstructured, suffix string) error {
suffix = strings.TrimSpace(suffix)
if len(suffix) == 0 {
return fmt.Errorf("passed empty suffix")
}
name := obj.GetName()
if name != obj.GetName() {
return fmt.Errorf("inventory object (%s) and resource.Info (%s) have different names", name, obj.GetName())
}
// Error if name already has suffix.
suffix = "-" + suffix
if strings.HasSuffix(name, suffix) {
return fmt.Errorf("name already has suffix: %s", name)
}
name += suffix
obj.SetName(name)
return nil
}
// fixLegacyInventoryName modifies the inventory name if it is
// the legacy default name (i.e. inventory) by adding a random suffix.
// This fixes a problem where inventory object names collide if
// they are created in the same namespace.
func fixLegacyInventoryName(obj *unstructured.Unstructured) error {
if obj.GetName() == legacyInvName {
klog.V(4).Infof("renaming legacy inventory name")
randomSuffix := common.RandomStr()
return addSuffixToName(obj, randomSuffix)
}
return nil
}