k8t

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 17, 2023 License: MIT Imports: 26 Imported by: 0

README

K8T - your kit for e2e Kubernetes testing

The project's goal is to offer high-level, user-friendly functions for e2e testing of Kubernetes. The K8T's vision is to:

  • provide hi-level easy-to use functions for Kubernetes testing
  • focus on simplicity
  • not covering all use cases, just the basic-once in simple manner
  • not to be test framework itself
  • do not depend on any specific test framework

Warning

Note that K8T in under active development and the APIs may change in a backwards incompatible manner.

Motivation

E2E testing for Kubernetes can be challenging and requires writing a lot of code. Personally, I prefer using BDD-style E2E testing, and I'm satisfied with what Ginkgo/Gomega or GoDog offer.

Although there is the e2s-kubernetes framework available, my main issue with it is that it aims to be a test framework itself. Also simplicity is questionable.

Another option is to use Go client directly. The Go client for Kubernetes is a low-level tool that can be challenging to use directly in e2e testing scenarios.

What I really desire is a simple collection of high-level functions that can seamlessly integrate with my preferred BDD-like framework, traditional unit tests, or even enable me to build my own testing CLI (similar to cilium connectivity test).

Getting started

The K8T is a straightforward Go module that can be directly integrated into your tests. Just fetch the K8T Go module by executing the following command:

go get sn3d.com/sn3d/k8t
Apply and delete resource from manifest

The module relies on a cluster instance that provides essential functions such as Apply(), Get(), List(), and Delete(). Additionally, the cluster instance includes a WaitFor() function that can be used with different checking functions.

Here's an example that applies the testdata/busybox.yaml manifest, verifies if the busybox pod is running, and finally deletes the pod:

// get the instance for tested cluster (from KUBECONFIG)
cluster,_ := k8t.NewFromEnvironment()

// apply the manifest
cluster.ApplyFile("testdata/busybox.yaml")

// check if pod is running
err := cluster.WaitFor(k8t.PodIsRunning("","busybox-pod"))
if err != nil {
   panic("Pod is not running")
}

cluster.DeleteFile("testdata/busybox.yaml")
Execute command inside cluster

The K8T module provides the Execf() function as well as the more detailed ExecWithOpts() function. These functions can be used when you need to execute commands inside a specific container.

Having the ability to execute commands from the test container within the tested cluster is useful for E2E testing. It allows us to verify the proper functioning of DNS or other network components, check storage, and perform various other tests.

// execute the command and get the result
result := cluster.Execf("busybox", "busybox-container", "nslookup %s", "google.com")

if result.Err != nil {
   panic("cannot execute command")
}

fmt.Printf(result.String())

The Execf() function is a straightforward execution function that accepts arguments such as the pod name, container name, and command. The command can be formatted using a syntax similar to Printf(). It's important to note that the pod should be running in the default test namespace, and the command is executed with the /bin/sh shell.

If you require additional control over the execution, you can use the ExecWithOpts() function. This function allows you to modify the namespace and provides more options for customization.

Installing Helm charts

K8T also provides support for installing Helm charts.

Helm is extensively used in the Kubernetes ecosystem, and for E2E testing scenarios, it's often necessary to install components using Helm. Let's say we have a Helm chart located in the testdata/my-helm folder that we want to install. We also want to customize certain values, such as deployment.replicaCount. Here's an example code snippet to accomplish this:

// get the instance for tested cluster (from KUBECONFIG)
cluster,_ := k8t.NewFromEnvironment()

// set values for Helm release
vals := helm.Value{
   "deployment": helm.Value{
      "replicaCount": 3,
   },
}

// install helm chart with values
err := helm.Install(cluster, "testdata/my-helm", vals)
if err != nil {
   panic("chart cannot be installed")
}
Using K8T with Ginkgo/Gomega

One of its notable features is its seamless integration with Ginkgo, a popular BDD testing framework. With K8T, you can effortlessly incorporate Kubernetes testing capabilities into your Ginkgo test suite.

var _ = Describe("My first K8T test", func() {
   var cluster *k8t.Cluster

   BeforeEach(func() {
      cluster,_ := k8t.NewFromEnvironment()
   })

   It("should apply manifest", func() {
      err := cluster.ApplyFile("testdata/my-manifest.yaml")
      Expect(err).NotTo(HaveOccurred())
   })
}

Feedback & Bugs

Feedback is more than welcome. Did you found a bug? Is something not behaving as expected? Feature or bug, feel free to create issue.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ApplyOpts

type ApplyOpts struct {
	// here you can pass your context. It it's not set, the default
	// context.Background() will be used
	Context context.Context

	// namespace where to apply resource. If it's not set, the cluster's
	// test namespace will be used. This is ignored for cluster-wide resources
	Namespace string
}

options for GetWithOpts function

type Checker

type Checker func(context.Context, *Cluster) (bool, error)

enables the WaitFor() function to receive and execute custom checking functions, allowing the caller to define their own conditions for waiting on a specific resource or state to be reached.

func PodIsRunning

func PodIsRunning(namespace, name string) Checker

check if given pod is running, that means it's in `Running` phase. The function expect also namespace. If you provide empty string, then cluster's default test namespace will be used.

func ResourceExist

func ResourceExist(apiVersion, kind, name string) Checker

check if resource of given kind and name exists in the given cluster, in the cluster's test namespace

func ResourceNotExist

func ResourceNotExist(apiVersion, kind, name string) Checker

check if resource of given kind and name doesn't exists in the given cluster, in the cluster's test namespace

type Cluster

type Cluster struct {
	// contains filtered or unexported fields
}

representing running cluster for your tests

func New

func New(apiConfig *api.Config) (*Cluster, error)

func NewFromEnvironment

func NewFromEnvironment() (*Cluster, error)

create cluster instance from KUBECONFIG

func NewFromFile

func NewFromFile(path string) (*Cluster, error)

create cluster instance from given kubeconfig file

func (*Cluster) APIConfig

func (c *Cluster) APIConfig() *api.Config

func (*Cluster) Apply

func (c *Cluster) Apply(yml string) error

Same as kubectl apply. Function apply any YAML into given cluster, into default test namespace.

func (*Cluster) ApplyFile

func (c *Cluster) ApplyFile(path string) error

Apply manifest on given path

func (*Cluster) ApplyWithOpts

func (c *Cluster) ApplyWithOpts(yml string, opts ApplyOpts) error

More verbose version of Apply() function where you can pass your own context or set namespace

func (*Cluster) Delete

func (c *Cluster) Delete(yml string) error

Delete resource of given YAML. The resource must be located in cluster's default test namespace or it must be cluster-wide resource. If you want to delete resource in another namespace, you should use more verbose DeleteWithOpts.

func (*Cluster) DeleteFile

func (c *Cluster) DeleteFile(path string) error

Delete resource in given file

func (*Cluster) DeleteWithOpts

func (c *Cluster) DeleteWithOpts(yml string, opts DeleteOpts) error

More verbose version of Delete() function that allow you pass the context or change the namespace etc.

func (*Cluster) ExecWithOpts

func (c *Cluster) ExecWithOpts(pod string, container string, command []string, opts ExecOpts) ExecResult

function executes command as array (without shell) in given pod and container. It's very general function.

func (*Cluster) Execf

func (c *Cluster) Execf(pod, container, command string, args ...any) ExecResult

Less verbose version of ExecWithOpts, which will format and executes given command with args with '/bin/sh' shell in given pod.

func (*Cluster) Get

func (c *Cluster) Get(apiVersion, kind, name string) (*unstructured.Unstructured, error)

Get unstructured data of given resource in test namespace. Resource is identified by it's name apiVersion e.g. ('networking.k8s.io/v1') and kind (e.g. 'NetworkPolicy')

func (*Cluster) GetWithOpts

func (c *Cluster) GetWithOpts(apiVersion, kind, name string, opts GetOpts) (*unstructured.Unstructured, error)

More verbose version of Get() function. Use this function if you want to pass own context, or specify the namespace

func (*Cluster) List

func (c *Cluster) List(apiVersion, kind, labelSelector string) (*unstructured.UnstructuredList, error)

More verbose version of List() function. Use this function if you want to pass own context, or specify the namespace

func (*Cluster) ListWithOpts

func (c *Cluster) ListWithOpts(apiVersion, kind, labelSelector string, opts ListOpts) (*unstructured.UnstructuredList, error)

More verbose version of List() function. Use this function if you want to pass own context, or specify the namespace

func (*Cluster) RESTConfig

func (c *Cluster) RESTConfig() *rest.Config

returns low-level client rest config

func (*Cluster) TestNamespace

func (c *Cluster) TestNamespace() string

func (*Cluster) WaitFor

func (c *Cluster) WaitFor(check Checker) error

simple, sort version of WaitForOpts with default values. The timeout is 2 minutes and interval is set to 2 seconds. Is't equivalent to:

WaitForOpts(check, WaitForOpts{})

func (*Cluster) WaitForWithOpts

func (c *Cluster) WaitForWithOpts(check Checker, opts WaitForOpts) error

the function will check if system met given condition. The checking is triggered every n seconds defined by interval in opts. The invocation of checking function continue until:

  • function ends with true
  • reach timeout limit defined in opts

The function returns nil if the given checking function met the condition. If we reach the timeout or some error occured during checking, the function returns error.

type DeleteOpts

type DeleteOpts struct {
	// here you can pass your context. It it's not set, the default
	// context.Background() will be used
	Context context.Context

	// namespace where delete resource. If it's not set, the cluster's
	// test namespace will be used. This is ignored for cluster-wide resources
	Namespace string
}

options for DeleteWithOpts function

type ExecOpts

type ExecOpts struct {
	// here you can pass your context. It it's not set, the default
	// context.Background() will be used
	Context context.Context

	// namespace where is pod for exec. If it's not set, the cluster's
	// test namespace will be used.
	Namespace string
}

type ExecResult

type ExecResult struct {
	Stdout bytes.Buffer
	Stderr bytes.Buffer
	Err    error
}

func (ExecResult) String

func (e ExecResult) String() string

type GetOpts

type GetOpts struct {
	// here you can pass your context. It it's not set, the default
	// context.Background() will be used
	Context context.Context

	// namespace where is the resource. If it's not set, the cluster's
	// testNamespace will be used
	Namespace string
}

Optional options for GetWithOpts function

type ListOpts

type ListOpts struct {
	// here you can pass your context. It it's not set, the default
	// context.Background() will be used
	Context context.Context

	// namespace where is the resource. If it's not set, the cluster's
	// testNamespace will be used
	Namespace string
}

Optional options for ListWithOpts function

type WaitForOpts

type WaitForOpts struct {

	// here you can pass your context. It it's not set, the default
	// context.Background() will be used
	Context context.Context

	// maximum duration for which the function will wait for the condition
	// to be met. If it's not set, the default 2 minutes will be used
	Timeout time.Duration

	// represents the duration between invocations of the checker function
	// If it's not set, the defauls 2 seconds will be used
	Interval time.Duration
}

Optional options for WaitForWithOpts function

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL