Skip to content

Commit e25fd6a

Browse files
committed
Adds e2e test case for the operator
adds vendor Adds e2e test case for the operator # Conflicts: # go.mod
1 parent a1699b5 commit e25fd6a

File tree

305 files changed

+44971
-83
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

305 files changed

+44971
-83
lines changed

go.sum

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,12 @@ github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
393393
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
394394
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
395395
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
396-
github.com/openshift/build-machinery-go v0.0.0-20250414185254-3ce8e800ceda h1:Yjnmq1n4zf1pTwao3EAgkG5o++6iOuxfxGVDwj2Kfrs=
397-
github.com/openshift/build-machinery-go v0.0.0-20250414185254-3ce8e800ceda/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE=
396+
github.com/openshift/api v0.0.0-20250517062239-9cbdb71c92bb h1:zsjZtFnPzE7l2VD5cDCs7PcpaRjaECByEw+JIjM0yW4=
397+
github.com/openshift/api v0.0.0-20250517062239-9cbdb71c92bb/go.mod h1:yk60tHAmHhtVpJQo3TwVYq2zpuP70iJIFDCmeKMIzPw=
398+
github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee h1:+Sp5GGnjHDhT/a/nQ1xdp43UscBMr7G5wxsYotyhzJ4=
399+
github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE=
400+
github.com/openshift/client-go v0.0.0-20250603093317-900624865677 h1:OE7ZwLdTM4FmkzfHJMN+CwkyIHCNevv5jAnfkYhw4+U=
401+
github.com/openshift/client-go v0.0.0-20250603093317-900624865677/go.mod h1:If4PFiis4Sp3vf5z7PYkZJy3gA9DP1kYZR9SdTjjKoY=
398402
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
399403
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
400404
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
@@ -773,12 +777,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
773777
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
774778
honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs=
775779
honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
776-
k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
777-
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
780+
k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
781+
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
778782
k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw=
779783
k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto=
780-
k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs=
781-
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
784+
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
785+
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
782786
k8s.io/apiserver v0.32.1 h1:oo0OozRos66WFq87Zc5tclUX2r0mymoVHRq8JmR7Aak=
783787
k8s.io/apiserver v0.32.1/go.mod h1:UcB9tWjBY7aryeI5zAgzVJB/6k7E97bkr1RgqDz0jPw=
784788
k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU=
@@ -801,8 +805,8 @@ k8s.io/kubelet v0.32.1 h1:bB91GvMsZb+LfzBxnjPEr1Fal/sdxZtYphlfwAaRJGw=
801805
k8s.io/kubelet v0.32.1/go.mod h1:4sAEZ6PlewD0GroV3zscY7llym6kmNNTVmUI/Qshm6w=
802806
k8s.io/kubernetes v1.32.1 h1:46YPpIBCT9dkmeglstZ2Gg4LGaAdro1/3IQ+1AfbF1s=
803807
k8s.io/kubernetes v1.32.1/go.mod h1:tiIKO63GcdPRBHW2WiUFm3C0eoLczl3f7qi56Dm1W8I=
804-
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
805-
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
808+
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
809+
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
806810
mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
807811
mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA=
808812
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U=

test/e2e/e2e_test.go

Lines changed: 94 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,119 @@
1-
/*
2-
Copyright 2025.
3-
4-
Licensed under the Apache License, Version 2.0 (the "License");
5-
you may not use this file except in compliance with the License.
6-
You may obtain a copy of the License at
7-
8-
http://www.apache.org/licenses/LICENSE-2.0
9-
10-
Unless required by applicable law or agreed to in writing, software
11-
distributed under the License is distributed on an "AS IS" BASIS,
12-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
See the License for the specific language governing permissions and
14-
limitations under the License.
15-
*/
16-
171
package e2e
182

193
import (
4+
"context"
205
"fmt"
21-
"os/exec"
22-
"time"
23-
246
. "github.com/onsi/ginkgo/v2"
257
. "github.com/onsi/gomega"
8+
utils "github.com/openshift/external-secrets-operator/test/utils"
9+
corev1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"os"
12+
"testing"
13+
"time"
14+
)
2615

