Add some tests for both the API Resource Controller and the Deployment Controller

This commit is contained in:
Eddy Filip
2022-09-13 16:34:36 +03:00
parent 5e496d2e77
commit 87ff93daad
4 changed files with 478 additions and 3 deletions

View File

@@ -46,6 +46,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- apps
resources:
- deployments
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
@@ -53,6 +65,12 @@ rules:
- replicasets
verbs:
- get
- apiGroups:
- apps
resources:
- deployments/finalizers
verbs:
- update
- apiGroups:
- apps
resourceNames:
@@ -61,6 +79,14 @@ rules:
- deployments/finalizers
verbs:
- update
- apiGroups:
- apps
resources:
- deployments/status
verbs:
- get
- patch
- update
- apiGroups:
- monitoring.coreos.com
resources:

View File

@@ -0,0 +1,131 @@
package controllers
import (
"context"
"github.com/1Password/connect-sdk-go/onepassword"
"github.com/1Password/onepassword-operator/pkg/mocks"
op "github.com/1Password/onepassword-operator/pkg/onepassword"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
onepasswordv1 "github.com/1Password/onepassword-operator/api/v1"
)
var _ = Describe("Deployment controller", func() {
const (
deploymentKind = "Deployment"
deploymentAPIVersion = "v1"
deploymentName = "test-deployment"
)
BeforeEach(func() {
// failed test runs that don't clean up leave resources behind.
k8sClient.DeleteAllOf(context.Background(), &onepasswordv1.OnePasswordItem{}, client.InNamespace(namespace))
k8sClient.DeleteAllOf(context.Background(), &v1.Secret{}, client.InNamespace(namespace))
k8sClient.DeleteAllOf(context.Background(), &appsv1.Deployment{}, client.InNamespace(namespace))
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
item := onepassword.Item{}
item.Fields = []*onepassword.ItemField{}
for k, v := range itemData {
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
}
item.Version = version
item.Vault.ID = vaultUUID
item.ID = uuid
return &item, nil
}
})
Context("Deployment with secrets from 1Password", func() {
It("Should Handle a deployment correctly", func() {
ctx := context.Background()
deploymentKey := types.NamespacedName{
Name: deploymentName,
Namespace: namespace,
}
secretKey := types.NamespacedName{
Name: ItemName,
Namespace: namespace,
}
By("Deploying a pod with proper annotations successfully")
deploymentResource := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: deploymentKind,
APIVersion: deploymentAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: deploymentKey.Name,
Namespace: deploymentKey.Namespace,
Annotations: map[string]string{
op.ItemPathAnnotation: itemPath,
op.NameAnnotation: ItemName,
},
},
Spec: appsv1.DeploymentSpec{
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"app": deploymentName},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: deploymentName,
Image: "eu.gcr.io/kyma-project/example/http-db-service:0.0.6",
ImagePullPolicy: "IfNotPresent",
},
},
},
},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": deploymentName},
},
},
}
Expect(k8sClient.Create(ctx, deploymentResource)).Should(Succeed())
By("Creating the K8s secret successfully")
createdSecret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, secretKey, createdSecret)
if err != nil {
return false
}
return true
}, timeout, interval).Should(BeTrue())
Expect(createdSecret.Data).Should(Equal(expectedSecretData))
By("Deleting the pod")
Eventually(func() error {
f := &appsv1.Deployment{}
err := k8sClient.Get(ctx, deploymentKey, f)
if err != nil {
return err
}
return k8sClient.Delete(ctx, f)
}, timeout, interval).Should(Succeed())
Eventually(func() error {
f := &appsv1.Deployment{}
return k8sClient.Get(ctx, deploymentKey, f)
}, timeout, interval).ShouldNot(Succeed())
Eventually(func() error {
f := &v1.Secret{}
return k8sClient.Get(ctx, secretKey, f)
}, timeout, interval).ShouldNot(Succeed())
})
})
})

View File

