Add functions to testhelper package

This commit is contained in:
Volodymyr Zotov
2025-09-17 11:08:11 -05:00
parent c9a8cc6fb8
commit 79ee171b7f
4 changed files with 157 additions and 18 deletions

View File

@@ -22,6 +22,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -45,6 +46,7 @@ type Config struct {
type Kube struct {
Config *Config
Client client.Client
Clientset kubernetes.Interface
Mapper meta.RESTMapper
}
@@ -79,6 +81,10 @@ func NewKubeClient(config *Config) *Kube {
})
Expect(err).NotTo(HaveOccurred())
// Create Kubernetes clientset for logs and other operations
clientset, err := kubernetes.NewForConfig(restConfig)
Expect(err).NotTo(HaveOccurred())
// update the current contexts namespace in kubeconfig
pathOpts := clientcmd.NewDefaultPathOptions()
cfg, err := pathOpts.GetStartingConfig()
@@ -97,6 +103,7 @@ func NewKubeClient(config *Config) *Kube {
return &Kube{
Config: config,
Client: kubernetesClient,
Clientset: clientset,
Mapper: rm,
}
}
@@ -120,6 +127,7 @@ func (k *Kube) Deployment(name string) *Deployment {
func (k *Kube) Pod(selector map[string]string) *Pod {
return &Pod{
client: k.Client,
clientset: k.Clientset,
config: k.Config,
selector: selector,
}
@@ -133,8 +141,16 @@ func (k *Kube) Namespace(name string) *Namespace {
}
}
// ApplyOnePasswordItem applies a OnePasswordItem manifest.
func (k *Kube) ApplyOnePasswordItem(ctx context.Context, fileName string) {
func (k *Kube) Webhook(name string) *Webhook {
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)
// Derive a short-lived context so this API call won't hang indefinitely.

View File

@@ -2,6 +2,7 @@ package kube
import (
"context"
"io"
"time"
//nolint:staticcheck // ST1001
@@ -10,11 +11,13 @@ import (
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type Pod struct {
client client.Client
clientset kubernetes.Interface
config *Config
selector map[string]string
}
@@ -45,3 +48,90 @@ func (p *Pod) WaitingForRunningPod(ctx context.Context) {
g.Expect(foundRunning).To(BeTrue(), "pod not Running yet")
}, 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())
}

View 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())
}

View File

@@ -106,7 +106,7 @@ var _ = Describe("Onepassword Operator e2e", Ordered, func() {
func runCommonTestCases(ctx context.Context) {
It("Should create kubernetes secret from manifest file", func() {
By("Creating secret `login` from 1Password item")
kubeClient.ApplyOnePasswordItem(ctx, "secret.yaml")
kubeClient.Apply(ctx, "secret.yaml")
kubeClient.Secret("login").CheckIfExists(ctx)
})
@@ -115,7 +115,7 @@ func runCommonTestCases(ctx context.Context) {
secretName := itemName
By("Creating secret `" + secretName + "` from 1Password item")
kubeClient.ApplyOnePasswordItem(ctx, secretName+".yaml")
kubeClient.Apply(ctx, secretName+".yaml")
kubeClient.Secret(secretName).CheckIfExists(ctx)
By("Reading old password")
@@ -147,7 +147,7 @@ func runCommonTestCases(ctx context.Context) {
secretName := itemName
By("Creating secret `" + secretName + "` from 1Password item")
kubeClient.ApplyOnePasswordItem(ctx, secretName+".yaml")
kubeClient.Apply(ctx, secretName+".yaml")
kubeClient.Secret(secretName).CheckIfExists(ctx)
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
kubeClient.ApplyOnePasswordItem(ctx, "secret-for-update.yaml")
kubeClient.Apply(ctx, "secret-for-update.yaml")
kubeClient.Secret("secret-for-update").CheckIfExists(ctx)
// add custom secret to the operator