Skip to content
Snippets Groups Projects
Unverified Commit b01b1647 authored by Angus Lees's avatar Angus Lees
Browse files

Add basic integration test framework

Currently contains basic "update" and "delete" tests.
More cases will be added in followup patches.
parent 744cb1bc
No related branches found
No related tags found
No related merge requests found
......@@ -9,8 +9,13 @@ os:
env:
global:
- PATH=$PATH:$GOPATH/bin
- VERSION="${TRAVIS_TAG:-build-$TRAVIS_BUILD_ID}"
- EXTRA_GO_FLAGS_TEST="-race"
- MINIKUBE_WANTUPDATENOTIFICATION=false
- MINIKUBE_WANTREPORTERRORPROMPT=false
- MINIKUBE_HOME=${HOME}
- CHANGE_MINIKUBE_NONE_USER=true
# Frequent jsonnet_cgo crashes with xcode7.3 (default)
osx_image: xcode8.3
......@@ -19,6 +24,7 @@ matrix:
include:
- env: TARGET=x86_64-linux-musl EXTRA_GO_FLAGS_TEST=""
go: 1.8.x
- env: DO_INTEGRATION_TEST=1 INT_KVERS=v1.7.0
# cgo requires golang >= 1.8.1 (or other workarounds) on recent
# osx/xcode - see https://github.com/golang/go/issues/19734
# 'go test' also hangs repeatably on go-1.8.3/MacOS ?
......@@ -30,6 +36,9 @@ matrix:
- os: osx
go: 1.8.x
services:
- docker
addons:
apt:
packages:
......@@ -73,6 +82,25 @@ before_install:
install:
- go build -i -ldflags "$GO_LDFLAGS" .
- |
if [ "$DO_INTEGRATION_TEST" = 1 ]; then
if ! which minikube; then
wget -O minikube \
https://storage.googleapis.com/minikube/releases/v0.21.0/minikube-$(go env GOOS)-$(go env GOARCH)
install -m 755 minikube $GOPATH/bin/minikube
fi
if ! which kubectl; then
wget https://storage.googleapis.com/kubernetes-release/release/$INT_KVERS/bin/$(go env GOOS)/$(go env GOARCH)/kubectl
install -m 755 kubectl $GOPATH/bin/kubectl
fi
mkdir -p $HOME/.kube
touch $HOME/.kube/config
sudo -E $GOPATH/bin/minikube start --vm-driver=none \
--extra-config apiserver.Authorization.Mode=RBAC \
--kubernetes-version $INT_KVERS
go get github.com/onsi/ginkgo/ginkgo
fi
script:
- make VERSION="$VERSION" EXTRA_GO_FLAGS="$EXTRA_GO_FLAGS_TEST" test
......@@ -82,6 +110,12 @@ script:
ldd ./kubecfg || otool -L ./kubecfg || :
- ./kubecfg help
- ./kubecfg version
- |
if [ "$DO_INTEGRATION_TEST" = 1 ]; then
minikube update-context
minikube status
make VERSION="$VERSION" EXTRA_GO_FLAGS="$EXTRA_GO_FLAGS_TEST" integrationtest
fi
after_script: set +e
......
......@@ -19,11 +19,16 @@ GO = go
EXTRA_GO_FLAGS =
GO_FLAGS = -ldflags="-X main.version=$(VERSION) $(GO_LDFLAGS)" $(EXTRA_GO_FLAGS)
GOFMT = gofmt
# GINKGO = "go test" also works if you want to avoid ginkgo tool
GINKGO = ginkgo
JSONNET_FILES = lib/kubecfg_test.jsonnet examples/guestbook.jsonnet
# TODO: Simplify this once ./... ignores ./vendor
GO_PACKAGES = ./cmd/... ./utils/... ./pkg/...
# Default cluster from this config is used for integration tests
KUBECONFIG = $(HOME)/.kube/config
all: kubecfg
kubecfg:
......@@ -38,6 +43,9 @@ jsonnettest: kubecfg $(JSONNET_FILES)
# TODO: use `kubecfg check` once implemented
./kubecfg -J lib show $(JSONNET_FILES) >/dev/null
integrationtest: kubecfg
$(GINKGO) -tags 'integration' integration -- -kubeconfig $(KUBECONFIG) -kubecfg-bin $(abspath $<)
vet:
$(GO) vet $(GO_FLAGS) $(GO_PACKAGES)
......
// +build integration
package integration
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/pkg/api/v1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func objNames(list *v1.ConfigMapList) []string {
ret := make([]string, 0, len(list.Items))
for _, obj := range list.Items {
ret = append(ret, obj.GetName())
}
return ret
}
var _ = Describe("delete", func() {
var c corev1.CoreV1Interface
var ns string
var objs []runtime.Object
BeforeEach(func() {
c = corev1.NewForConfigOrDie(clusterConfigOrDie())
ns = createNsOrDie(c, "delete")
objs = []runtime.Object{
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
},
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
},
}
})
AfterEach(func() {
deleteNsOrDie(c, ns)
})
Describe("Simple delete", func() {
JustBeforeEach(func() {
err := runKubecfgWith([]string{"delete", "-vv", "-n", ns}, objs)
Expect(err).NotTo(HaveOccurred())
})
Context("With no existing state", func() {
It("should succeed", func() {
Expect(c.ConfigMaps(ns).List(metav1.ListOptions{})).
To(WithTransform(objNames, BeEmpty()))
})
})
Context("With existing objects", func() {
BeforeEach(func() {
toCreate := []*v1.ConfigMap{}
for _, cm := range objs {
toCreate = append(toCreate, cm.(*v1.ConfigMap))
}
// .. and one extra (that should not be deleted)
baz := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "baz"},
}
toCreate = append(toCreate, baz)
for _, cm := range toCreate {
_, err := c.ConfigMaps(ns).Create(cm)
Expect(err).To(Not(HaveOccurred()))
}
})
It("should delete mentioned objects", func() {
Eventually(func() (*v1.ConfigMapList, error) {
return c.ConfigMaps(ns).List(metav1.ListOptions{})
}).Should(WithTransform(objNames, ConsistOf("baz")))
})
})
})
})
// +build integration
package integration
import (
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/pkg/api"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
// For client auth plugins
_ "k8s.io/client-go/plugin/pkg/client/auth"
// For apimachinery serialisation magic
_ "k8s.io/client-go/pkg/api/install"
)
var kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
var kubecfgBin = flag.String("kubecfg-bin", "kubecfg", "path to kubecfg executable under test")
func init() {
if missingVersions := api.Registry.ValidateEnvRequestedVersions(); len(missingVersions) != 0 {
panic(fmt.Sprintf("KUBE_API_VERSIONS contains versions that are not installed: %q.", missingVersions))
}
}
func clusterConfigOrDie() *rest.Config {
var config *rest.Config
var err error
if *kubeconfig != "" {
config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig)
} else {
config, err = rest.InClusterConfig()
}
if err != nil {
panic(err.Error())
}
return config
}
func createNsOrDie(c corev1.CoreV1Interface, ns string) string {
result, err := c.Namespaces().Create(
&v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: ns,
},
})
if err != nil {
panic(err.Error())
}
name := result.GetName()
fmt.Fprintf(GinkgoWriter, "Created namespace %s\n", name)
return name
}
func deleteNsOrDie(c corev1.CoreV1Interface, ns string) {
err := c.Namespaces().Delete(ns, &metav1.DeleteOptions{})
if err != nil {
panic(err.Error())
}
}
func runKubecfgWith(flags []string, input []runtime.Object) error {
tmpdir, err := ioutil.TempDir("", "kubecfg-testdata")
if err != nil {
return err
}
defer os.RemoveAll(tmpdir)
fname := filepath.Join(tmpdir, "input.yaml")
f, err := os.Create(fname)
if err != nil {
return err
}
enc := api.Codecs.LegacyCodec(v1.SchemeGroupVersion)
for _, o := range input {
buf, err := runtime.Encode(enc, o)
if err != nil {
return err
}
fmt.Fprintf(f, "---\n")
_, err = f.Write(buf)
if err != nil {
return err
}
}
if err := f.Close(); err != nil {
return err
}
args := []string{}
args = append(args, flags...)
args = append(args, fname)
fmt.Fprintf(GinkgoWriter, "Running %q %q\n", *kubecfgBin, args)
cmd := exec.Command(*kubecfgBin, args...)
cmd.Stdout = GinkgoWriter
cmd.Stderr = GinkgoWriter
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func TestE2e(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "kubecfg integration tests")
}
// +build integration
package integration
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/pkg/api/v1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func cmData(cm *v1.ConfigMap) map[string]string {
return cm.Data
}
var _ = Describe("update", func() {
var c corev1.CoreV1Interface
var ns string
const cmName = "testcm"
BeforeEach(func() {
c = corev1.NewForConfigOrDie(clusterConfigOrDie())
ns = createNsOrDie(c, "update")
})
AfterEach(func() {
deleteNsOrDie(c, ns)
})
Describe("A simple update", func() {
var cm *v1.ConfigMap
BeforeEach(func() {
cm = &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: cmName},
Data: map[string]string{"foo": "bar"},
}
})
JustBeforeEach(func() {
err := runKubecfgWith([]string{"update", "-vv", "-n", ns}, []runtime.Object{cm})
Expect(err).NotTo(HaveOccurred())
})
Context("With no existing state", func() {
It("should produce expected object", func() {
Expect(c.ConfigMaps(ns).Get("testcm", metav1.GetOptions{})).
To(WithTransform(cmData, HaveKeyWithValue("foo", "bar")))
})
})
Context("With existing object", func() {
BeforeEach(func() {
_, err := c.ConfigMaps(ns).Create(cm)
Expect(err).To(Not(HaveOccurred()))
})
It("should succeed", func() {
Expect(c.ConfigMaps(ns).Get("testcm", metav1.GetOptions{})).
To(WithTransform(cmData, HaveKeyWithValue("foo", "bar")))
})
})
Context("With modified object", func() {
BeforeEach(func() {
otherCm := &v1.ConfigMap{
ObjectMeta: cm.ObjectMeta,
Data: map[string]string{"foo": "not bar"},
}
_, err := c.ConfigMaps(ns).Create(otherCm)
Expect(err).NotTo(HaveOccurred())
})
It("should update the object", func() {
Expect(c.ConfigMaps(ns).Get("testcm", metav1.GetOptions{})).
To(WithTransform(cmData, HaveKeyWithValue("foo", "bar")))
})
})
})
Describe("An update with mixed namespaces", func() {
var ns2 string
BeforeEach(func() {
ns2 = createNsOrDie(c, "update")
})
AfterEach(func() {
deleteNsOrDie(c, ns2)
})
var objs []runtime.Object
BeforeEach(func() {
objs = []runtime.Object{
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "nons"},
},
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: "ns1"},
},
&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: ns2, Name: "ns2"},
},
}
})
JustBeforeEach(func() {
err := runKubecfgWith([]string{"update", "-vv", "-n", ns}, objs)
Expect(err).NotTo(HaveOccurred())
})
It("should create objects in the correct namespaces", func() {
Expect(c.ConfigMaps(ns).Get("nons", metav1.GetOptions{})).
NotTo(BeNil())
Expect(c.ConfigMaps(ns).Get("ns1", metav1.GetOptions{})).
NotTo(BeNil())
Expect(c.ConfigMaps(ns2).Get("ns2", metav1.GetOptions{})).
NotTo(BeNil())
})
})
})
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment