Add the dynamic kubernetes client

The dynamic client will be needed to interact with any custom resource
that airshipctl doesn't know about. It will be required for checking the
health of a cluster, as well as any other operations that may need to be
performed on generic objects.

This also adds the pkg/k8s/client/fake package, which can be used to
create a mock instance of a client for use in unit tests.

Change-Id: Ia331ff4875a067045f6f9245daee109126fb1d33
Relates-To: #73
Relates-To: #20
This commit is contained in:
Ian Howell 2020-03-13 09:37:57 -05:00
parent 906c2b2ec2
commit ea9fba7278
3 changed files with 123 additions and 43 deletions

View File

@ -8,24 +8,15 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"k8s.io/client-go/kubernetes"
"opendev.org/airship/airshipctl/pkg/cluster/initinfra" "opendev.org/airship/airshipctl/pkg/cluster/initinfra"
"opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/document"
"opendev.org/airship/airshipctl/pkg/environment" "opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/pkg/k8s/client" "opendev.org/airship/airshipctl/pkg/k8s/client"
"opendev.org/airship/airshipctl/pkg/k8s/client/fake"
"opendev.org/airship/airshipctl/pkg/k8s/kubectl" "opendev.org/airship/airshipctl/pkg/k8s/kubectl"
"opendev.org/airship/airshipctl/testutil/k8sutils" "opendev.org/airship/airshipctl/testutil/k8sutils"
) )
type TestClient struct {
MockKubectl func() kubectl.Interface
MockClientset func() kubernetes.Interface
}
func (tc TestClient) ClientSet() kubernetes.Interface { return tc.MockClientset() }
func (tc TestClient) Kubectl() kubectl.Interface { return tc.MockKubectl() }
const ( const (
kubeconfigPath = "testdata/kubeconfig.yaml" kubeconfigPath = "testdata/kubeconfig.yaml"
filenameRC = "testdata/primary/site/test-site/ephemeral/initinfra/replicationcontroller.yaml" filenameRC = "testdata/primary/site/test-site/ephemeral/initinfra/replicationcontroller.yaml"
@ -55,7 +46,7 @@ func TestDeploy(t *testing.T) {
infra.FileSystem = document.NewDocumentFs() infra.FileSystem = document.NewDocumentFs()
kctl := kubectl.NewKubectl(tf) kctl := kubectl.NewKubectl(tf)
tc := TestClient{ tc := fake.Client{
MockKubectl: func() kubectl.Interface { return kctl }, MockKubectl: func() kubectl.Interface { return kctl },
} }
@ -66,7 +57,7 @@ func TestDeploy(t *testing.T) {
expectedError error expectedError error
}{ }{
{ {
client: TestClient{ client: fake.Client{
MockKubectl: func() kubectl.Interface { MockKubectl: func() kubectl.Interface {
return kubectl.NewKubectl(k8sutils. return kubectl.NewKubectl(k8sutils.
NewMockKubectlFactory(). NewMockKubectlFactory().

View File

@ -3,6 +3,7 @@ package client
import ( import (
"path/filepath" "path/filepath"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"opendev.org/airship/airshipctl/pkg/environment" "opendev.org/airship/airshipctl/pkg/environment"
@ -15,25 +16,72 @@ const (
buffDir = ".airship" buffDir = ".airship"
) )
// Interface provides an abstraction layer to interactions // Interface provides an abstraction layer to interactions with kubernetes
// with kubernetes clusters by getting Clientset which includes // clusters by providing a ClientSet which includes all kubernetes core objects
// all kubernetes core objects with standard operations and kubectl // with standard operations, a DynamicClient which provides interactions with
// interface that is built on top of kubectl libraries and implements // loosely typed kubernetes resources, and a Kubectl interface that is built on
// such kubectl subcommands as kubectl apply (more will be added) // top of kubectl libraries and implements such kubectl subcommands as kubectl
// apply (more will be added)
type Interface interface { type Interface interface {
ClientSet() kubernetes.Interface ClientSet() kubernetes.Interface
DynamicClient() dynamic.Interface
Kubectl() kubectl.Interface Kubectl() kubectl.Interface
} }
// Client is implementation of Cluster interface // Client is an implementation of Interface
type Client struct { type Client struct {
clientSet kubernetes.Interface
dynamicClient dynamic.Interface
kubectl kubectl.Interface kubectl kubectl.Interface
clientset kubernetes.Interface
} }
// ClientSet getter for Clientset interface // Client implements Interface
var _ Interface = &Client{}
// NewClient returns Cluster interface with Kubectl
// and ClientSet interfaces initialized
func NewClient(settings *environment.AirshipCTLSettings) (Interface, error) {
client := new(Client)
var err error
f := k8sutils.FactoryFromKubeconfigPath(settings.KubeConfigPath())
pathToBufferDir := filepath.Join(filepath.Dir(settings.AirshipConfigPath()), buffDir)
client.kubectl = kubectl.NewKubectl(f).WithBufferDir(pathToBufferDir)
client.clientSet, err = f.KubernetesClientSet()
if err != nil {
return nil, err
}
client.dynamicClient, err = f.DynamicClient()
if err != nil {
return nil, err
}
return client, nil
}
// ClientSet getter for ClientSet interface
func (c *Client) ClientSet() kubernetes.Interface { func (c *Client) ClientSet() kubernetes.Interface {
return c.clientset return c.clientSet
}
// SetClientSet setter for ClientSet interface
func (c *Client) SetClientSet(clientSet kubernetes.Interface) {
c.clientSet = clientSet
}
// DynamicClient getter for DynamicClient interface
func (c *Client) DynamicClient() dynamic.Interface {
return c.dynamicClient
}
// SetDynamicClient setter for DynamicClient interface
func (c *Client) SetDynamicClient(dynamicClient dynamic.Interface) {
c.dynamicClient = dynamicClient
} }
// Kubectl getter for Kubectl interface // Kubectl getter for Kubectl interface
@ -41,27 +89,6 @@ func (c *Client) Kubectl() kubectl.Interface {
return c.kubectl return c.kubectl
} }
// NewClient returns Cluster interface with Kubectl
// and Clientset interfaces initialized
func NewClient(as *environment.AirshipCTLSettings) (Interface, error) {
f := k8sutils.FactoryFromKubeconfigPath(as.KubeConfigPath())
kctl := kubectl.NewKubectl(f).
WithBufferDir(filepath.Dir(as.AirshipConfigPath()) + buffDir)
clientSet, err := f.KubernetesClientSet()
if err != nil {
return nil, err
}
client := &Client{}
client.SetClientset(clientSet)
client.SetKubectl(kctl)
return client, nil
}
// SetClientset setter for Clientset interface
func (c *Client) SetClientset(cs kubernetes.Interface) {
c.clientset = cs
}
// SetKubectl setter for Kubectl interface // SetKubectl setter for Kubectl interface
func (c *Client) SetKubectl(kctl kubectl.Interface) { func (c *Client) SetKubectl(kctl kubectl.Interface) {
c.kubectl = kctl c.kubectl = kctl

View File

@ -0,0 +1,62 @@
package fake
import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"opendev.org/airship/airshipctl/pkg/k8s/client"
"opendev.org/airship/airshipctl/pkg/k8s/kubectl"
)
// 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
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()
}
// 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()
}
// 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()
}