-
Notifications
You must be signed in to change notification settings - Fork 226
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Dual-delegating provider allows ConfigMap and ResourceGroup inventory
- Loading branch information
Showing
5 changed files
with
349 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// Copyright 2020 Google LLC. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package live | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
|
||
"k8s.io/apimachinery/pkg/api/meta" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"k8s.io/klog" | ||
"k8s.io/kubectl/pkg/cmd/util" | ||
"sigs.k8s.io/cli-utils/pkg/inventory" | ||
"sigs.k8s.io/cli-utils/pkg/manifestreader" | ||
"sigs.k8s.io/cli-utils/pkg/provider" | ||
) | ||
|
||
// DualDelegatingProvider encapsulates another Provider to which it | ||
// delegates most functions, enabling a Provider which will return | ||
// values based on the inventory object found from ManifestReader() | ||
// call. | ||
type DualDelegatingProvider struct { | ||
// ResourceGroupProvider is the delegate. | ||
rgProvider provider.Provider | ||
// Default inventory function is for ConfigMap. | ||
wrapInv inventory.InventoryFactoryFunc | ||
// Boolean on whether we've calculated the inventory object type. | ||
calcInventory bool | ||
} | ||
|
||
// NewDualDelagatingProvider returns a pointer to the DualDelegatingProvider, | ||
// setting default values. | ||
func NewDualDelegatingProvider(f util.Factory) *DualDelegatingProvider { | ||
return &DualDelegatingProvider{ | ||
rgProvider: NewResourceGroupProvider(f), | ||
wrapInv: inventory.WrapInventoryObj, | ||
calcInventory: false, | ||
} | ||
} | ||
|
||
// Factory returns the delegate factory. | ||
func (cp *DualDelegatingProvider) Factory() util.Factory { | ||
return cp.rgProvider.Factory() | ||
} | ||
|
||
// InventoryClient returns an InventoryClient that is created from the | ||
// stored/calculated InventoryFactoryFunction. This must be called | ||
// after ManifestReader(). | ||
func (cp *DualDelegatingProvider) InventoryClient() (inventory.InventoryClient, error) { | ||
if !cp.calcInventory { | ||
return nil, fmt.Errorf("must be called after ManifestReader()") | ||
} | ||
return inventory.NewInventoryClient(cp.Factory(), cp.wrapInv) | ||
} | ||
|
||
// ToRESTMapper returns the value from the delegate provider; or an error. | ||
func (cp *DualDelegatingProvider) ToRESTMapper() (meta.RESTMapper, error) { | ||
return cp.Factory().ToRESTMapper() | ||
} | ||
|
||
// ManifestReader retrieves the ManifestReader from the delegate ResourceGroup | ||
// Provider, then calls Read() for this ManifestReader to retrieve the objects | ||
// and to calculate the type of Inventory object is present. Returns a | ||
// CachedManifestReader with the read objects, or an error. Can return a | ||
// NoInventoryError or MultipleInventoryError. | ||
func (cp *DualDelegatingProvider) ManifestReader(reader io.Reader, args []string) (manifestreader.ManifestReader, error) { | ||
r, err := cp.rgProvider.ManifestReader(reader, args) | ||
if err != nil { | ||
return nil, err | ||
} | ||
objs, err := r.Read() | ||
if err != nil { | ||
return nil, err | ||
} | ||
klog.V(4).Infof("ManifestReader read %d objects", len(objs)) | ||
rgInv := findResourceGroupInv(objs) | ||
// A ResourceGroup inventory object means we need an InventoryFactoryFunc | ||
// which works for ResourceGroup (instead of ConfigMap, which is default). | ||
if rgInv != nil { | ||
cp.wrapInv = WrapInventoryObj | ||
} | ||
cmInv := findConfigMapInv(objs) | ||
if rgInv == nil && cmInv == nil { | ||
return nil, inventory.NoInventoryObjError{} | ||
} | ||
if rgInv != nil && cmInv != nil { | ||
return nil, inventory.MultipleInventoryObjError{ | ||
InventoryObjectTemplates: []*unstructured.Unstructured{rgInv, cmInv}, | ||
} | ||
} | ||
cp.calcInventory = true | ||
return &CachedManifestReader{objs: objs}, nil | ||
} | ||
|
||
// CachedManifestReader implements ManifestReader, storing objects to return. | ||
type CachedManifestReader struct { | ||
objs []*unstructured.Unstructured | ||
} | ||
|
||
// Read simply returns the stored objects. | ||
func (r *CachedManifestReader) Read() ([]*unstructured.Unstructured, error) { | ||
return r.objs, nil | ||
} | ||
|
||
// findResourceGroupInv returns the pointer to the ResourceGroup inventory object, | ||
// or nil if it does not exist. | ||
func findResourceGroupInv(objs []*unstructured.Unstructured) *unstructured.Unstructured { | ||
for _, obj := range objs { | ||
if inventory.IsInventoryObject(obj) { | ||
if obj.GetKind() == "ResourceGroup" { | ||
return obj | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// findConfigMapInv returns the pointer to the ConfigMap inventory object, | ||
// or nil if it does not exist. | ||
func findConfigMapInv(objs []*unstructured.Unstructured) *unstructured.Unstructured { | ||
for _, obj := range objs { | ||
if inventory.IsInventoryObject(obj) { | ||
if obj.GetKind() == "ConfigMap" { | ||
return obj | ||
} | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// Copyright 2020 Google LLC. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package live | ||
|
||
import ( | ||
"io/ioutil" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing" | ||
"sigs.k8s.io/cli-utils/pkg/inventory" | ||
) | ||
|
||
var configMapInv = ` | ||
apiVersion: v1 | ||
kind: ConfigMap | ||
metadata: | ||
namespace: test-ns | ||
name: inventory-111111 | ||
labels: | ||
cli-utils.sigs.k8s.io/inventory-id: XXXX-YYYY-ZZZZ | ||
` | ||
|
||
func TestDualDelegatingProvider_Read(t *testing.T) { | ||
testCases := map[string]struct { | ||
manifests map[string]string | ||
numObjs int | ||
invKind string | ||
isError bool | ||
}{ | ||
"Basic ResourceGroup inventory object created": { | ||
manifests: map[string]string{ | ||
"Kptfile": kptFile, | ||
"pod-a.yaml": podA, | ||
}, | ||
numObjs: 2, | ||
invKind: "ResourceGroup", | ||
isError: false, | ||
}, | ||
"Only ResourceGroup inventory object created": { | ||
manifests: map[string]string{ | ||
"Kptfile": kptFile, | ||
}, | ||
numObjs: 1, | ||
invKind: "ResourceGroup", | ||
isError: false, | ||
}, | ||
"ResourceGroup inventory object with multiple objects": { | ||
manifests: map[string]string{ | ||
"pod-a.yaml": podA, | ||
"Kptfile": kptFile, | ||
"deployment-a.yaml": deploymentA, | ||
}, | ||
numObjs: 3, | ||
invKind: "ResourceGroup", | ||
isError: false, | ||
}, | ||
"Basic ConfigMap inventory object created": { | ||
manifests: map[string]string{ | ||
"inventory-template.yaml": configMapInv, | ||
"deployment-a.yaml": deploymentA, | ||
}, | ||
numObjs: 2, | ||
invKind: "ConfigMap", | ||
isError: false, | ||
}, | ||
"Only ConfigMap inventory object created": { | ||
manifests: map[string]string{ | ||
"inventory-template.yaml": configMapInv, | ||
}, | ||
numObjs: 1, | ||
invKind: "ConfigMap", | ||
isError: false, | ||
}, | ||
"ConfigMap inventory object with multiple objects": { | ||
manifests: map[string]string{ | ||
"deployment-a.yaml": deploymentA, | ||
"inventory-template.yaml": configMapInv, | ||
"pod-a.yaml": podA, | ||
}, | ||
numObjs: 3, | ||
invKind: "ConfigMap", | ||
isError: false, | ||
}, | ||
"No inventory manifests is an error": { | ||
manifests: map[string]string{ | ||
"pod-a.yaml": podA, | ||
"deployment-a.yaml": deploymentA, | ||
}, | ||
numObjs: 2, | ||
isError: true, | ||
}, | ||
"Multiple manifests is an error": { | ||
manifests: map[string]string{ | ||
"inventory-template.yaml": configMapInv, | ||
"Kptfile": kptFile, | ||
"pod-a.yaml": podA, | ||
}, | ||
numObjs: 3, | ||
isError: true, | ||
}, | ||
} | ||
|
||
for tn, tc := range testCases { | ||
t.Run(tn, func(t *testing.T) { | ||
// Create the fake factory | ||
tf := cmdtesting.NewTestFactory().WithNamespace("test-ns") | ||
defer tf.Cleanup() | ||
// Set up the yaml manifests (including Kptfile) in temp dir. | ||
dir, err := ioutil.TempDir("", "provider-test") | ||
assert.NoError(t, err) | ||
for filename, content := range tc.manifests { | ||
p := filepath.Join(dir, filename) | ||
err := ioutil.WriteFile(p, []byte(content), 0600) | ||
assert.NoError(t, err) | ||
} | ||
// Create the DualDelegatingProvider | ||
provider := NewDualDelegatingProvider(tf) | ||
assert.Equal(t, tf, provider.Factory()) | ||
// Calling InventoryClient before ManifestReader is always an error. | ||
_, err = provider.InventoryClient() | ||
if err == nil { | ||
t.Errorf("expecting error on InventoryClient, but received none.") | ||
} | ||
// Read objects using provider ManifestReader. | ||
mr, err := provider.ManifestReader(nil, []string{dir}) | ||
if tc.isError { | ||
if err == nil { | ||
t.Errorf("expected error on ManifestReader, but received none.") | ||
} | ||
return | ||
} | ||
objs, err := mr.Read() | ||
assert.NoError(t, err) | ||
if tc.numObjs != len(objs) { | ||
t.Errorf("expected to read (%d) objs, got (%d)", tc.numObjs, len(objs)) | ||
} | ||
// Retrieve single inventory object and validate the kind. | ||
inv := inventory.FindInventoryObj(objs) | ||
if inv == nil { | ||
t.Errorf("inventory object not found") | ||
} | ||
if tc.invKind != inv.GetKind() { | ||
t.Errorf("expected inventory kind (%s), got (%s)", tc.invKind, inv.GetKind()) | ||
} | ||
// Calling InventoryClient after ManifestReader is valid. | ||
_, err = provider.InventoryClient() | ||
if err != nil { | ||
t.Errorf("unexpected error calling InventoryClient: %s", err) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.