package tests import ( "context" "fmt" "os" "testing" "time" "github.com/keel-hq/keel/secrets" "github.com/keel-hq/keel/types" log "github.com/sirupsen/logrus" apps_v1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestPollingSemverUpdate(t *testing.T) { // stop := make(chan struct{}) ctx, cancel := context.WithCancel(context.Background()) // defer close(ctx) defer cancel() // go startKeel(ctx) keel := &KeelCmd{} go func() { err := keel.Start(ctx) if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("failed to start Keel process") } }() defer func() { err := keel.Stop() if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("failed to stop Keel process") } }() _, kcs := getKubernetesClient() t.Run("UpdateThroughDockerHubPollingA", func(t *testing.T) { // UpdateThroughDockerHubPollingA tests a polling trigger when we have a higher version // but without a pre-release tag and a lower version with pre-release. The version of the deployment // is with pre-prerealse so we should upgrade to that one. 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{ types.KeelPollScheduleAnnotation: "@every 2s", }, }, 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{ { Name: "wd-1", Image: "keelhq/push-workflow-example:0.1.0-dev", }, }, }, }, }, apps_v1.DeploymentStatus{}, } createOptions := meta_v1.CreateOptions{} _, err := kcs.AppsV1().Deployments(testNamespace).Create(context.Background(), dep, createOptions) if err != nil { t.Fatalf("failed to create deployment: %s", err) } ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() err = waitFor(ctx, kcs, testNamespace, dep.ObjectMeta.Name, "keelhq/push-workflow-example:0.5.0-dev") if err != nil { t.Errorf("update failed: %s", err) } }) t.Run("UpdateThroughDockerHubPollingB", func(t *testing.T) { // UpdateThroughDockerHubPollingA tests a polling trigger when we have a higher version // but without a pre-release tag and a lower version with pre-release. The version of the deployment // is without pre-prerealse testNamespace := createNamespaceForTest() defer deleteTestNamespace(testNamespace) dep := &apps_v1.Deployment{ meta_v1.TypeMeta{}, meta_v1.ObjectMeta{ Name: "deployment-2", Namespace: testNamespace, Labels: map[string]string{ types.KeelPolicyLabel: "major", types.KeelTriggerLabel: "poll", }, Annotations: map[string]string{ types.KeelPollScheduleAnnotation: "@every 2s", }, }, 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{ { Name: "wd-1", Image: "keelhq/push-workflow-example:0.1.0", }, }, }, }, }, apps_v1.DeploymentStatus{}, } createOptions := meta_v1.CreateOptions{} _, err := kcs.AppsV1().Deployments(testNamespace).Create(context.Background(), dep, createOptions) if err != nil { t.Fatalf("failed to create deployment: %s", err) } ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() err = waitFor(ctx, kcs, testNamespace, dep.ObjectMeta.Name, "keelhq/push-workflow-example:0.10.0") if err != nil { t.Errorf("update failed: %s", err) } }) t.Run("UpdateThroughDockerHubPollingC", func(t *testing.T) { testNamespace := createNamespaceForTest() defer deleteTestNamespace(testNamespace) dep := &apps_v1.Deployment{ meta_v1.TypeMeta{}, meta_v1.ObjectMeta{ Name: "deployment-2", Namespace: testNamespace, Labels: map[string]string{ types.KeelPolicyLabel: "major", types.KeelTriggerLabel: "poll", }, Annotations: map[string]string{ types.KeelPollScheduleAnnotation: "@every 2s", }, }, 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{ { Name: "wd-1", Image: "keelhq/push-workflow-example:0.3.0-alpha", }, }, }, }, }, apps_v1.DeploymentStatus{}, } createOptions := meta_v1.CreateOptions{} _, err := kcs.AppsV1().Deployments(testNamespace).Create(context.Background(), dep, createOptions) if err != nil { t.Fatalf("failed to create deployment: %s", err) } ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() err = waitFor(ctx, kcs, testNamespace, dep.ObjectMeta.Name, "keelhq/push-workflow-example:0.11.0-alpha") if err != nil { t.Errorf("update failed: %s", err) } }) t.Run("UpdateThroughDockerHubPollingD", func(t *testing.T) { testNamespace := createNamespaceForTest() defer deleteTestNamespace(testNamespace) dep := &apps_v1.Deployment{ meta_v1.TypeMeta{}, meta_v1.ObjectMeta{ Name: "deployment-3", Namespace: testNamespace, Labels: map[string]string{ types.KeelPolicyLabel: "patch", types.KeelTriggerLabel: "poll", }, Annotations: map[string]string{ types.KeelPollScheduleAnnotation: "@every 2s", }, }, 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{ { Name: "wd-1", Image: "vaultwarden/server:1.25.1", }, }, }, }, }, apps_v1.DeploymentStatus{}, } createOptions := meta_v1.CreateOptions{} _, err := kcs.AppsV1().Deployments(testNamespace).Create(context.Background(), dep, createOptions) if err != nil { t.Fatalf("failed to create deployment: %s", err) } ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() err = waitFor(ctx, kcs, testNamespace, dep.ObjectMeta.Name, "vaultwarden/server:1.25.2") if err != nil { t.Errorf("update failed: %s", err) } }) } func TestPollingPrivateRegistry(t *testing.T) { // stop := make(chan struct{}) ctx, cancel := context.WithCancel(context.Background()) // defer close(ctx) defer cancel() // go startKeel(ctx) keel := &KeelCmd{} go func() { err := keel.Start(ctx) if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("failed to start Keel process") } }() defer func() { err := keel.Stop() if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("failed to stop Keel process") } }() _, kcs := getKubernetesClient() t.Run("UpdateThroughPrivateQuayPollingA", func(t *testing.T) { user := os.Getenv("DOCKERHUB_USERNAME") password := os.Getenv("DOCKERHUB_PASSWORD") if user == "" || password == "" { fmt.Println("[X] Skipping UpdateThroughPrivateQuayPollingA test since DOCKERHUB_USERNAME and/or DOCKERHUB_PASSWORD env vars not set") t.Skip() } // UpdateThroughDockerHubPollingA tests a polling trigger when we have a higher version // but without a pre-release tag and a lower version with pre-release. The version of the deployment // is with pre-prerealse so we should upgrade to that one. testNamespace := createNamespaceForTest() defer deleteTestNamespace(testNamespace) payload, err := secrets.EncodeDockerCfgJson(&secrets.DockerCfg{ "https://index.docker.io/v1/": &secrets.Auth{ Auth: secrets.EncodeBase64Secret(user, password), }, }) if err != nil { t.Fatalf("failed to encode docker cfg secret payload: %s", err) } secretName := "verysecret" fmt.Println(string(payload)) secret := &v1.Secret{ TypeMeta: meta_v1.TypeMeta{}, ObjectMeta: meta_v1.ObjectMeta{ Name: secretName, Namespace: testNamespace, Labels: map[string]string{}, Annotations: map[string]string{}, }, Type: v1.SecretTypeDockerConfigJson, Data: map[string][]byte{ ".dockerconfigjson": []byte(payload), }, } createOptions := meta_v1.CreateOptions{} _, err = kcs.CoreV1().Secrets(testNamespace).Create(context.Background(), secret, createOptions) if err != nil { t.Fatalf("failed to create secret: %s", err) } 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{ types.KeelPollScheduleAnnotation: "@every 2s", }, }, 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{ ImagePullSecrets: []v1.LocalObjectReference{ { Name: secretName, }, }, Containers: []v1.Container{ { ImagePullPolicy: v1.PullAlways, Name: "wd-1", Image: "karolisr/demo-webhook:0.0.1", }, }, }, }, }, apps_v1.DeploymentStatus{}, } createOptions = meta_v1.CreateOptions{} _, err = kcs.AppsV1().Deployments(testNamespace).Create(context.Background(), dep, createOptions) if err != nil { t.Fatalf("failed to create deployment: %s", err) } ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() err = waitFor(ctx, kcs, testNamespace, dep.ObjectMeta.Name, "karolisr/demo-webhook:0.0.2") if err != nil { t.Errorf("update failed: %s", err) } }) t.Run("UpdateThroughPrivateGitlabPolling", func(t *testing.T) { user := os.Getenv("GITLAB_USERNAME") password := os.Getenv("GITLAB_PASSWORD") if user == "" || password == "" { fmt.Println("[X] Skipping UpdateThroughPrivateGitlabPolling test since GITLAB_USERNAME and/or GITLAB_PASSWORD env vars not set") t.Skip() } testNamespace := createNamespaceForTest() defer func() { err := deleteTestNamespace(testNamespace) if err != nil { log.WithFields(log.Fields{ "error": err, "namespace": testNamespace, }).Error("error while deleting test namespace") } }() payload, err := secrets.EncodeDockerCfgJson(&secrets.DockerCfg{ "registry.gitlab.com": &secrets.Auth{ Auth: secrets.EncodeBase64Secret(user, password), }, }) if err != nil { t.Fatalf("failed to encode docker cfg secret payload: %s", err) } secretName := "gitlab-registry-credentials" secret := &v1.Secret{ TypeMeta: meta_v1.TypeMeta{}, ObjectMeta: meta_v1.ObjectMeta{ Name: secretName, Namespace: testNamespace, Labels: map[string]string{}, Annotations: map[string]string{}, }, Type: v1.SecretTypeDockerConfigJson, Data: map[string][]byte{ ".dockerconfigjson": []byte(payload), }, } createOptions := meta_v1.CreateOptions{} _, err = kcs.CoreV1().Secrets(testNamespace).Create(context.Background(), secret, createOptions) if err != nil { t.Fatalf("failed to create secret: %s", err) } else { t.Logf("secret '%s' created in namespace '%s'", secret.Name, secret.Namespace) } 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{ types.KeelPollScheduleAnnotation: "@every 2s", }, }, 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{ ImagePullSecrets: []v1.LocalObjectReference{ { Name: secretName, }, }, Containers: []v1.Container{ { ImagePullPolicy: v1.PullAlways, Name: "wd-1", Image: "registry.gitlab.com/karolisr/keel:0.1.0", }, }, }, }, }, apps_v1.DeploymentStatus{}, } createOptions = meta_v1.CreateOptions{} _, err = kcs.AppsV1().Deployments(testNamespace).Create(context.Background(), dep, createOptions) if err != nil { t.Fatalf("failed to create deployment: %s", err) } ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() err = waitFor(ctx, kcs, testNamespace, dep.ObjectMeta.Name, "registry.gitlab.com/karolisr/keel:0.2.0") if err != nil { t.Errorf("update failed: %s", err) } }) }