mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-21 15:08:06 +00:00
Add functions to testhelper package
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/yaml"
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
@@ -43,9 +44,10 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Kube struct {
|
type Kube struct {
|
||||||
Config *Config
|
Config *Config
|
||||||
Client client.Client
|
Client client.Client
|
||||||
Mapper meta.RESTMapper
|
Clientset kubernetes.Interface
|
||||||
|
Mapper meta.RESTMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKubeClient(config *Config) *Kube {
|
func NewKubeClient(config *Config) *Kube {
|
||||||
@@ -79,6 +81,10 @@ func NewKubeClient(config *Config) *Kube {
|
|||||||
})
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Create Kubernetes clientset for logs and other operations
|
||||||
|
clientset, err := kubernetes.NewForConfig(restConfig)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
// update the current context’s namespace in kubeconfig
|
// update the current context’s namespace in kubeconfig
|
||||||
pathOpts := clientcmd.NewDefaultPathOptions()
|
pathOpts := clientcmd.NewDefaultPathOptions()
|
||||||
cfg, err := pathOpts.GetStartingConfig()
|
cfg, err := pathOpts.GetStartingConfig()
|
||||||
@@ -95,9 +101,10 @@ func NewKubeClient(config *Config) *Kube {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
return &Kube{
|
return &Kube{
|
||||||
Config: config,
|
Config: config,
|
||||||
Client: kubernetesClient,
|
Client: kubernetesClient,
|
||||||
Mapper: rm,
|
Clientset: clientset,
|
||||||
|
Mapper: rm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,9 +126,10 @@ func (k *Kube) Deployment(name string) *Deployment {
|
|||||||
|
|
||||||
func (k *Kube) Pod(selector map[string]string) *Pod {
|
func (k *Kube) Pod(selector map[string]string) *Pod {
|
||||||
return &Pod{
|
return &Pod{
|
||||||
client: k.Client,
|
client: k.Client,
|
||||||
config: k.Config,
|
clientset: k.Clientset,
|
||||||
selector: selector,
|
config: k.Config,
|
||||||
|
selector: selector,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,8 +141,16 @@ func (k *Kube) Namespace(name string) *Namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyOnePasswordItem applies a OnePasswordItem manifest.
|
func (k *Kube) Webhook(name string) *Webhook {
|
||||||
func (k *Kube) ApplyOnePasswordItem(ctx context.Context, fileName string) {
|
return &Webhook{
|
||||||
|
client: k.Client,
|
||||||
|
config: k.Config,
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a Kubernetes manifest file using server-side apply.
|
||||||
|
func (k *Kube) Apply(ctx context.Context, fileName string) {
|
||||||
By("Applying " + fileName)
|
By("Applying " + fileName)
|
||||||
|
|
||||||
// Derive a short-lived context so this API call won't hang indefinitely.
|
// Derive a short-lived context so this API call won't hang indefinitely.
|
||||||
|
@@ -2,6 +2,7 @@ package kube
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
//nolint:staticcheck // ST1001
|
//nolint:staticcheck // ST1001
|
||||||
@@ -10,13 +11,15 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Pod struct {
|
type Pod struct {
|
||||||
client client.Client
|
client client.Client
|
||||||
config *Config
|
clientset kubernetes.Interface
|
||||||
selector map[string]string
|
config *Config
|
||||||
|
selector map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pod) WaitingForRunningPod(ctx context.Context) {
|
func (p *Pod) WaitingForRunningPod(ctx context.Context) {
|
||||||
@@ -45,3 +48,90 @@ func (p *Pod) WaitingForRunningPod(ctx context.Context) {
|
|||||||
g.Expect(foundRunning).To(BeTrue(), "pod not Running yet")
|
g.Expect(foundRunning).To(BeTrue(), "pod not Running yet")
|
||||||
}, p.config.TestConfig.Timeout, p.config.TestConfig.Interval).Should(Succeed())
|
}, p.config.TestConfig.Timeout, p.config.TestConfig.Interval).Should(Succeed())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Pod) GetPodLogs(ctx context.Context) string {
|
||||||
|
// First find the pod by label selector
|
||||||
|
var pods corev1.PodList
|
||||||
|
listOpts := []client.ListOption{
|
||||||
|
client.InNamespace(p.config.Namespace),
|
||||||
|
client.MatchingLabels(p.selector),
|
||||||
|
}
|
||||||
|
err := p.client.List(ctx, &pods, listOpts...)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(pods.Items).NotTo(BeEmpty(), "no pods found with selector %q", labels.Set(p.selector).String())
|
||||||
|
|
||||||
|
// Use the first pod found
|
||||||
|
pod := pods.Items[0]
|
||||||
|
podName := pod.Name
|
||||||
|
|
||||||
|
// Verify pod is running before getting logs
|
||||||
|
Expect(pod.Status.Phase).To(Equal(corev1.PodRunning), "pod %s is not running (status: %s)", podName, pod.Status.Phase)
|
||||||
|
|
||||||
|
// Get logs using the Kubernetes clientset
|
||||||
|
req := p.clientset.CoreV1().Pods(p.config.Namespace).GetLogs(podName, &corev1.PodLogOptions{})
|
||||||
|
stream, err := req.Stream(context.TODO())
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to stream logs for pod %s", podName)
|
||||||
|
defer stream.Close()
|
||||||
|
|
||||||
|
// Read all logs from the stream
|
||||||
|
logs, err := io.ReadAll(stream)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to read logs for pod %s", podName)
|
||||||
|
|
||||||
|
return string(logs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pod) VerifyWebhookInjection(ctx context.Context) {
|
||||||
|
By("Verifying webhook injection for pod with selector " + labels.Set(p.selector).String())
|
||||||
|
|
||||||
|
Eventually(func(g Gomega) {
|
||||||
|
// short per-attempt timeout to avoid hanging calls while Eventually polls
|
||||||
|
attemptCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// First find the pod by label selector
|
||||||
|
var pods corev1.PodList
|
||||||
|
listOpts := []client.ListOption{
|
||||||
|
client.InNamespace(p.config.Namespace),
|
||||||
|
client.MatchingLabels(p.selector),
|
||||||
|
}
|
||||||
|
g.Expect(p.client.List(attemptCtx, &pods, listOpts...)).To(Succeed())
|
||||||
|
g.Expect(pods.Items).NotTo(BeEmpty(), "no pods found with selector %q", labels.Set(p.selector).String())
|
||||||
|
|
||||||
|
// Use the first pod found
|
||||||
|
pod := pods.Items[0]
|
||||||
|
|
||||||
|
// Check injection status annotation
|
||||||
|
g.Expect(pod.Annotations).To(HaveKey("operator.1password.io/status"))
|
||||||
|
g.Expect(pod.Annotations["operator.1password.io/status"]).To(Equal("injected"))
|
||||||
|
|
||||||
|
// Check command was modified to use op run
|
||||||
|
if len(pod.Spec.Containers) > 0 {
|
||||||
|
container := pod.Spec.Containers[0]
|
||||||
|
g.Expect(container.Command).To(HaveLen(4))
|
||||||
|
g.Expect(container.Command[0]).To(Equal("/op/bin/op"))
|
||||||
|
g.Expect(container.Command[1]).To(Equal("run"))
|
||||||
|
g.Expect(container.Command[2]).To(Equal("--"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check init container was added
|
||||||
|
g.Expect(pod.Spec.InitContainers).To(HaveLen(1))
|
||||||
|
g.Expect(pod.Spec.InitContainers[0].Name).To(Equal("copy-op-bin"))
|
||||||
|
|
||||||
|
// Check volume mount was added
|
||||||
|
g.Expect(pod.Spec.Containers[0].VolumeMounts).To(ContainElement(HaveField("Name", "op-bin")))
|
||||||
|
}, p.config.TestConfig.Timeout, p.config.TestConfig.Interval).Should(Succeed())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pod) VerifySecretsInjected(ctx context.Context) {
|
||||||
|
By("Verifying secrets are injected and concealed in pod with selector " + labels.Set(p.selector).String())
|
||||||
|
|
||||||
|
Eventually(func(g Gomega) {
|
||||||
|
// short per-attempt timeout to avoid hanging calls while Eventually polls
|
||||||
|
attemptCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
logs := p.GetPodLogs(attemptCtx)
|
||||||
|
// Check that secrets are concealed in the application logs
|
||||||
|
g.Expect(logs).To(ContainSubstring("SECRET: '<concealed by 1Password>'"))
|
||||||
|
}, p.config.TestConfig.Timeout, p.config.TestConfig.Interval).Should(Succeed())
|
||||||
|
}
|
||||||
|
33
pkg/testhelper/kube/webhook.go
Normal file
33
pkg/testhelper/kube/webhook.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package kube
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
//nolint:staticcheck // ST1001
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
//nolint:staticcheck // ST1001
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Webhook struct {
|
||||||
|
client client.Client
|
||||||
|
config *Config
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Webhook) WaitForWebhookToBeRegistered(ctx context.Context) {
|
||||||
|
By("Waiting for webhook " + w.name + " to be registered")
|
||||||
|
|
||||||
|
Eventually(func(g Gomega) {
|
||||||
|
// short per-attempt timeout to avoid hanging calls while Eventually polls
|
||||||
|
attemptCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
webhookConfig := &admissionregistrationv1.MutatingWebhookConfiguration{}
|
||||||
|
err := w.client.Get(attemptCtx, client.ObjectKey{Name: w.name}, webhookConfig)
|
||||||
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
|
}, w.config.TestConfig.Timeout, w.config.TestConfig.Interval).Should(Succeed())
|
||||||
|
}
|
@@ -106,7 +106,7 @@ var _ = Describe("Onepassword Operator e2e", Ordered, func() {
|
|||||||
func runCommonTestCases(ctx context.Context) {
|
func runCommonTestCases(ctx context.Context) {
|
||||||
It("Should create kubernetes secret from manifest file", func() {
|
It("Should create kubernetes secret from manifest file", func() {
|
||||||
By("Creating secret `login` from 1Password item")
|
By("Creating secret `login` from 1Password item")
|
||||||
kubeClient.ApplyOnePasswordItem(ctx, "secret.yaml")
|
kubeClient.Apply(ctx, "secret.yaml")
|
||||||
kubeClient.Secret("login").CheckIfExists(ctx)
|
kubeClient.Secret("login").CheckIfExists(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ func runCommonTestCases(ctx context.Context) {
|
|||||||
secretName := itemName
|
secretName := itemName
|
||||||
|
|
||||||
By("Creating secret `" + secretName + "` from 1Password item")
|
By("Creating secret `" + secretName + "` from 1Password item")
|
||||||
kubeClient.ApplyOnePasswordItem(ctx, secretName+".yaml")
|
kubeClient.Apply(ctx, secretName+".yaml")
|
||||||
kubeClient.Secret(secretName).CheckIfExists(ctx)
|
kubeClient.Secret(secretName).CheckIfExists(ctx)
|
||||||
|
|
||||||
By("Reading old password")
|
By("Reading old password")
|
||||||
@@ -147,7 +147,7 @@ func runCommonTestCases(ctx context.Context) {
|
|||||||
secretName := itemName
|
secretName := itemName
|
||||||
|
|
||||||
By("Creating secret `" + secretName + "` from 1Password item")
|
By("Creating secret `" + secretName + "` from 1Password item")
|
||||||
kubeClient.ApplyOnePasswordItem(ctx, secretName+".yaml")
|
kubeClient.Apply(ctx, secretName+".yaml")
|
||||||
kubeClient.Secret(secretName).CheckIfExists(ctx)
|
kubeClient.Secret(secretName).CheckIfExists(ctx)
|
||||||
|
|
||||||
By("Reading old password")
|
By("Reading old password")
|
||||||
@@ -205,7 +205,7 @@ func runCommonTestCases(ctx context.Context) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Ensure the secret exists (created in earlier test), but apply again safely just in case
|
// Ensure the secret exists (created in earlier test), but apply again safely just in case
|
||||||
kubeClient.ApplyOnePasswordItem(ctx, "secret-for-update.yaml")
|
kubeClient.Apply(ctx, "secret-for-update.yaml")
|
||||||
kubeClient.Secret("secret-for-update").CheckIfExists(ctx)
|
kubeClient.Secret("secret-for-update").CheckIfExists(ctx)
|
||||||
|
|
||||||
// add custom secret to the operator
|
// add custom secret to the operator
|
||||||
|
Reference in New Issue
Block a user