diff --git a/tests/acceptance_test.go b/tests/acceptance_test.go new file mode 100644 index 00000000..20fb279d --- /dev/null +++ b/tests/acceptance_test.go @@ -0,0 +1,187 @@ +package tests + +import ( + "bytes" + "context" + "net/http" + "testing" + "time" + + "github.com/keel-hq/keel/types" + + apps_v1 "k8s.io/api/apps/v1" + "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var dockerHub0150Webhook = `{ + "push_data": { + "pushed_at": 1497467660, + "images": [], + "tag": "0.0.15", + "pusher": "karolisr" + }, + "repository": { + "status": "Active", + "description": "", + "is_trusted": false, + "repo_url": "https://hub.docker.com/r/webhook-demo", + "owner": "karolisr", + "is_official": false, + "is_private": false, + "name": "keel", + "namespace": "karolisr", + "star_count": 0, + "comment_count": 0, + "date_created": 1497032538, + "repo_name": "karolisr/webhook-demo" + } +}` + +func TestSemverUpdate(t *testing.T) { + + // stop := make(chan struct{}) + context, cancel := context.WithCancel(context.Background()) + // defer close(ctx) + defer cancel() + + go startKeel(context) + + _, kcs := getKubernetesClient() + + t.Run("UpdateThroughDockerHubWebhook", func(t *testing.T) { + + testNamespace := createNamespaceForTest() + defer deleteTestNamespace(testNamespace) + + dep := &apps_v1.Deployment{ + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "deployment-1", + Namespace: testNamespace, + Labels: map[string]string{types.KeelPolicyLabel: "all"}, + Annotations: map[string]string{}, + }, + apps_v1.DeploymentSpec{ + Selector: &meta_v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "wd-1", + }, + }, + Template: v1.PodTemplateSpec{ + ObjectMeta: meta_v1.ObjectMeta{ + Labels: map[string]string{ + "app": "wd-1", + "release": "1", + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + v1.Container{ + Name: "wd-1", + Image: "karolisr/webhook-demo:0.0.14", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + } + + _, err := kcs.AppsV1().Deployments(testNamespace).Create(dep) + if err != nil { + t.Fatalf("failed to create deployment: %s", err) + } + // giving some time to get started + // TODO: replace with a readiness check function to wait for 1/1 READY + time.Sleep(2 * time.Second) + + // sending webhook + client := http.DefaultClient + buf := bytes.NewBufferString(dockerHub0150Webhook) + req, err := http.NewRequest("POST", "http://localhost:9300/v1/webhooks/dockerhub", buf) + if err != nil { + t.Fatalf("failed to create req: %s", err) + } + resp, err := client.Do(req) + if err != nil { + t.Errorf("failed to make a webhook request to keel: %s", err) + } + + if resp.StatusCode != 200 { + t.Errorf("unexpected webhook response from keel: %d", resp.StatusCode) + } + + time.Sleep(2 * time.Second) + + updated, err := kcs.AppsV1().Deployments(testNamespace).Get(dep.ObjectMeta.Name, meta_v1.GetOptions{}) + if err != nil { + t.Fatalf("failed to get deployment: %s", err) + } + + if updated.Spec.Template.Spec.Containers[0].Image != "karolisr/webhook-demo:0.0.15" { + t.Errorf("expected 'karolisr/webhook-demo:0.0.15', got: '%s'", updated.Spec.Template.Spec.Containers[0].Image) + } + }) + + t.Run("UpdateThroughDockerHubPolling", func(t *testing.T) { + + testNamespace := createNamespaceForTest() + defer deleteTestNamespace(testNamespace) + + dep := &apps_v1.Deployment{ + meta_v1.TypeMeta{}, + meta_v1.ObjectMeta{ + Name: "deployment-1", + Namespace: testNamespace, + Labels: map[string]string{ + types.KeelPolicyLabel: "major", + types.KeelTriggerLabel: "poll", + }, + Annotations: map[string]string{}, + }, + apps_v1.DeploymentSpec{ + Selector: &meta_v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "wd-1", + }, + }, + Template: v1.PodTemplateSpec{ + ObjectMeta: meta_v1.ObjectMeta{ + Labels: map[string]string{ + "app": "wd-1", + "release": "1", + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + v1.Container{ + Name: "wd-1", + Image: "keelhq/push-workflow-example:0.1.0-dev", + }, + }, + }, + }, + }, + apps_v1.DeploymentStatus{}, + } + + _, err := kcs.AppsV1().Deployments(testNamespace).Create(dep) + if err != nil { + t.Fatalf("failed to create deployment: %s", err) + } + // giving some time to get started + // TODO: replace with a readiness check function to wait for 1/1 READY + time.Sleep(2 * time.Second) + + updated, err := kcs.AppsV1().Deployments(testNamespace).Get(dep.ObjectMeta.Name, meta_v1.GetOptions{}) + if err != nil { + t.Fatalf("failed to get deployment: %s", err) + } + + if updated.Spec.Template.Spec.Containers[0].Image != "keelhq/push-workflow-example:0.5.0-dev" { + t.Errorf("expected 'keelhq/push-workflow-example:0.5.0-dev', got: '%s'", updated.Spec.Template.Spec.Containers[0].Image) + } + }) + +} diff --git a/tests/helpers.go b/tests/helpers.go new file mode 100644 index 00000000..a4bd7a36 --- /dev/null +++ b/tests/helpers.go @@ -0,0 +1,93 @@ +package tests + +import ( + "context" + "flag" + "os" + "os/exec" + "path/filepath" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + // load the gcp plugin (required to authenticate against GKE clusters). + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + + log "github.com/sirupsen/logrus" +) + +var kubeConfig = flag.String("kubeconfig", "", "Path to Kubernetes config file") + +func getKubeConfig() string { + if *kubeConfig != "" { + return *kubeConfig + } + + return filepath.Join(os.Getenv("HOME"), ".kube", "config") +} + +func getKubernetesClient() (*rest.Config, *kubernetes.Clientset) { + // use the current context in kubeconfig + config, err := clientcmd.BuildConfigFromFlags("", getKubeConfig()) + if err != nil { + panic(err.Error()) + } + + // create the clientset + clientSet, err := kubernetes.NewForConfig(config) + if err != nil { + panic(err.Error()) + } + + return config, clientSet +} + +func createNamespaceForTest() string { + _, clientset := getKubernetesClient() + ns := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "keel-e2e-test-", + }, + } + cns, err := clientset.CoreV1().Namespaces().Create(ns) + if err != nil { + panic(err) + } + + log.Infof("test namespace '%s' created", cns.Name) + + return cns.Name +} + +func deleteTestNamespace(namespace string) error { + + defer log.Infof("test namespace '%s' deleted", namespace) + _, clientset := getKubernetesClient() + deleteOptions := metav1.DeleteOptions{} + return clientset.CoreV1().Namespaces().Delete(namespace, &deleteOptions) +} + +func startKeel(ctx context.Context) error { + + log.Info("keel started") + defer log.Info("keel stopped") + + cmd := "keel" + args := []string{"--no-incluster", "--kubeconfig", getKubeConfig()} + c := exec.CommandContext(ctx, cmd, args...) + c.Stdout = os.Stdout + c.Stderr = os.Stderr + + go func() { + <-ctx.Done() + err := c.Process.Kill() + if err != nil { + log.Errorf("failed to kill keel process: %s", err) + } + }() + + return c.Run() +}