Enhance the fake Client
This change accomplishes the following: * Add a constructor for fake.Clients * Add the ResourceAccumulator type and several instances of ResourceAccumulators, each of which is intended to supply a fake.Client with arbitrary kubernetes resources. * Add the client.Factory type, which provides an easier method of providing a fake.Client in place of a real one. Change-Id: I97f5a613df3ca14bc4fdcf726d3e20c5413cbb5b
This commit is contained in:
parent
e9f8ac3ac3
commit
e15f1218f6
@ -60,9 +60,6 @@ func TestDeploy(t *testing.T) {
|
||||
infra.FileSystem = document.NewDocumentFs()
|
||||
|
||||
kctl := kubectl.NewKubectl(tf)
|
||||
tc := fake.Client{
|
||||
MockKubectl: func() kubectl.Interface { return kctl },
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
theInfra *initinfra.Infra
|
||||
@ -71,24 +68,22 @@ func TestDeploy(t *testing.T) {
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
client: fake.Client{
|
||||
MockKubectl: func() kubectl.Interface {
|
||||
return kubectl.NewKubectl(k8sutils.
|
||||
NewMockKubectlFactory().
|
||||
WithDynamicClientByError(nil, DynamicClientError))
|
||||
},
|
||||
},
|
||||
|
||||
client: fake.NewClient(fake.WithKubectl(
|
||||
kubectl.NewKubectl(k8sutils.
|
||||
NewMockKubectlFactory().
|
||||
WithDynamicClientByError(nil, DynamicClientError)))),
|
||||
expectedError: DynamicClientError,
|
||||
},
|
||||
{
|
||||
expectedError: nil,
|
||||
prune: false,
|
||||
client: tc,
|
||||
client: fake.NewClient(fake.WithKubectl(kctl)),
|
||||
},
|
||||
{
|
||||
expectedError: nil,
|
||||
prune: true,
|
||||
client: tc,
|
||||
client: fake.NewClient(fake.WithKubectl(kctl)),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -22,9 +22,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/dynamic"
|
||||
dynamicFake "k8s.io/client-go/dynamic/fake"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/cluster"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
@ -107,7 +104,7 @@ func TestGetStatusForResource(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
selector document.Selector
|
||||
testClient fake.Client
|
||||
testClient *fake.Client
|
||||
expectedStatus cluster.Status
|
||||
err error
|
||||
}{
|
||||
@ -116,7 +113,9 @@ func TestGetStatusForResource(t *testing.T) {
|
||||
selector: document.NewSelector().
|
||||
ByGvk("example.com", "v1", "Resource").
|
||||
ByName("stable-resource"),
|
||||
testClient: makeTestClient(makeResource("Resource", "stable-resource", "stable")),
|
||||
testClient: fake.NewClient(
|
||||
fake.WithDynamicObjects(makeResource("Resource", "stable-resource", "stable")),
|
||||
),
|
||||
expectedStatus: cluster.Status("Stable"),
|
||||
},
|
||||
{
|
||||
@ -124,7 +123,9 @@ func TestGetStatusForResource(t *testing.T) {
|
||||
selector: document.NewSelector().
|
||||
ByGvk("example.com", "v1", "Resource").
|
||||
ByName("pending-resource"),
|
||||
testClient: makeTestClient(makeResource("Resource", "pending-resource", "pending")),
|
||||
testClient: fake.NewClient(
|
||||
fake.WithDynamicObjects(makeResource("Resource", "pending-resource", "pending")),
|
||||
),
|
||||
expectedStatus: cluster.Status("Pending"),
|
||||
},
|
||||
{
|
||||
@ -132,7 +133,9 @@ func TestGetStatusForResource(t *testing.T) {
|
||||
selector: document.NewSelector().
|
||||
ByGvk("example.com", "v1", "Resource").
|
||||
ByName("unknown"),
|
||||
testClient: makeTestClient(makeResource("Resource", "unknown", "unknown")),
|
||||
testClient: fake.NewClient(
|
||||
fake.WithDynamicObjects(makeResource("Resource", "unknown", "unknown")),
|
||||
),
|
||||
expectedStatus: cluster.UnknownStatus,
|
||||
},
|
||||
{
|
||||
@ -140,7 +143,9 @@ func TestGetStatusForResource(t *testing.T) {
|
||||
selector: document.NewSelector().
|
||||
ByGvk("example.com", "v1", "Legacy").
|
||||
ByName("stable-legacy"),
|
||||
testClient: makeTestClient(makeResource("Legacy", "stable-legacy", "stable")),
|
||||
testClient: fake.NewClient(
|
||||
fake.WithDynamicObjects(makeResource("Legacy", "stable-legacy", "stable")),
|
||||
),
|
||||
expectedStatus: cluster.Status("Stable"),
|
||||
},
|
||||
{
|
||||
@ -148,7 +153,7 @@ func TestGetStatusForResource(t *testing.T) {
|
||||
selector: document.NewSelector().
|
||||
ByGvk("example.com", "v1", "Missing").
|
||||
ByName("missing-resource"),
|
||||
testClient: makeTestClient(),
|
||||
testClient: fake.NewClient(),
|
||||
err: cluster.ErrResourceNotFound{Resource: "missing-resource"},
|
||||
},
|
||||
}
|
||||
@ -177,15 +182,6 @@ func TestGetStatusForResource(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestClient(obj ...runtime.Object) fake.Client {
|
||||
testClient := fake.Client{
|
||||
MockDynamicClient: func() dynamic.Interface {
|
||||
return dynamicFake.NewSimpleDynamicClient(runtime.NewScheme(), obj...)
|
||||
},
|
||||
}
|
||||
return testClient
|
||||
}
|
||||
|
||||
func makeResource(kind, name, state string) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
|
@ -28,11 +28,12 @@ import (
|
||||
)
|
||||
|
||||
// Interface provides an abstraction layer to interactions with kubernetes
|
||||
// clusters by providing a ClientSet which includes all kubernetes core objects
|
||||
// with standard operations, a DynamicClient which provides interactions with
|
||||
// loosely typed kubernetes resources, and a Kubectl interface that is built on
|
||||
// top of kubectl libraries and implements such kubectl subcommands as kubectl
|
||||
// apply (more will be added)
|
||||
// clusters by providing the following:
|
||||
// * A ClientSet which includes all kubernetes core objects with standard operations
|
||||
// * A DynamicClient which provides interactions with loosely typed kubernetes resources
|
||||
// * An ApiextensionsClientSet which provides interactions with CustomResourceDefinitions
|
||||
// * A Kubectl interface that is built on top of kubectl libraries and
|
||||
// implements such kubectl subcommands as kubectl apply (more will be added)
|
||||
type Interface interface {
|
||||
ClientSet() kubernetes.Interface
|
||||
DynamicClient() dynamic.Interface
|
||||
@ -53,8 +54,13 @@ type Client struct {
|
||||
// Client implements Interface
|
||||
var _ Interface = &Client{}
|
||||
|
||||
// NewClient returns Cluster interface with Kubectl
|
||||
// and ClientSet interfaces initialized
|
||||
// Factory is a function which creates Interfaces
|
||||
type Factory func(*environment.AirshipCTLSettings) (Interface, error)
|
||||
|
||||
// DefaultClient is a factory which generates a default client
|
||||
var DefaultClient Factory = NewClient
|
||||
|
||||
// NewClient creates a Client initialized from the passed in settings
|
||||
func NewClient(settings *environment.AirshipCTLSettings) (Interface, error) {
|
||||
client := new(Client)
|
||||
var err error
|
||||
@ -88,42 +94,42 @@ func NewClient(settings *environment.AirshipCTLSettings) (Interface, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// ClientSet getter for ClientSet interface
|
||||
// ClientSet returns the ClientSet interface
|
||||
func (c *Client) ClientSet() kubernetes.Interface {
|
||||
return c.clientSet
|
||||
}
|
||||
|
||||
// SetClientSet setter for ClientSet interface
|
||||
// SetClientSet sets the ClientSet interface
|
||||
func (c *Client) SetClientSet(clientSet kubernetes.Interface) {
|
||||
c.clientSet = clientSet
|
||||
}
|
||||
|
||||
// DynamicClient getter for DynamicClient interface
|
||||
// DynamicClient returns the DynamicClient interface
|
||||
func (c *Client) DynamicClient() dynamic.Interface {
|
||||
return c.dynamicClient
|
||||
}
|
||||
|
||||
// SetDynamicClient setter for DynamicClient interface
|
||||
// SetDynamicClient sets the DynamicClient interface
|
||||
func (c *Client) SetDynamicClient(dynamicClient dynamic.Interface) {
|
||||
c.dynamicClient = dynamicClient
|
||||
}
|
||||
|
||||
// ApiextensionsV1 getter for ApiextensionsV1 interface
|
||||
// ApiextensionsClientSet returns the Apiextensions interface
|
||||
func (c *Client) ApiextensionsClientSet() apix.Interface {
|
||||
return c.apixClient
|
||||
}
|
||||
|
||||
// SetApiextensionsV1 setter for ApiextensionsV1 interface
|
||||
// SetApiextensionsClientSet sets the ApiextensionsClientSet interface
|
||||
func (c *Client) SetApiextensionsClientSet(apixClient apix.Interface) {
|
||||
c.apixClient = apixClient
|
||||
}
|
||||
|
||||
// Kubectl getter for Kubectl interface
|
||||
// Kubectl returns the Kubectl interface
|
||||
func (c *Client) Kubectl() kubectl.Interface {
|
||||
return c.kubectl
|
||||
}
|
||||
|
||||
// SetKubectl setter for Kubectl interface
|
||||
// SetKubectl sets the Kubectl interface
|
||||
func (c *Client) SetKubectl(kctl kubectl.Interface) {
|
||||
c.kubectl = kctl
|
||||
}
|
||||
|
@ -51,5 +51,7 @@ func TestNewClient(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.NotNil(t, client.ClientSet())
|
||||
assert.NotNil(t, client.DynamicClient())
|
||||
assert.NotNil(t, client.ApiextensionsClientSet())
|
||||
assert.NotNil(t, client.Kubectl())
|
||||
}
|
||||
|
@ -16,79 +16,131 @@ package fake
|
||||
|
||||
import (
|
||||
apix "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
apixFake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/dynamic"
|
||||
dynamicFake "k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
kubernetesFake "k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/k8s/client"
|
||||
"opendev.org/airship/airshipctl/pkg/k8s/kubectl"
|
||||
"opendev.org/airship/airshipctl/testutil/k8sutils"
|
||||
)
|
||||
|
||||
// Client is an implementation of client.Interface meant for testing purposes.
|
||||
// Its member methods are intended to be implemented on a case-by-case basis
|
||||
// per test. Examples of implementations can be found with each interface
|
||||
// method.
|
||||
type Client struct {
|
||||
MockClientSet func() kubernetes.Interface
|
||||
MockDynamicClient func() dynamic.Interface
|
||||
MockApiextensionsClientSet func() apix.Interface
|
||||
MockKubectl func() kubectl.Interface
|
||||
mockClientSet func() kubernetes.Interface
|
||||
mockDynamicClient func() dynamic.Interface
|
||||
mockApiextensionsClientSet func() apix.Interface
|
||||
mockKubectl func() kubectl.Interface
|
||||
}
|
||||
|
||||
var _ client.Interface = &Client{}
|
||||
|
||||
// ClientSet is used to get a mocked implementation of a kubernetes clientset.
|
||||
// To initialize the mocked clientset to be returned, the MockClientSet method
|
||||
// must be implemented, ideally returning a k8s.io/client-go/kubernetes/fake.Clientset.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// testClient := fake.Client {
|
||||
// MockClientSet: func() kubernetes.Interface {
|
||||
// return kubernetes_fake.NewSimpleClientset()
|
||||
// },
|
||||
// }
|
||||
func (c Client) ClientSet() kubernetes.Interface {
|
||||
return c.MockClientSet()
|
||||
// To initialize the mocked clientset to be returned, use the WithTypedObjects
|
||||
// ResourceAccumulator
|
||||
func (c *Client) ClientSet() kubernetes.Interface {
|
||||
return c.mockClientSet()
|
||||
}
|
||||
|
||||
// DynamicClient is used to get a mocked implementation of a dynamic client.
|
||||
// To initialize the mocked client to be returned, the MockDynamicClient method
|
||||
// must be implemented, ideally returning a k8s.io/client-go/dynamic/fake.FakeDynamicClient.
|
||||
//
|
||||
// Example:
|
||||
// Here, scheme is a k8s.io/apimachinery/pkg/runtime.Scheme, possibly created
|
||||
// via runtime.NewScheme()
|
||||
//
|
||||
// testClient := fake.Client {
|
||||
// MockDynamicClient: func() dynamic.Interface {
|
||||
// return dynamic_fake.NewSimpleDynamicClient(scheme)
|
||||
// },
|
||||
// }
|
||||
func (c Client) DynamicClient() dynamic.Interface {
|
||||
return c.MockDynamicClient()
|
||||
// To initialize the mocked client to be returned, use the WithDynamicObjects
|
||||
// ResourceAccumulator.
|
||||
func (c *Client) DynamicClient() dynamic.Interface {
|
||||
return c.mockDynamicClient()
|
||||
}
|
||||
|
||||
// ApiextensionsClientSet is used to get a mocked implementation of an
|
||||
// Apiextensions clientset. To initialize the mocked client to be returned,
|
||||
// the MockApiextensionsClientSet method must be implemented, ideally returning a
|
||||
// k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake.ClientSet.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// testClient := fake.Client {
|
||||
// MockApiextensionsClientSet: func() apix.Interface {
|
||||
// return apix_fake.NewSimpleClientset()
|
||||
// },
|
||||
// }
|
||||
func (c Client) ApiextensionsClientSet() apix.Interface {
|
||||
return c.MockApiextensionsClientSet()
|
||||
// Apiextensions clientset. To initialize the mocked client to be returned,
|
||||
// use the WithCRDs ResourceAccumulator
|
||||
func (c *Client) ApiextensionsClientSet() apix.Interface {
|
||||
return c.mockApiextensionsClientSet()
|
||||
}
|
||||
|
||||
// Kubectl is used to get a mocked implementation of a Kubectl client.
|
||||
// To initialize the mocked client to be returned, the MockKubectl method
|
||||
// must be implemented.
|
||||
//
|
||||
// Example: TODO(howell)
|
||||
func (c Client) Kubectl() kubectl.Interface {
|
||||
return c.MockKubectl()
|
||||
// To initialize the mocked client to be returned, use the WithKubectl ResourceAccumulator
|
||||
func (c *Client) Kubectl() kubectl.Interface {
|
||||
return c.mockKubectl()
|
||||
}
|
||||
|
||||
// A ResourceAccumulator is an option meant to be passed to NewClient.
|
||||
// ResourceAccumulators can be mixed and matched to create a collection of
|
||||
// mocked clients, each having their own fake objects.
|
||||
type ResourceAccumulator func(*Client)
|
||||
|
||||
// NewClient creates an instance of a Client. If no arguments are passed, the
|
||||
// returned Client will have fresh mocked kubernetes clients which will have no
|
||||
// prior knowledge of any resources.
|
||||
//
|
||||
// If prior knowledge of resources is desirable, NewClient should receive an
|
||||
// appropriate ResourceAccumulator initialized with the desired resources.
|
||||
func NewClient(resourceAccumulators ...ResourceAccumulator) *Client {
|
||||
fakeClient := new(Client)
|
||||
for _, accumulator := range resourceAccumulators {
|
||||
accumulator(fakeClient)
|
||||
}
|
||||
|
||||
if fakeClient.mockClientSet == nil {
|
||||
fakeClient.mockClientSet = func() kubernetes.Interface {
|
||||
return kubernetesFake.NewSimpleClientset()
|
||||
}
|
||||
}
|
||||
if fakeClient.mockDynamicClient == nil {
|
||||
fakeClient.mockDynamicClient = func() dynamic.Interface {
|
||||
return dynamicFake.NewSimpleDynamicClient(runtime.NewScheme())
|
||||
}
|
||||
}
|
||||
if fakeClient.mockApiextensionsClientSet == nil {
|
||||
fakeClient.mockApiextensionsClientSet = func() apix.Interface {
|
||||
return apixFake.NewSimpleClientset()
|
||||
}
|
||||
}
|
||||
if fakeClient.mockKubectl == nil {
|
||||
fakeClient.mockKubectl = func() kubectl.Interface {
|
||||
return kubectl.NewKubectl(k8sutils.NewMockKubectlFactory())
|
||||
}
|
||||
}
|
||||
return fakeClient
|
||||
}
|
||||
|
||||
// WithTypedObjects returns a ResourceAccumulator with resources which would
|
||||
// normally be accessible through a kubernetes ClientSet (e.g. Pods,
|
||||
// Deployments, etc...).
|
||||
func WithTypedObjects(objs ...runtime.Object) ResourceAccumulator {
|
||||
return func(c *Client) {
|
||||
c.mockClientSet = func() kubernetes.Interface {
|
||||
return kubernetesFake.NewSimpleClientset(objs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithCRDs returns a ResourceAccumulator with resources which would
|
||||
// normally be accessible through a kubernetes ApiextensionsClientSet (e.g. CRDs).
|
||||
func WithCRDs(objs ...runtime.Object) ResourceAccumulator {
|
||||
return func(c *Client) {
|
||||
c.mockApiextensionsClientSet = func() apix.Interface {
|
||||
return apixFake.NewSimpleClientset(objs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithDynamicObjects returns a ResourceAccumulator with resources which would
|
||||
// normally be accessible through a kubernetes DynamicClient (e.g. unstructured.Unstructured).
|
||||
func WithDynamicObjects(objs ...runtime.Object) ResourceAccumulator {
|
||||
return func(c *Client) {
|
||||
c.mockDynamicClient = func() dynamic.Interface {
|
||||
return dynamicFake.NewSimpleDynamicClient(runtime.NewScheme(), objs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithKubectl returns a ResourceAccumulator with an instance of a kubectl.Interface.
|
||||
func WithKubectl(kubectlInstance *kubectl.Kubectl) ResourceAccumulator {
|
||||
return func(c *Client) {
|
||||
c.mockKubectl = func() kubectl.Interface {
|
||||
return kubectlInstance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user