package k8s import ( "context" "errors" "testing" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" k8sfake "k8s.io/client-go/kubernetes/fake" k8stesting "k8s.io/client-go/testing" ) func TestLoadSecretDataCoversMissingSecretValueAndCopy(t *testing.T) { client := &Client{Clientset: k8sfake.NewSimpleClientset( &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "filled", Namespace: "atlas"}, Data: map[string][]byte{"token": []byte("atlas-secret")}, }, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "missing-key", Namespace: "atlas"}, Data: map[string][]byte{"other": []byte("atlas-secret")}, }, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "empty-value", Namespace: "atlas"}, Data: map[string][]byte{"token": {}}, }, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "empty", Namespace: "atlas"}, }, )} value, err := client.LoadSecretData(context.Background(), "atlas", "missing", "token") if err != nil || value != nil { t.Fatalf("expected missing secret to return nil, got %q %v", string(value), err) } value, err = client.LoadSecretData(context.Background(), "atlas", "empty", "token") if err != nil || value != nil { t.Fatalf("expected empty secret key to return nil, got %q %v", string(value), err) } value, err = client.LoadSecretData(context.Background(), "atlas", "missing-key", "token") if err != nil || value != nil { t.Fatalf("expected missing data key to return nil, got %q %v", string(value), err) } value, err = client.LoadSecretData(context.Background(), "atlas", "empty-value", "token") if err != nil || value != nil { t.Fatalf("expected empty data value to return nil, got %q %v", string(value), err) } value, err = client.LoadSecretData(context.Background(), "atlas", "filled", "token") if err != nil || string(value) != "atlas-secret" { t.Fatalf("expected copied secret value, got %q %v", string(value), err) } value[0] = 'X' secret, err := client.Clientset.CoreV1().Secrets("atlas").Get(context.Background(), "filled", metav1.GetOptions{}) if err != nil { t.Fatalf("reload filled secret: %v", err) } if string(secret.Data["token"]) != "atlas-secret" { t.Fatalf("expected returned bytes to be copied, got stored=%q", string(secret.Data["token"])) } } func TestLoadSecretDataWrapsUnexpectedErrors(t *testing.T) { clientset := k8sfake.NewSimpleClientset() clientset.PrependReactor("get", "secrets", func(action k8stesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("get exploded") }) client := &Client{Clientset: clientset} if _, err := client.LoadSecretData(context.Background(), "atlas", "filled", "token"); err == nil || err.Error() == "get exploded" { t.Fatalf("expected wrapped get error, got %v", err) } } func TestSaveSecretDataCreatesAndUpdatesSecrets(t *testing.T) { client := &Client{Clientset: k8sfake.NewSimpleClientset()} if err := client.SaveSecretData(context.Background(), "atlas", "restic-usage", "usage.json", []byte("first"), map[string]string{"app": "soteria"}); err != nil { t.Fatalf("create secret data: %v", err) } created, err := client.Clientset.CoreV1().Secrets("atlas").Get(context.Background(), "restic-usage", metav1.GetOptions{}) if err != nil { t.Fatalf("get created secret: %v", err) } if string(created.Data["usage.json"]) != "first" || created.Labels["app"] != "soteria" { t.Fatalf("unexpected created secret: %#v", created) } created.ResourceVersion = "1" if _, err := client.Clientset.CoreV1().Secrets("atlas").Update(context.Background(), created, metav1.UpdateOptions{}); err != nil { t.Fatalf("prime created secret resource version: %v", err) } if err := client.SaveSecretData(context.Background(), "atlas", "restic-usage", "usage.json", []byte("second"), map[string]string{"component": "usage-store"}); err != nil { t.Fatalf("update secret data: %v", err) } updated, err := client.Clientset.CoreV1().Secrets("atlas").Get(context.Background(), "restic-usage", metav1.GetOptions{}) if err != nil { t.Fatalf("get updated secret: %v", err) } if string(updated.Data["usage.json"]) != "second" { t.Fatalf("expected updated secret payload, got %#v", updated.Data) } if updated.Labels["app"] != "soteria" || updated.Labels["component"] != "usage-store" { t.Fatalf("expected merged labels, got %#v", updated.Labels) } } func TestSaveSecretDataInitializesNilDataAndLabelsOnExistingSecret(t *testing.T) { client := &Client{Clientset: k8sfake.NewSimpleClientset()} if _, err := client.Clientset.CoreV1().Secrets("atlas").Create(context.Background(), &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "restic-usage", Namespace: "atlas"}, }, metav1.CreateOptions{}); err != nil { t.Fatalf("seed secret: %v", err) } seeded, err := client.Clientset.CoreV1().Secrets("atlas").Get(context.Background(), "restic-usage", metav1.GetOptions{}) if err != nil { t.Fatalf("get seeded secret: %v", err) } seeded.ResourceVersion = "1" if _, err := client.Clientset.CoreV1().Secrets("atlas").Update(context.Background(), seeded, metav1.UpdateOptions{}); err != nil { t.Fatalf("prime seeded secret resource version: %v", err) } if err := client.SaveSecretData(context.Background(), "atlas", "restic-usage", "usage.json", []byte("value"), map[string]string{"app": "soteria"}); err != nil { t.Fatalf("save secret data with nil data/labels: %v", err) } updated, err := client.Clientset.CoreV1().Secrets("atlas").Get(context.Background(), "restic-usage", metav1.GetOptions{}) if err != nil { t.Fatalf("get updated secret: %v", err) } if string(updated.Data["usage.json"]) != "value" { t.Fatalf("expected updated secret payload, got %#v", updated.Data) } if updated.Labels["app"] != "soteria" { t.Fatalf("expected labels to be initialized, got %#v", updated.Labels) } } func TestSaveSecretDataWrapsGetAndWriteErrors(t *testing.T) { t.Run("get error", func(t *testing.T) { clientset := k8sfake.NewSimpleClientset() clientset.PrependReactor("get", "secrets", func(action k8stesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("get exploded") }) client := &Client{Clientset: clientset} if err := client.SaveSecretData(context.Background(), "atlas", "restic-usage", "usage.json", []byte("value"), nil); err == nil || err.Error() == "get exploded" { t.Fatalf("expected wrapped get error, got %v", err) } }) t.Run("create error", func(t *testing.T) { clientset := k8sfake.NewSimpleClientset() clientset.PrependReactor("get", "secrets", func(action k8stesting.Action) (bool, runtime.Object, error) { return true, nil, apierrors.NewNotFound(corev1.Resource("secrets"), "restic-usage") }) clientset.PrependReactor("create", "secrets", func(action k8stesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("create exploded") }) client := &Client{Clientset: clientset} if err := client.SaveSecretData(context.Background(), "atlas", "restic-usage", "usage.json", []byte("value"), nil); err == nil || err.Error() == "create exploded" { t.Fatalf("expected wrapped create error, got %v", err) } }) t.Run("update error", func(t *testing.T) { clientset := k8sfake.NewSimpleClientset(&corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "restic-usage", Namespace: "atlas"}, Data: map[string][]byte{}, }) clientset.PrependReactor("update", "secrets", func(action k8stesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("update exploded") }) client := &Client{Clientset: clientset} if err := client.SaveSecretData(context.Background(), "atlas", "restic-usage", "usage.json", []byte("value"), nil); err == nil || err.Error() == "update exploded" { t.Fatalf("expected wrapped update error, got %v", err) } }) }