mirror of https://github.com/fluxcd/cli-utils.git
				
				
				
			feat: Add inventory.StatusPolicy
- StatusPolicyNone disables inventory status updates. - StatusPolicyAll fully enables inventory status updates. - This allows an opt-out feature for working around the problem that adding status can make the inventory larger than the max etcd object size, causing the applier to exit without applying or pruning anything. With StatusPolicyNone, the user can still safely prune objects to make their inventory smaller, and then re-enable the status with StatusPolicyAll. - Note: the default ConfigMap does not currently support status, so this only affects custom inventory impls.
This commit is contained in:
		
							parent
							
								
									16847b444b
								
							
						
					
					
						commit
						2bdc05945d
					
				|  | @ -16,8 +16,9 @@ type ClientFactory interface { | ||||||
| 
 | 
 | ||||||
| // ClusterClientFactory is a factory that creates instances of ClusterClient inventory client.
 | // ClusterClientFactory is a factory that creates instances of ClusterClient inventory client.
 | ||||||
| type ClusterClientFactory struct { | type ClusterClientFactory struct { | ||||||
|  | 	StatusPolicy StatusPolicy | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ClusterClientFactory) NewClient(factory cmdutil.Factory) (Client, error) { | func (ccf ClusterClientFactory) NewClient(factory cmdutil.Factory) (Client, error) { | ||||||
| 	return NewClient(factory, WrapInventoryObj, InvInfoToConfigMap) | 	return NewClient(factory, WrapInventoryObj, InvInfoToConfigMap, ccf.StatusPolicy) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -54,6 +54,7 @@ type ClusterClient struct { | ||||||
| 	mapper                meta.RESTMapper | 	mapper                meta.RESTMapper | ||||||
| 	InventoryFactoryFunc  StorageFactoryFunc | 	InventoryFactoryFunc  StorageFactoryFunc | ||||||
| 	invToUnstructuredFunc ToUnstructuredFunc | 	invToUnstructuredFunc ToUnstructuredFunc | ||||||
|  | 	statusPolicy          StatusPolicy | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var _ Client = &ClusterClient{} | var _ Client = &ClusterClient{} | ||||||
|  | @ -62,7 +63,9 @@ var _ Client = &ClusterClient{} | ||||||
| // Client interface or an error.
 | // Client interface or an error.
 | ||||||
| func NewClient(factory cmdutil.Factory, | func NewClient(factory cmdutil.Factory, | ||||||
| 	invFunc StorageFactoryFunc, | 	invFunc StorageFactoryFunc, | ||||||
| 	invToUnstructuredFunc ToUnstructuredFunc) (*ClusterClient, error) { | 	invToUnstructuredFunc ToUnstructuredFunc, | ||||||
|  | 	statusPolicy StatusPolicy, | ||||||
|  | ) (*ClusterClient, error) { | ||||||
| 	dc, err := factory.DynamicClient() | 	dc, err := factory.DynamicClient() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  | @ -81,6 +84,7 @@ func NewClient(factory cmdutil.Factory, | ||||||
| 		mapper:                mapper, | 		mapper:                mapper, | ||||||
| 		InventoryFactoryFunc:  invFunc, | 		InventoryFactoryFunc:  invFunc, | ||||||
| 		invToUnstructuredFunc: invToUnstructuredFunc, | 		invToUnstructuredFunc: invToUnstructuredFunc, | ||||||
|  | 		statusPolicy:          statusPolicy, | ||||||
| 	} | 	} | ||||||
| 	return &clusterClient, nil | 	return &clusterClient, nil | ||||||
| } | } | ||||||
|  | @ -451,6 +455,10 @@ func (cic *ClusterClient) getMapping(obj *unstructured.Unstructured) (*meta.REST | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (cic *ClusterClient) updateStatus(obj *unstructured.Unstructured, dryRun common.DryRunStrategy) error { | func (cic *ClusterClient) updateStatus(obj *unstructured.Unstructured, dryRun common.DryRunStrategy) error { | ||||||
|  | 	if cic.statusPolicy != StatusPolicyAll { | ||||||
|  | 		klog.V(4).Infof("inventory status update skipped (StatusPolicy: %s)", cic.statusPolicy) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
| 	if dryRun.ClientOrServerDryRun() { | 	if dryRun.ClientOrServerDryRun() { | ||||||
| 		klog.V(4).Infof("dry-run update inventory status: not updated") | 		klog.V(4).Infof("dry-run update inventory status: not updated") | ||||||
| 		return nil | 		return nil | ||||||
|  |  | ||||||
|  | @ -39,10 +39,11 @@ func podData(name string) map[string]string { | ||||||
| 
 | 
 | ||||||
| func TestGetClusterInventoryInfo(t *testing.T) { | func TestGetClusterInventoryInfo(t *testing.T) { | ||||||
| 	tests := map[string]struct { | 	tests := map[string]struct { | ||||||
| 		inv       Info | 		statusPolicy StatusPolicy | ||||||
| 		localObjs object.ObjMetadataSet | 		inv          Info | ||||||
| 		objStatus []actuation.ObjectStatus | 		localObjs    object.ObjMetadataSet | ||||||
| 		isError   bool | 		objStatus    []actuation.ObjectStatus | ||||||
|  | 		isError      bool | ||||||
| 	}{ | 	}{ | ||||||
| 		"Nil local inventory object is an error": { | 		"Nil local inventory object is an error": { | ||||||
| 			inv:       nil, | 			inv:       nil, | ||||||
|  | @ -83,7 +84,7 @@ func TestGetClusterInventoryInfo(t *testing.T) { | ||||||
| 	for name, tc := range tests { | 	for name, tc := range tests { | ||||||
| 		t.Run(name, func(t *testing.T) { | 		t.Run(name, func(t *testing.T) { | ||||||
| 			invClient, err := NewClient(tf, | 			invClient, err := NewClient(tf, | ||||||
| 				WrapInventoryObj, InvInfoToConfigMap) | 				WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy) | ||||||
| 			require.NoError(t, err) | 			require.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 			var inv *unstructured.Unstructured | 			var inv *unstructured.Unstructured | ||||||
|  | @ -116,11 +117,12 @@ func TestGetClusterInventoryInfo(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestMerge(t *testing.T) { | func TestMerge(t *testing.T) { | ||||||
| 	tests := map[string]struct { | 	tests := map[string]struct { | ||||||
| 		localInv    Info | 		statusPolicy StatusPolicy | ||||||
| 		localObjs   object.ObjMetadataSet | 		localInv     Info | ||||||
| 		clusterObjs object.ObjMetadataSet | 		localObjs    object.ObjMetadataSet | ||||||
| 		pruneObjs   object.ObjMetadataSet | 		clusterObjs  object.ObjMetadataSet | ||||||
| 		isError     bool | 		pruneObjs    object.ObjMetadataSet | ||||||
|  | 		isError      bool | ||||||
| 	}{ | 	}{ | ||||||
| 		"Nil local inventory object is error": { | 		"Nil local inventory object is error": { | ||||||
| 			localInv:    nil, | 			localInv:    nil, | ||||||
|  | @ -188,7 +190,7 @@ func TestMerge(t *testing.T) { | ||||||
| 				tf.FakeDynamicClient.PrependReactor("list", "configmaps", toReactionFunc(tc.clusterObjs)) | 				tf.FakeDynamicClient.PrependReactor("list", "configmaps", toReactionFunc(tc.clusterObjs)) | ||||||
| 				// Create the local inventory object storing "tc.localObjs"
 | 				// Create the local inventory object storing "tc.localObjs"
 | ||||||
| 				invClient, err := NewClient(tf, | 				invClient, err := NewClient(tf, | ||||||
| 					WrapInventoryObj, InvInfoToConfigMap) | 					WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy) | ||||||
| 				require.NoError(t, err) | 				require.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 				// Call "Merge" to create the union of clusterObjs and localObjs.
 | 				// Call "Merge" to create the union of clusterObjs and localObjs.
 | ||||||
|  | @ -212,10 +214,11 @@ func TestMerge(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestCreateInventory(t *testing.T) { | func TestCreateInventory(t *testing.T) { | ||||||
| 	tests := map[string]struct { | 	tests := map[string]struct { | ||||||
| 		inv       Info | 		statusPolicy StatusPolicy | ||||||
| 		localObjs object.ObjMetadataSet | 		inv          Info | ||||||
| 		error     string | 		localObjs    object.ObjMetadataSet | ||||||
| 		objStatus []actuation.ObjectStatus | 		error        string | ||||||
|  | 		objStatus    []actuation.ObjectStatus | ||||||
| 	}{ | 	}{ | ||||||
| 		"Nil local inventory object is an error": { | 		"Nil local inventory object is an error": { | ||||||
| 			inv:       nil, | 			inv:       nil, | ||||||
|  | @ -260,7 +263,7 @@ func TestCreateInventory(t *testing.T) { | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
| 			invClient, err := NewClient(tf, | 			invClient, err := NewClient(tf, | ||||||
| 				WrapInventoryObj, InvInfoToConfigMap) | 				WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy) | ||||||
| 			require.NoError(t, err) | 			require.NoError(t, err) | ||||||
| 			inv := invClient.invToUnstructuredFunc(tc.inv) | 			inv := invClient.invToUnstructuredFunc(tc.inv) | ||||||
| 			if inv != nil { | 			if inv != nil { | ||||||
|  | @ -288,10 +291,11 @@ func TestCreateInventory(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestReplace(t *testing.T) { | func TestReplace(t *testing.T) { | ||||||
| 	tests := map[string]struct { | 	tests := map[string]struct { | ||||||
| 		localObjs   object.ObjMetadataSet | 		statusPolicy StatusPolicy | ||||||
| 		clusterObjs object.ObjMetadataSet | 		localObjs    object.ObjMetadataSet | ||||||
| 		objStatus   []actuation.ObjectStatus | 		clusterObjs  object.ObjMetadataSet | ||||||
| 		data        map[string]string | 		objStatus    []actuation.ObjectStatus | ||||||
|  | 		data         map[string]string | ||||||
| 	}{ | 	}{ | ||||||
| 		"Cluster and local inventories empty": { | 		"Cluster and local inventories empty": { | ||||||
| 			localObjs:   object.ObjMetadataSet{}, | 			localObjs:   object.ObjMetadataSet{}, | ||||||
|  | @ -336,7 +340,8 @@ func TestReplace(t *testing.T) { | ||||||
| 	defer tf.Cleanup() | 	defer tf.Cleanup() | ||||||
| 
 | 
 | ||||||
| 	// Client and server dry-run do not throw errors.
 | 	// Client and server dry-run do not throw errors.
 | ||||||
| 	invClient, err := NewClient(tf, WrapInventoryObj, InvInfoToConfigMap) | 	invClient, err := NewClient(tf, | ||||||
|  | 		WrapInventoryObj, InvInfoToConfigMap, StatusPolicyAll) | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| 	err = invClient.Replace(copyInventory(), object.ObjMetadataSet{}, nil, common.DryRunClient) | 	err = invClient.Replace(copyInventory(), object.ObjMetadataSet{}, nil, common.DryRunClient) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -351,7 +356,7 @@ func TestReplace(t *testing.T) { | ||||||
| 		t.Run(name, func(t *testing.T) { | 		t.Run(name, func(t *testing.T) { | ||||||
| 			// Create inventory client, and store the cluster objs in the inventory object.
 | 			// Create inventory client, and store the cluster objs in the inventory object.
 | ||||||
| 			invClient, err := NewClient(tf, | 			invClient, err := NewClient(tf, | ||||||
| 				WrapInventoryObj, InvInfoToConfigMap) | 				WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy) | ||||||
| 			require.NoError(t, err) | 			require.NoError(t, err) | ||||||
| 			wrappedInv := invClient.InventoryFactoryFunc(inventoryObj) | 			wrappedInv := invClient.InventoryFactoryFunc(inventoryObj) | ||||||
| 			if err := wrappedInv.Store(tc.clusterObjs, tc.objStatus); err != nil { | 			if err := wrappedInv.Store(tc.clusterObjs, tc.objStatus); err != nil { | ||||||
|  | @ -388,9 +393,10 @@ func TestReplace(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestGetClusterObjs(t *testing.T) { | func TestGetClusterObjs(t *testing.T) { | ||||||
| 	tests := map[string]struct { | 	tests := map[string]struct { | ||||||
| 		localInv    Info | 		statusPolicy StatusPolicy | ||||||
| 		clusterObjs object.ObjMetadataSet | 		localInv     Info | ||||||
| 		isError     bool | 		clusterObjs  object.ObjMetadataSet | ||||||
|  | 		isError      bool | ||||||
| 	}{ | 	}{ | ||||||
| 		"Nil cluster inventory is error": { | 		"Nil cluster inventory is error": { | ||||||
| 			localInv:    nil, | 			localInv:    nil, | ||||||
|  | @ -421,7 +427,7 @@ func TestGetClusterObjs(t *testing.T) { | ||||||
| 			tf.FakeDynamicClient.PrependReactor("list", "configmaps", toReactionFunc(tc.clusterObjs)) | 			tf.FakeDynamicClient.PrependReactor("list", "configmaps", toReactionFunc(tc.clusterObjs)) | ||||||
| 
 | 
 | ||||||
| 			invClient, err := NewClient(tf, | 			invClient, err := NewClient(tf, | ||||||
| 				WrapInventoryObj, InvInfoToConfigMap) | 				WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy) | ||||||
| 			require.NoError(t, err) | 			require.NoError(t, err) | ||||||
| 			clusterObjs, err := invClient.GetClusterObjs(tc.localInv) | 			clusterObjs, err := invClient.GetClusterObjs(tc.localInv) | ||||||
| 			if tc.isError { | 			if tc.isError { | ||||||
|  | @ -442,9 +448,10 @@ func TestGetClusterObjs(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestDeleteInventoryObj(t *testing.T) { | func TestDeleteInventoryObj(t *testing.T) { | ||||||
| 	tests := map[string]struct { | 	tests := map[string]struct { | ||||||
| 		inv       Info | 		statusPolicy StatusPolicy | ||||||
| 		localObjs object.ObjMetadataSet | 		inv          Info | ||||||
| 		objStatus []actuation.ObjectStatus | 		localObjs    object.ObjMetadataSet | ||||||
|  | 		objStatus    []actuation.ObjectStatus | ||||||
| 	}{ | 	}{ | ||||||
| 		"Nil local inventory object is an error": { | 		"Nil local inventory object is an error": { | ||||||
| 			inv:       nil, | 			inv:       nil, | ||||||
|  | @ -483,7 +490,7 @@ func TestDeleteInventoryObj(t *testing.T) { | ||||||
| 				defer tf.Cleanup() | 				defer tf.Cleanup() | ||||||
| 
 | 
 | ||||||
| 				invClient, err := NewClient(tf, | 				invClient, err := NewClient(tf, | ||||||
| 					WrapInventoryObj, InvInfoToConfigMap) | 					WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy) | ||||||
| 				require.NoError(t, err) | 				require.NoError(t, err) | ||||||
| 				inv := invClient.invToUnstructuredFunc(tc.inv) | 				inv := invClient.invToUnstructuredFunc(tc.inv) | ||||||
| 				if inv != nil { | 				if inv != nil { | ||||||
|  | @ -500,6 +507,7 @@ func TestDeleteInventoryObj(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestApplyInventoryNamespace(t *testing.T) { | func TestApplyInventoryNamespace(t *testing.T) { | ||||||
| 	testCases := map[string]struct { | 	testCases := map[string]struct { | ||||||
|  | 		statusPolicy   StatusPolicy | ||||||
| 		namespace      *unstructured.Unstructured | 		namespace      *unstructured.Unstructured | ||||||
| 		dryRunStrategy common.DryRunStrategy | 		dryRunStrategy common.DryRunStrategy | ||||||
| 		reactorError   error | 		reactorError   error | ||||||
|  | @ -529,7 +537,7 @@ func TestApplyInventoryNamespace(t *testing.T) { | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
| 			invClient, err := NewClient(tf, | 			invClient, err := NewClient(tf, | ||||||
| 				WrapInventoryObj, InvInfoToConfigMap) | 				WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy) | ||||||
| 			require.NoError(t, err) | 			require.NoError(t, err) | ||||||
| 			err = invClient.ApplyInventoryNamespace(tc.namespace, tc.dryRunStrategy) | 			err = invClient.ApplyInventoryNamespace(tc.namespace, tc.dryRunStrategy) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | // Copyright 2022 The Kubernetes Authors.
 | ||||||
|  | // SPDX-License-Identifier: Apache-2.0
 | ||||||
|  | 
 | ||||||
|  | package inventory | ||||||
|  | 
 | ||||||
|  | // StatusPolicy specifies whether the inventory client should apply status to
 | ||||||
|  | // the inventory object. The status contains the actuation and reconcile stauts
 | ||||||
|  | // of each object in the inventory.
 | ||||||
|  | //go:generate stringer -type=StatusPolicy -linecomment
 | ||||||
|  | type StatusPolicy int | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// StatusPolicyNone disables inventory status updates.
 | ||||||
|  | 	StatusPolicyNone StatusPolicy = iota // None
 | ||||||
|  | 
 | ||||||
|  | 	// StatusPolicyAll fully enables inventory status updates.
 | ||||||
|  | 	StatusPolicyAll // All
 | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | // Code generated by "stringer -type=StatusPolicy -linecomment"; DO NOT EDIT.
 | ||||||
|  | 
 | ||||||
|  | package inventory | ||||||
|  | 
 | ||||||
|  | import "strconv" | ||||||
|  | 
 | ||||||
|  | func _() { | ||||||
|  | 	// An "invalid array index" compiler error signifies that the constant values have changed.
 | ||||||
|  | 	// Re-run the stringer command to generate them again.
 | ||||||
|  | 	var x [1]struct{} | ||||||
|  | 	_ = x[StatusPolicyNone-0] | ||||||
|  | 	_ = x[StatusPolicyAll-1] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const _StatusPolicy_name = "NoneAll" | ||||||
|  | 
 | ||||||
|  | var _StatusPolicy_index = [...]uint8{0, 4, 7} | ||||||
|  | 
 | ||||||
|  | func (i StatusPolicy) String() string { | ||||||
|  | 	if i < 0 || i >= StatusPolicy(len(_StatusPolicy_index)-1) { | ||||||
|  | 		return "StatusPolicy(" + strconv.FormatInt(int64(i), 10) + ")" | ||||||
|  | 	} | ||||||
|  | 	return _StatusPolicy_name[_StatusPolicy_index[i]:_StatusPolicy_index[i+1]] | ||||||
|  | } | ||||||
|  | @ -81,7 +81,9 @@ type CustomClientFactory struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (CustomClientFactory) NewClient(factory util.Factory) (inventory.Client, error) { | func (CustomClientFactory) NewClient(factory util.Factory) (inventory.Client, error) { | ||||||
| 	return inventory.NewClient(factory, WrapInventoryObj, invToUnstructuredFunc) | 	// TODO: add status to custom inventory crd and enable StatusPolicyAll
 | ||||||
|  | 	return inventory.NewClient(factory, | ||||||
|  | 		WrapInventoryObj, invToUnstructuredFunc, inventory.StatusPolicyNone) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func invToUnstructuredFunc(inv inventory.Info) *unstructured.Unstructured { | func invToUnstructuredFunc(inv inventory.Info) *unstructured.Unstructured { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue