mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-22 07:28:06 +00:00
Merge pull request #126 from 1Password/feature/controllers_tests
Feature/controllers tests
This commit is contained in:
@@ -2,10 +2,10 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/1Password/connect-sdk-go/onepassword"
|
"github.com/1Password/connect-sdk-go/onepassword"
|
||||||
"github.com/1Password/onepassword-operator/pkg/mocks"
|
"github.com/1Password/onepassword-operator/pkg/mocks"
|
||||||
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
op "github.com/1Password/onepassword-operator/pkg/onepassword"
|
||||||
|
"time"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
@@ -19,100 +19,115 @@ import (
|
|||||||
onepasswordv1 "github.com/1Password/onepassword-operator/api/v1"
|
onepasswordv1 "github.com/1Password/onepassword-operator/api/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
deploymentKind = "Deployment"
|
||||||
|
deploymentAPIVersion = "v1"
|
||||||
|
deploymentName = "test-deployment"
|
||||||
|
)
|
||||||
|
|
||||||
var _ = Describe("Deployment controller", func() {
|
var _ = Describe("Deployment controller", func() {
|
||||||
const (
|
var ctx context.Context
|
||||||
deploymentKind = "Deployment"
|
var deploymentKey types.NamespacedName
|
||||||
deploymentAPIVersion = "v1"
|
var secretKey types.NamespacedName
|
||||||
deploymentName = "test-deployment"
|
var deploymentResource *appsv1.Deployment
|
||||||
)
|
createdSecret := &v1.Secret{}
|
||||||
|
|
||||||
BeforeEach(func() {
|
makeDeployment := func() {
|
||||||
|
ctx = context.Background()
|
||||||
|
|
||||||
|
deploymentKey = types.NamespacedName{
|
||||||
|
Name: deploymentName,
|
||||||
|
Namespace: namespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
secretKey = types.NamespacedName{
|
||||||
|
Name: item1.Name,
|
||||||
|
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: item1.Path,
|
||||||
|
op.NameAnnotation: item1.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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")
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
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(item1.SecretData))
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanK8sResources := func() {
|
||||||
// failed test runs that don't clean up leave resources behind.
|
// failed test runs that don't clean up leave resources behind.
|
||||||
k8sClient.DeleteAllOf(context.Background(), &onepasswordv1.OnePasswordItem{}, client.InNamespace(namespace))
|
err := k8sClient.DeleteAllOf(context.Background(), &onepasswordv1.OnePasswordItem{}, client.InNamespace(namespace))
|
||||||
k8sClient.DeleteAllOf(context.Background(), &v1.Secret{}, client.InNamespace(namespace))
|
Expect(err).ToNot(HaveOccurred())
|
||||||
k8sClient.DeleteAllOf(context.Background(), &appsv1.Deployment{}, client.InNamespace(namespace))
|
|
||||||
|
|
||||||
|
err = k8sClient.DeleteAllOf(context.Background(), &v1.Secret{}, client.InNamespace(namespace))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = k8sClient.DeleteAllOf(context.Background(), &appsv1.Deployment{}, client.InNamespace(namespace))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
mockGetItemFunc := func() {
|
||||||
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||||
|
|
||||||
item := onepassword.Item{}
|
item := onepassword.Item{}
|
||||||
item.Fields = []*onepassword.ItemField{}
|
item.Fields = []*onepassword.ItemField{}
|
||||||
for k, v := range itemData {
|
for k, v := range item1.Data {
|
||||||
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||||
}
|
}
|
||||||
item.Version = version
|
item.Version = item1.Version
|
||||||
item.Vault.ID = vaultUUID
|
item.Vault.ID = vaultUUID
|
||||||
item.ID = uuid
|
item.ID = uuid
|
||||||
return &item, nil
|
return &item, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
cleanK8sResources()
|
||||||
|
mockGetItemFunc()
|
||||||
|
time.Sleep(time.Second) // TODO: can we achieve that with ginkgo?
|
||||||
|
makeDeployment()
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Implement the following test cases:
|
|
||||||
// - Updating Existing K8s Secret using Deployment
|
|
||||||
// - Do not update if Annotations have not changed
|
|
||||||
// - Delete Deployment where secret is being used in another deployment's container
|
|
||||||
// - Delete Deployment where secret is being used in another deployment's volumes
|
|
||||||
|
|
||||||
Context("Deployment with secrets from 1Password", func() {
|
Context("Deployment with secrets from 1Password", func() {
|
||||||
It("Should Handle a deployment correctly", func() {
|
It("Should delete secret if deployment is deleted", 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")
|
By("Deleting the pod")
|
||||||
Eventually(func() error {
|
Eventually(func() error {
|
||||||
f := &appsv1.Deployment{}
|
f := &appsv1.Deployment{}
|
||||||
@@ -133,5 +148,271 @@ var _ = Describe("Deployment controller", func() {
|
|||||||
return k8sClient.Get(ctx, secretKey, f)
|
return k8sClient.Get(ctx, secretKey, f)
|
||||||
}, timeout, interval).ShouldNot(Succeed())
|
}, timeout, interval).ShouldNot(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("Should update existing K8s Secret using deployment", func() {
|
||||||
|
By("Updating secret")
|
||||||
|
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||||
|
item := onepassword.Item{}
|
||||||
|
item.Fields = []*onepassword.ItemField{}
|
||||||
|
for k, v := range item2.Data {
|
||||||
|
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||||
|
}
|
||||||
|
item.Version = item2.Version
|
||||||
|
item.Vault.ID = vaultUUID
|
||||||
|
item.ID = uuid
|
||||||
|
return &item, nil
|
||||||
|
}
|
||||||
|
Eventually(func() error {
|
||||||
|
updatedDeployment := &appsv1.Deployment{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: deploymentKind,
|
||||||
|
APIVersion: deploymentAPIVersion,
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: deploymentKey.Name,
|
||||||
|
Namespace: deploymentKey.Namespace,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
op.ItemPathAnnotation: item2.Path,
|
||||||
|
op.NameAnnotation: item1.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := k8sClient.Update(ctx, updatedDeployment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
|
||||||
|
// TODO: can we achieve the same without sleep?
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
By("Reading updated K8s secret")
|
||||||
|
updatedSecret := &v1.Secret{}
|
||||||
|
Eventually(func() bool {
|
||||||
|
err := k8sClient.Get(ctx, secretKey, updatedSecret)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, timeout, interval).Should(BeTrue())
|
||||||
|
Expect(updatedSecret.Data).Should(Equal(item2.SecretData))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should not update secret if Annotations have not changed", func() {
|
||||||
|
By("Updating secret without changing annotations")
|
||||||
|
Eventually(func() error {
|
||||||
|
updatedDeployment := &appsv1.Deployment{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: deploymentKind,
|
||||||
|
APIVersion: deploymentAPIVersion,
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: deploymentKey.Name,
|
||||||
|
Namespace: deploymentKey.Namespace,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
op.ItemPathAnnotation: item1.Path,
|
||||||
|
op.NameAnnotation: item1.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := k8sClient.Update(ctx, updatedDeployment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
|
||||||
|
// TODO: can we achieve the same without sleep?
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
By("Reading updated K8s secret")
|
||||||
|
updatedSecret := &v1.Secret{}
|
||||||
|
Eventually(func() bool {
|
||||||
|
err := k8sClient.Get(ctx, secretKey, updatedSecret)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, timeout, interval).Should(BeTrue())
|
||||||
|
Expect(updatedSecret.Data).Should(Equal(item1.SecretData))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should not delete secret created via deployment if it's used in another container", func() {
|
||||||
|
By("Creating another POD with created secret")
|
||||||
|
anotherDeploymentKey := types.NamespacedName{
|
||||||
|
Name: "other-deployment",
|
||||||
|
Namespace: namespace,
|
||||||
|
}
|
||||||
|
Eventually(func() error {
|
||||||
|
anotherDeployment := &appsv1.Deployment{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: deploymentKind,
|
||||||
|
APIVersion: deploymentAPIVersion,
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: anotherDeploymentKey.Name,
|
||||||
|
Namespace: anotherDeploymentKey.Namespace,
|
||||||
|
},
|
||||||
|
Spec: appsv1.DeploymentSpec{
|
||||||
|
Template: v1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: map[string]string{"app": anotherDeploymentKey.Name},
|
||||||
|
},
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Name: anotherDeploymentKey.Name,
|
||||||
|
Image: "eu.gcr.io/kyma-project/example/http-db-service:0.0.6",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
Env: []v1.EnvVar{
|
||||||
|
{
|
||||||
|
Name: anotherDeploymentKey.Name,
|
||||||
|
ValueFrom: &v1.EnvVarSource{
|
||||||
|
SecretKeyRef: &v1.SecretKeySelector{
|
||||||
|
LocalObjectReference: v1.LocalObjectReference{
|
||||||
|
Name: secretKey.Name,
|
||||||
|
},
|
||||||
|
Key: "password",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Selector: &metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"app": anotherDeploymentKey.Name},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := k8sClient.Create(ctx, anotherDeployment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
|
||||||
|
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 := &v1.Secret{}
|
||||||
|
return k8sClient.Get(ctx, secretKey, f)
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should not delete secret created via deployment if it's used in another volume", func() {
|
||||||
|
By("Creating another POD with created secret")
|
||||||
|
anotherDeploymentKey := types.NamespacedName{
|
||||||
|
Name: "other-deployment",
|
||||||
|
Namespace: namespace,
|
||||||
|
}
|
||||||
|
Eventually(func() error {
|
||||||
|
anotherDeployment := &appsv1.Deployment{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: deploymentKind,
|
||||||
|
APIVersion: deploymentAPIVersion,
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: anotherDeploymentKey.Name,
|
||||||
|
Namespace: anotherDeploymentKey.Namespace,
|
||||||
|
},
|
||||||
|
Spec: appsv1.DeploymentSpec{
|
||||||
|
Template: v1.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: map[string]string{"app": anotherDeploymentKey.Name},
|
||||||
|
},
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Volumes: []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: anotherDeploymentKey.Name,
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
Secret: &v1.SecretVolumeSource{
|
||||||
|
SecretName: secretKey.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Name: anotherDeploymentKey.Name,
|
||||||
|
Image: "eu.gcr.io/kyma-project/example/http-db-service:0.0.6",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Selector: &metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"app": anotherDeploymentKey.Name},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := k8sClient.Create(ctx, anotherDeployment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
|
||||||
|
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 := &v1.Secret{}
|
||||||
|
return k8sClient.Get(ctx, secretKey, f)
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -27,39 +27,33 @@ const (
|
|||||||
var _ = Describe("OnePasswordItem controller", func() {
|
var _ = Describe("OnePasswordItem controller", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
// failed test runs that don't clean up leave resources behind.
|
// failed test runs that don't clean up leave resources behind.
|
||||||
k8sClient.DeleteAllOf(context.Background(), &onepasswordv1.OnePasswordItem{}, client.InNamespace(namespace))
|
err := k8sClient.DeleteAllOf(context.Background(), &onepasswordv1.OnePasswordItem{}, client.InNamespace(namespace))
|
||||||
k8sClient.DeleteAllOf(context.Background(), &v1.Secret{}, client.InNamespace(namespace))
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
err = k8sClient.DeleteAllOf(context.Background(), &v1.Secret{}, client.InNamespace(namespace))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
mocks.DoGetItemFunc = func(uuid string, vaultUUID string) (*onepassword.Item, error) {
|
||||||
|
|
||||||
item := onepassword.Item{}
|
item := onepassword.Item{}
|
||||||
item.Fields = []*onepassword.ItemField{}
|
item.Fields = []*onepassword.ItemField{}
|
||||||
for k, v := range itemData {
|
for k, v := range item1.Data {
|
||||||
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||||
}
|
}
|
||||||
item.Version = version
|
item.Version = item1.Version
|
||||||
item.Vault.ID = vaultUUID
|
item.Vault.ID = vaultUUID
|
||||||
item.ID = uuid
|
item.ID = uuid
|
||||||
return &item, nil
|
return &item, nil
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Implement the following missing tests:
|
|
||||||
// - K8s secret is not updated if OnePasswordItem Version or VaultPath has not changed
|
|
||||||
// - Update type of existing K8s Secret using OnePasswordItem
|
|
||||||
// - Create a custom K8s Secret type using OnePasswordItem (e.g. .dockerconfigjson)
|
|
||||||
// - Operator should throw an error if secret type is changed
|
|
||||||
// - Secret from 1Password item with `-`, `_` and `.`
|
|
||||||
|
|
||||||
Context("Happy path", func() {
|
Context("Happy path", func() {
|
||||||
It("Should handle 1Password Item and secret correctly", func() {
|
It("Should handle 1Password Item and secret correctly", func() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
spec := onepasswordv1.OnePasswordItemSpec{
|
spec := onepasswordv1.OnePasswordItemSpec{
|
||||||
ItemPath: itemPath,
|
ItemPath: item1.Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
key := types.NamespacedName{
|
key := types.NamespacedName{
|
||||||
Name: ItemName,
|
Name: item1.Name,
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +86,7 @@ var _ = Describe("OnePasswordItem controller", func() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}, timeout, interval).Should(BeTrue())
|
}, timeout, interval).Should(BeTrue())
|
||||||
Expect(createdSecret.Data).Should(Equal(expectedSecretData))
|
Expect(createdSecret.Data).Should(Equal(item1.SecretData))
|
||||||
|
|
||||||
By("Updating existing secret successfully")
|
By("Updating existing secret successfully")
|
||||||
newData := map[string]string{
|
newData := map[string]string{
|
||||||
@@ -111,7 +105,7 @@ var _ = Describe("OnePasswordItem controller", func() {
|
|||||||
for k, v := range newData {
|
for k, v := range newData {
|
||||||
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||||
}
|
}
|
||||||
item.Version = version + 1
|
item.Version = item1.Version + 1
|
||||||
item.Vault.ID = vaultUUID
|
item.Vault.ID = vaultUUID
|
||||||
item.ID = uuid
|
item.ID = uuid
|
||||||
return &item, nil
|
return &item, nil
|
||||||
@@ -153,7 +147,7 @@ var _ = Describe("OnePasswordItem controller", func() {
|
|||||||
It("Should handle 1Password Item with fields and sections that have invalid K8s labels correctly", func() {
|
It("Should handle 1Password Item with fields and sections that have invalid K8s labels correctly", func() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
spec := onepasswordv1.OnePasswordItemSpec{
|
spec := onepasswordv1.OnePasswordItemSpec{
|
||||||
ItemPath: itemPath,
|
ItemPath: item1.Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
key := types.NamespacedName{
|
key := types.NamespacedName{
|
||||||
@@ -163,7 +157,7 @@ var _ = Describe("OnePasswordItem controller", func() {
|
|||||||
|
|
||||||
toCreate := &onepasswordv1.OnePasswordItem{
|
toCreate := &onepasswordv1.OnePasswordItem{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "my-secret-it3m",
|
Name: key.Name,
|
||||||
Namespace: key.Namespace,
|
Namespace: key.Namespace,
|
||||||
},
|
},
|
||||||
Spec: spec,
|
Spec: spec,
|
||||||
@@ -191,7 +185,7 @@ var _ = Describe("OnePasswordItem controller", func() {
|
|||||||
for k, v := range testData {
|
for k, v := range testData {
|
||||||
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
item.Fields = append(item.Fields, &onepassword.ItemField{Label: k, Value: v})
|
||||||
}
|
}
|
||||||
item.Version = version + 1
|
item.Version = item1.Version + 1
|
||||||
item.Vault.ID = vaultUUID
|
item.Vault.ID = vaultUUID
|
||||||
item.ID = uuid
|
item.ID = uuid
|
||||||
return &item, nil
|
return &item, nil
|
||||||
@@ -240,5 +234,193 @@ var _ = Describe("OnePasswordItem controller", func() {
|
|||||||
return k8sClient.Get(ctx, key, f)
|
return k8sClient.Get(ctx, key, f)
|
||||||
}, timeout, interval).ShouldNot(Succeed())
|
}, timeout, interval).ShouldNot(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("Should not update K8s secret if OnePasswordItem Version or VaultPath has not changed", func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
spec := onepasswordv1.OnePasswordItemSpec{
|
||||||
|
ItemPath: item1.Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
key := types.NamespacedName{
|
||||||
|
Name: item1.Name,
|
||||||
|
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())
|
||||||
|
|
||||||
|
item := &onepasswordv1.OnePasswordItem{}
|
||||||
|
Eventually(func() bool {
|
||||||
|
err := k8sClient.Get(ctx, key, item)
|
||||||
|
return err == nil
|
||||||
|
}, timeout, interval).Should(BeTrue())
|
||||||
|
|
||||||
|
By("Creating the K8s secret successfully")
|
||||||
|
createdSecret := &v1.Secret{}
|
||||||
|
Eventually(func() bool {
|
||||||
|
err := k8sClient.Get(ctx, key, createdSecret)
|
||||||
|
return err == nil
|
||||||
|
}, timeout, interval).Should(BeTrue())
|
||||||
|
Expect(createdSecret.Data).Should(Equal(item1.SecretData))
|
||||||
|
|
||||||
|
By("Updating OnePasswordItem type")
|
||||||
|
Eventually(func() bool {
|
||||||
|
err1 := k8sClient.Get(ctx, key, item)
|
||||||
|
if err1 != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
item.Type = string(v1.SecretTypeOpaque)
|
||||||
|
err := k8sClient.Update(ctx, item)
|
||||||
|
return err == nil
|
||||||
|
}, timeout, interval).Should(BeTrue())
|
||||||
|
|
||||||
|
By("Reading K8s secret")
|
||||||
|
secret := &v1.Secret{}
|
||||||
|
Eventually(func() bool {
|
||||||
|
err := k8sClient.Get(ctx, key, secret)
|
||||||
|
return err == nil
|
||||||
|
}, timeout, interval).Should(BeTrue())
|
||||||
|
Expect(secret.Data).Should(Equal(item1.SecretData))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should create custom K8s Secret type using OnePasswordItem", func() {
|
||||||
|
const customType = "CustomType"
|
||||||
|
ctx := context.Background()
|
||||||
|
spec := onepasswordv1.OnePasswordItemSpec{
|
||||||
|
ItemPath: item1.Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
key := types.NamespacedName{
|
||||||
|
Name: "test6",
|
||||||
|
Namespace: namespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
toCreate := &onepasswordv1.OnePasswordItem{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: key.Name,
|
||||||
|
Namespace: key.Namespace,
|
||||||
|
},
|
||||||
|
Spec: spec,
|
||||||
|
Type: customType,
|
||||||
|
}
|
||||||
|
|
||||||
|
By("Creating a new OnePasswordItem successfully")
|
||||||
|
Expect(k8sClient.Create(ctx, toCreate)).Should(Succeed())
|
||||||
|
|
||||||
|
By("Reading K8s secret")
|
||||||
|
secret := &v1.Secret{}
|
||||||
|
Eventually(func() bool {
|
||||||
|
err := k8sClient.Get(ctx, key, secret)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, timeout, interval).Should(BeTrue())
|
||||||
|
Expect(secret.Type).Should(Equal(v1.SecretType(customType)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Unhappy path", func() {
|
||||||
|
It("Should throw an error if K8s Secret type is changed", func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
spec := onepasswordv1.OnePasswordItemSpec{
|
||||||
|
ItemPath: item1.Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
key := types.NamespacedName{
|
||||||
|
Name: "test7",
|
||||||
|
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())
|
||||||
|
|
||||||
|
By("Reading K8s secret")
|
||||||
|
secret := &v1.Secret{}
|
||||||
|
Eventually(func() bool {
|
||||||
|
err := k8sClient.Get(ctx, key, secret)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, timeout, interval).Should(BeTrue())
|
||||||
|
|
||||||
|
By("Failing to update K8s secret")
|
||||||
|
Eventually(func() bool {
|
||||||
|
secret.Type = v1.SecretTypeBasicAuth
|
||||||
|
err := k8sClient.Update(ctx, secret)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, timeout, interval).Should(BeFalse())
|
||||||
|
})
|
||||||
|
|
||||||
|
When("OnePasswordItem resource name contains `_`", func() {
|
||||||
|
It("Should fail creating a OnePasswordItem resource", func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
spec := onepasswordv1.OnePasswordItemSpec{
|
||||||
|
ItemPath: item1.Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
key := types.NamespacedName{
|
||||||
|
Name: "invalid_name",
|
||||||
|
Namespace: namespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
toCreate := &onepasswordv1.OnePasswordItem{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: key.Name,
|
||||||
|
Namespace: key.Namespace,
|
||||||
|
},
|
||||||
|
Spec: spec,
|
||||||
|
}
|
||||||
|
|
||||||
|
By("Creating a new OnePasswordItem")
|
||||||
|
Expect(k8sClient.Create(ctx, toCreate)).To(HaveOccurred())
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("OnePasswordItem resource name contains capital letters", func() {
|
||||||
|
It("Should fail creating a OnePasswordItem resource", func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
spec := onepasswordv1.OnePasswordItemSpec{
|
||||||
|
ItemPath: item1.Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
key := types.NamespacedName{
|
||||||
|
Name: "invalidName",
|
||||||
|
Namespace: namespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
toCreate := &onepasswordv1.OnePasswordItem{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: key.Name,
|
||||||
|
Namespace: key.Namespace,
|
||||||
|
},
|
||||||
|
Spec: spec,
|
||||||
|
}
|
||||||
|
|
||||||
|
By("Creating a new OnePasswordItem")
|
||||||
|
Expect(k8sClient.Create(ctx, toCreate)).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -26,7 +26,6 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -52,25 +51,12 @@ import (
|
|||||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||||
|
|
||||||
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 (
|
const (
|
||||||
vaultId = "hfnjvi6aymbsnfc2xeeoheizda"
|
|
||||||
itemId = "nwrhuano7bcwddcviubpp4mhfq"
|
|
||||||
username = "test-user"
|
username = "test-user"
|
||||||
password = "QmHumKc$mUeEem7caHtbaBaJ"
|
password = "QmHumKc$mUeEem7caHtbaBaJ"
|
||||||
version = 123
|
|
||||||
|
username2 = "test-user2"
|
||||||
|
password2 = "4zotzqDqXKasLFT2jzTs"
|
||||||
|
|
||||||
annotationRegExpString = "^operator.1password.io\\/[a-zA-Z\\.]+"
|
annotationRegExpString = "^operator.1password.io\\/[a-zA-Z\\.]+"
|
||||||
)
|
)
|
||||||
@@ -78,7 +64,6 @@ const (
|
|||||||
// Define utility constants for object names and testing timeouts/durations and intervals.
|
// Define utility constants for object names and testing timeouts/durations and intervals.
|
||||||
const (
|
const (
|
||||||
namespace = "default"
|
namespace = "default"
|
||||||
ItemName = "test-item"
|
|
||||||
|
|
||||||
timeout = time.Second * 10
|
timeout = time.Second * 10
|
||||||
duration = time.Second * 10
|
duration = time.Second * 10
|
||||||
@@ -86,16 +71,51 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
cfg *rest.Config
|
||||||
|
k8sClient client.Client
|
||||||
|
testEnv *envtest.Environment
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
onePasswordItemReconciler *OnePasswordItemReconciler
|
onePasswordItemReconciler *OnePasswordItemReconciler
|
||||||
deploymentReconciler *DeploymentReconciler
|
deploymentReconciler *DeploymentReconciler
|
||||||
|
|
||||||
itemPath = fmt.Sprintf("vaults/%v/items/%v", vaultId, itemId)
|
item1 = &TestItem{
|
||||||
expectedSecretData = map[string][]byte{
|
Name: "test-item",
|
||||||
"password": []byte(password),
|
Version: 123,
|
||||||
"username": []byte(username),
|
Path: "vaults/hfnjvi6aymbsnfc2xeeoheizda/items/nwrhuano7bcwddcviubpp4mhfq",
|
||||||
|
Data: map[string]string{
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
},
|
||||||
|
SecretData: map[string][]byte{
|
||||||
|
"password": []byte(password),
|
||||||
|
"username": []byte(username),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
item2 = &TestItem{
|
||||||
|
Name: "test-item2",
|
||||||
|
Path: "vaults/hfnjvi6aymbsnfc2xeeoheizd2/items/nwrhuano7bcwddcviubpp4mhf2",
|
||||||
|
Version: 456,
|
||||||
|
Data: map[string]string{
|
||||||
|
"username": username2,
|
||||||
|
"password": password2,
|
||||||
|
},
|
||||||
|
SecretData: map[string][]byte{
|
||||||
|
"password": []byte(password2),
|
||||||
|
"username": []byte(username2),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TestItem struct {
|
||||||
|
Name string
|
||||||
|
Version int
|
||||||
|
Path string
|
||||||
|
Data map[string]string
|
||||||
|
SecretData map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIs(t *testing.T) {
|
func TestAPIs(t *testing.T) {
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user