27-
"github.com/openshift/external-secrets-operator/test/utils"
16+
const (
17+
namespace = "external-secrets-operator"
18+
secretStoreFile = "testdata/aws_secret_store.yaml"
19+
externalSecretFile = "testdata/aws_external_secret.yaml"
2820
)
2921

30-
const namespace = "external-secrets-operator"
22+
var _ = Describe("External Secrets Operator", Ordered, func() {
23+
24+
var (
25+
ctx = context.TODO()
26+
loader utils.DynamicResourceLoader
27+
)
3128

32-
var _ = Describe("controller", Ordered, func() {
3329
BeforeAll(func() {
34-
//TODO: add any pre-reqs for the tests.
30+
loader = utils.NewDynamicResourceLoader(ctx, &testing.T{})
31+
32+
// Create ExternalSecret resource
33+
loader.CreateFromFile(loadFromFile, "testdata/external_secret.yaml", namespace)
3534
})
3635

3736
AfterAll(func() {
38-
//TODO: add any clean up required after the tests.
37+
loader.DeleteFromFile(loadFromFile, "testdata/external_secret.yaml", namespace)
3938
})
4039

4140
Context("Operator", func() {
42-
It("should run successfully", func() {
43-
var controllerPodName string
44-
45-
By("validating that the controller-manager pod is running as expected")
46-
verifyControllerUp := func() error {
47-
// Get pod name
48-
49-
cmd := exec.Command("oc", "get",
50-
"pods", "-l", "control-plane=controller-manager",
51-
"-o", "go-template={{ range .items }}"+
52-
"{{ if not .metadata.deletionTimestamp }}"+
53-
"{{ .metadata.name }}"+
54-
"{{ \"\\n\" }}{{ end }}{{ end }}",
55-
"-n", namespace,
56-
)
57-
58-
podOutput, err := utils.Run(cmd)
41+
It("should have controller pod running", func() {
42+
verifyControllerPod := func() error {
43+
pods, err := loader.KubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
44+
LabelSelector: "control-plane=controller-manager",
45+
})
5946
ExpectWithOffset(2, err).NotTo(HaveOccurred())
60-
podNames := utils.GetNonEmptyLines(string(podOutput))
61-
if len(podNames) != 1 {
62-
return fmt.Errorf("expect 1 controller pods running, but got %d", len(podNames))
47+
48+
var runningPods []corev1.Pod
49+
for _, pod := range pods.Items {
50+
if pod.DeletionTimestamp == nil && pod.Status.Phase == corev1.PodRunning {
51+
runningPods = append(runningPods, pod)
52+
}
6353
}
64-
controllerPodName = podNames[0]
65-
ExpectWithOffset(2, controllerPodName).Should(ContainSubstring("controller-manager"))
66-
67-
// Validate pod status
68-
cmd = exec.Command("oc", "get",
69-
"pods", controllerPodName, "-o", "jsonpath={.status.phase}",
70-
"-n", namespace,
71-
)
72-
status, err := utils.Run(cmd)
73-
ExpectWithOffset(2, err).NotTo(HaveOccurred())
74-
if string(status) != "Running" {
75-
return fmt.Errorf("controller pod in %s status", status)
54+
55+
if len(runningPods) != 1 {
56+
return fmt.Errorf("expected 1 running controller pod, got %d", len(runningPods))
7657
}
58+
59+
ExpectWithOffset(2, runningPods[0].Name).To(ContainSubstring("controller-manager"))
60+
fmt.Println(runningPods[0].Name)
7761
return nil
7862
}
79-
EventuallyWithOffset(1, verifyControllerUp, time.Minute, time.Second).Should(Succeed())
63+
64+
EventuallyWithOffset(1, verifyControllerPod, time.Minute, time.Second).Should(Succeed())
65+
})
66+
})
67+
68+
Context("AWS SecretStore", func() {
69+
BeforeEach(func() {
70+
loader.CreateFromFile(loadFromFile, secretStoreFile, namespace)
71+
loader.CreateFromFile(loadFromFile, externalSecretFile, namespace)
72+
73+
})
74+
75+
AfterEach(func() {
76+
// Clean up SecretStore
77+
loader.DeleteFromFile(loadFromFile, secretStoreFile, namespace)
78+
// Clean up ExternalStore
79+
loader.DeleteFromFile(loadFromFile, externalSecretFile, namespace)
80+
81+
})
82+
83+
It("should synchronize secrets from AWS Secrets Manager", func() {
84+
By("verifying the synchronization of the secret")
85+
Eventually(func() error {
86+
k8sSecret, err := loader.KubeClient.CoreV1().Secrets(namespace).Get(ctx, "aws-secret", metav1.GetOptions{})
87+
88+
secretsList, err := loader.KubeClient.CoreV1().Secrets("kube-system").List(ctx, metav1.ListOptions{})
89+
Expect(err).NotTo(HaveOccurred())
90+
91+
fmt.Println("Secrets in kube-system:")
92+
for _, s := range secretsList.Items {
93+
fmt.Println("-", s.Name)
94+
}
95+
if err != nil {
96+
return fmt.Errorf("failed to get secret: %v", err)
97+
}
98+
99+
if string(k8sSecret.Data["aws_secret_access_key"]) == "" {
100+
return fmt.Errorf("secret data is empty")
101+
}
102+
103+
decodedValue, err := os.ReadFile("testdata/expected_value.yaml")
104+
if err != nil {
105+
return fmt.Errorf("failed to read expected secret value: %v", err)
106+
}
107+
108+
if string(k8sSecret.Data["aws_secret_access_key"]) != string(decodedValue) {
109+
return fmt.Errorf("secret value does not match expected")
110+
}
111+
return nil
112+
}, time.Minute, time.Second).Should(Succeed())
80113
})
81114
})
82115
})
116+
117+
func loadFromFile(name string) ([]byte, error) {
118+
return os.ReadFile(name)
119+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: aws-creds
5+
namespace: kube-system
6+
type: Opaque
7+
data:
8+
aws_access_key_id: ""
9+
aws_secret_access_key: ""
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: external-secrets.io/v1beta1
2+
kind: ExternalSecret
3+
metadata:
4+
name: aws-secret
5+
namespace: external-secrets-operator
6+
spec:
7+
refreshInterval: 1h
8+
secretStoreRef:
9+
name: aws-secret-store
10+
kind: SecretStore
11+
target:
12+
name: aws-secret
13+
creationPolicy: Owner
14+
data:
15+
- secretKey: aws_secret_access_key
16+
remoteRef:
17+
key: aws_secret_access_key #TODO
18+
property: aws_secret_access_key
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
apiVersion: external-secrets.io/v1beta1
2+
kind: SecretStore
3+
metadata:
4+
name: aws-secret-store
5+
namespace: external-secrets-operator
6+
spec:
7+
provider:
8+
aws:
9+
service: SecretsManager
10+
region: us-east-1
11+
auth:
12+
secretRef:
13+
accessKeyIDSecretRef:
14+
name: aws-creds
15+
key: aws_access_key_id
16+
namespace: kube-system
17+
secretAccessKeySecretRef:
18+
name: aws-creds
19+
key: aws_secret_access_key
20+
namespace: kube-system
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hqTTSYkFYgkw3OfQ9lFvQgtsReb1g1a+Po5Y/HNU
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: operator.openshift.io/v1alpha1
2+
kind: ExternalSecretsManager
3+
metadata:
4+
labels:
5+
app.kubernetes.io/name: external-secrets-operator
6+
app.kubernetes.io/managed-by: kustomize
7+
name: cluster
8+
spec: {}

test/utils/dynamic_resources.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package utils
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"github.com/stretchr/testify/require"
7+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
8+
"k8s.io/apimachinery/pkg/api/meta"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+
"k8s.io/apimachinery/pkg/runtime"
12+
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
13+
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
14+
"k8s.io/client-go/dynamic"
15+
"k8s.io/client-go/kubernetes"
16+
"k8s.io/client-go/restmapper"
17+
"testing"
18+
)
19+
20+
type DynamicResourceLoader struct {
21+
KubeClient kubernetes.Interface
22+
DynamicClient dynamic.Interface
23+
24+
context context.Context
25+
t *testing.T
26+
}
27+
28+
type doFunc func(t *testing.T, unstructured *unstructured.Unstructured, dynamicResourceInterface dynamic.ResourceInterface)
29+
30+
func NewDynamicResourceLoader(context context.Context, t *testing.T) DynamicResourceLoader {
31+
k, d := NewClientsConfigForTest(t)
32+
return DynamicResourceLoader{
33+
KubeClient: k,
34+
DynamicClient: d,
35+
context: context,
36+
t: t,
37+
}
38+
}
39+
40+
func (d DynamicResourceLoader) noErrorSkipExists(err error) {
41+
if !k8serrors.IsAlreadyExists(err) {
42+
require.NoError(d.t, err)
43+
}
44+
}
45+
46+
func (d DynamicResourceLoader) noErrorSkipNotExisting(err error) {
47+
if !k8serrors.IsNotFound(err) {
48+
require.NoError(d.t, err)
49+
}
50+
}
51+
52+
func (d DynamicResourceLoader) do(do doFunc, assetFunc func(name string) ([]byte, error), filename string, overrideNamespace string) {
53+
b, err := assetFunc(filename)
54+
require.NoError(d.t, err)
55+
56+
decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(b), 1024)
57+
var rawObj runtime.RawExtension
58+
err = decoder.Decode(&rawObj)
59+
require.NoError(d.t, err)
60+
61+
obj, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(rawObj.Raw, nil, nil)
62+
require.NoError(d.t, err)
63+
64+
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
65+
require.NoError(d.t, err)
66+
67+
unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap}
68+
69+
gr, err := restmapper.GetAPIGroupResources(d.KubeClient.Discovery())
70+
require.NoError(d.t, err)
71+
72+
mapper := restmapper.NewDiscoveryRESTMapper(gr)
73+
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
74+
require.NoError(d.t, err)
75+
76+
var dri dynamic.ResourceInterface
77+
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
78+
if overrideNamespace != "" {
79+
unstructuredObj.SetNamespace(overrideNamespace)
80+
}
81+
require.NotEmpty(d.t, unstructuredObj.GetNamespace(), "Namespace can not be empty!")
82+
dri = d.DynamicClient.Resource(mapping.Resource).Namespace(unstructuredObj.GetNamespace())
83+
} else {
84+
dri = d.DynamicClient.Resource(mapping.Resource)
85+
}
86+
87+
do(d.t, unstructuredObj, dri)
88+
}
89+
90+
func (d DynamicResourceLoader) DeleteFromFile(assetFunc func(name string) ([]byte, error), filename string, overrideNamespace string) {
91+
d.t.Logf("Deleting resource %v\n", filename)
92+
deleteFunc := func(t *testing.T, unstructured *unstructured.Unstructured, dynamicResourceInterface dynamic.ResourceInterface) {
93+
err := dynamicResourceInterface.Delete(context.TODO(), unstructured.GetName(), metav1.DeleteOptions{})
94+
d.noErrorSkipNotExisting(err)
95+
}
96+
97+
d.do(deleteFunc, assetFunc, filename, overrideNamespace)
98+
d.t.Logf("Resource %v deleted\n", filename)
99+
}
100+
101+
func (d DynamicResourceLoader) CreateFromFile(assetFunc func(name string) ([]byte, error), filename string, overrideNamespace string) {
102+
d.t.Logf("Creating resource %v\n", filename)
103+
createFunc := func(t *testing.T, unstructured *unstructured.Unstructured, dynamicResourceInterface dynamic.ResourceInterface) {
104+
_, err := dynamicResourceInterface.Create(context.TODO(), unstructured, metav1.CreateOptions{})
105+
d.noErrorSkipExists(err)
106+
}
107+
108+
d.do(createFunc, assetFunc, filename, overrideNamespace)
109+
d.t.Logf("Resource %v created\n", filename)
110+
}

0 commit comments

Comments
 (0)