@@ -0,0 +1,237 @@
package controllers
import (
"context"
"github.com/1Password/connect-sdk-go/onepassword"
"github.com/1Password/onepassword-operator/pkg/mocks"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
onepasswordv1 "github.com/1Password/onepassword-operator/api/v1"
)
const (
firstHost = "http://localhost:8080"
awsKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
iceCream = "freezing blue 20%"
)
var _ = Describe("OnePasswordItem controller", func() {
BeforeEach(func() {
// failed test runs that don't clean up leave resources behind.
k8sClient.DeleteAllOf(context.Background(), &onepasswordv1.OnePasswordItem{}, client.InNamespace(namespace))
k8sClient.DeleteAllOf(context.Background(), &v1.Secret{}, client.InNamespace(namespace))
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
item := onepassword.Item{}
item.Fields = []*onepassword.ItemField{}
for k, v := range itemData {
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
}
item.Version = version
item.Vault.ID = vaultUUID
item.ID = uuid
return &item, nil
}
})
Context("Happy path", func() {
It("Should handle 1Password Item and secret correctly", func() {
ctx := context.Background()
spec := onepasswordv1.OnePasswordItemSpec{
ItemPath: itemPath,
}
key := types.NamespacedName{
Name: ItemName,
Namespace: namespace,
}
toCreate := &onepasswordv1.OnePasswordItem{
ObjectMeta: metav1.ObjectMeta{
Name: key.Name,
Namespace: key.Namespace,
},
Spec: spec,
}
By("Creating a new OnePasswordItem successfully")
Expect(k8sClient.Create(ctx, toCreate)).Should(Succeed())
created := &onepasswordv1.OnePasswordItem{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, created)
if err != nil {
return false
}
return true
}, timeout, interval).Should(BeTrue())
By("Creating the K8s secret successfully")
createdSecret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, createdSecret)
if err != nil {
return false
}
return true
}, timeout, interval).Should(BeTrue())
Expect(createdSecret.Data).Should(Equal(expectedSecretData))
By("Updating existing secret successfully")
newData := map[string]string{
"username": "newUser1234",
"password": "##newPassword##",
"extraField": "dev",
}
newDataByte := map[string][]byte{
"username": []byte("newUser1234"),
"password": []byte("##newPassword##"),
"extraField": []byte("dev"),
}
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
item := onepassword.Item{}
item.Fields = []*onepassword.ItemField{}
for k, v := range newData {
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
}
item.Version = version + 1
item.Vault.ID = vaultUUID
item.ID = uuid
return &item, nil
}
_, err := onePasswordItemReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: key})
Expect(err).ToNot(HaveOccurred())
updatedSecret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, updatedSecret)
if err != nil {
return false
}
return true
}, timeout, interval).Should(BeTrue())
Expect(updatedSecret.Data).Should(Equal(newDataByte))
By("Deleting the OnePasswordItem successfully")
Eventually(func() error {
f := &onepasswordv1.OnePasswordItem{}
err := k8sClient.Get(ctx, key, f)
if err != nil {
return err
}
return k8sClient.Delete(ctx, f)
}, timeout, interval).Should(Succeed())
Eventually(func() error {
f := &onepasswordv1.OnePasswordItem{}
return k8sClient.Get(ctx, key, f)
}, timeout, interval).ShouldNot(Succeed())
Eventually(func() error {
f := &v1.Secret{}
return k8sClient.Get(ctx, key, f)
}, timeout, interval).ShouldNot(Succeed())
})
It("Should handle 1Password Item with fields and sections that have invalid K8s labels correctly", func() {
ctx := context.Background()
spec := onepasswordv1.OnePasswordItemSpec{
ItemPath: itemPath,
}
key := types.NamespacedName{
Name: "my-secret-it3m",
Namespace: namespace,
}
toCreate := &onepasswordv1.OnePasswordItem{
ObjectMeta: metav1.ObjectMeta{
Name: "my-secret-it3m",
Namespace: key.Namespace,
},
Spec: spec,
}
testData := map[string]string{
"username": username,
"password": password,
"first host": firstHost,
"AWS Access Key": awsKey,
"😄 ice-cream type": iceCream,
}
expectedData := map[string][]byte{
"username": []byte(username),
"password": []byte(password),
"first-host": []byte(firstHost),
"AWS-Access-Key": []byte(awsKey),
"ice-cream-type": []byte(iceCream),
}
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
item := onepassword.Item{}
item.Title = "!my sECReT it3m%"
item.Fields = []*onepassword.ItemField{}
for k, v := range testData {
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
}
item.Version = version + 1
item.Vault.ID = vaultUUID
item.ID = uuid
return &item, nil
}
By("Creating a new OnePasswordItem successfully")
Expect(k8sClient.Create(ctx, toCreate)).Should(Succeed())
created := &onepasswordv1.OnePasswordItem{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, created)
if err != nil {
return false
}
return true
}, timeout, interval).Should(BeTrue())
By("Creating the K8s secret successfully")
createdSecret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, createdSecret)
if err != nil {
return false
}
return true
}, timeout, interval).Should(BeTrue())
Expect(createdSecret.Data).Should(Equal(expectedData))
By("Deleting the OnePasswordItem successfully")
Eventually(func() error {
f := &onepasswordv1.OnePasswordItem{}
err := k8sClient.Get(ctx, key, f)
if err != nil {
return err
}
return k8sClient.Delete(ctx, f)
}, timeout, interval).Should(Succeed())
Eventually(func() error {
f := &onepasswordv1.OnePasswordItem{}
return k8sClient.Get(ctx, key, f)
}, timeout, interval).ShouldNot(Succeed())
Eventually(func() error {
f := &v1.Secret{}
return k8sClient.Get(ctx, key, f)
}, timeout, interval).ShouldNot(Succeed())
})
})
})

View File

@@ -25,14 +25,21 @@ SOFTWARE.
package controllers
import (
"context"
"fmt"
"path/filepath"
"regexp"
"testing"
"time"
"github.com/1Password/onepassword-operator/pkg/mocks"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
@@ -45,9 +52,49 @@ import (
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
var (
cfg *rest.Config
k8sClient client.Client
testEnv *envtest.Environment
ctx context.Context
cancel context.CancelFunc
itemData = map[string]string{
"username": username,
"password": password,
}
)
const (
vaultId = "hfnjvi6aymbsnfc2xeeoheizda"
itemId = "nwrhuano7bcwddcviubpp4mhfq"
username = "test-user"
password = "QmHumKc$mUeEem7caHtbaBaJ"
version = 123
annotationRegExpString = "^operator.1password.io\\/[a-zA-Z\\.]+"
)
// Define utility constants for object names and testing timeouts/durations and intervals.
const (
namespace = "default"
ItemName = "test-item"
timeout = time.Second * 10
duration = time.Second * 10
interval = time.Millisecond * 250
)
var (
onePasswordItemReconciler *OnePasswordItemReconciler
deploymentReconciler *DeploymentReconciler
itemPath = fmt.Sprintf("vaults/%v/items/%v", vaultId, itemId)
expectedSecretData = map[string][]byte{
"password": []byte(password),
"username": []byte(username),
}
)
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
@@ -58,6 +105,8 @@ func TestAPIs(t *testing.T) {
var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
ctx, cancel = context.WithCancel(context.TODO())
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
@@ -79,9 +128,41 @@ var _ = BeforeSuite(func() {
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme.Scheme,
})
Expect(err).ToNot(HaveOccurred())
opConnectClient := &mocks.TestClient{}
onePasswordItemReconciler = &OnePasswordItemReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
OpConnectClient: opConnectClient,
}
err = (onePasswordItemReconciler).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())
r, _ := regexp.Compile(annotationRegExpString)
deploymentReconciler = &DeploymentReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
OpConnectClient: opConnectClient,
OpAnnotationRegExp: r,
}
err = (deploymentReconciler).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())
go func() {
defer GinkgoRecover()
err = k8sManager.Start(ctx)
Expect(err).ToNot(HaveOccurred(), "failed to run manager")
}()
})
var _ = AfterSuite(func() {
cancel()
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())