Skip to content

Commit 162ab3b

Browse files
committed
Removed dependency on kubectl binary
Signed-off-by: Anand Francis Joseph <anjoseph@redhat.com>
1 parent 400da88 commit 162ab3b

3 files changed

Lines changed: 73 additions & 121 deletions

File tree

tests/ginkgo/fixture/application/fixture.go

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package application
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"os/exec"
@@ -9,10 +10,20 @@ import (
910
. "github.com/onsi/ginkgo/v2"
1011
. "github.com/onsi/gomega"
1112
matcher "github.com/onsi/gomega/types"
13+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14+
"k8s.io/apimachinery/pkg/runtime/schema"
15+
"k8s.io/apimachinery/pkg/types"
1216

1317
argocdFixture "github.com/argoproj-labs/argocd-operator/tests/ginkgo/fixture/argocd"
18+
fixtureUtils "github.com/argoproj-labs/argocd-operator/tests/ginkgo/fixture/utils"
1419
)
1520

21+
var applicationGVR = schema.GroupVersionResource{
22+
Group: "argoproj.io",
23+
Version: "v1alpha1",
24+
Resource: "applications",
25+
}
26+
1627
// AppRef is a lightweight reference to an Argo CD Application.
1728
type AppRef struct {
1829
Name string
@@ -135,7 +146,7 @@ func WithSession(s *argocdFixture.Session) AppOption {
135146
return func(c *appConfig) { c.session = s }
136147
}
137148

138-
// Create creates an Argo CD Application using the argocd CLI in login mode.
149+
// Create creates an Argo CD Application using the argocd CLI.
139150
func Create(name, namespace string, opts ...AppOption) *AppRef {
140151
cfg := &appConfig{}
141152
for _, o := range opts {
@@ -150,38 +161,58 @@ func Create(name, namespace string, opts ...AppOption) *AppRef {
150161

151162
ref := &AppRef{Name: name, Namespace: namespace, session: cfg.session}
152163

153-
// Post-create: annotations via kubectl
154-
for k, v := range cfg.annotations {
155-
out, err := runKubectl("annotate", "application.argoproj.io", name, "-n", namespace,
156-
fmt.Sprintf("%s=%s", k, v))
157-
Expect(err).ToNot(HaveOccurred(), "kubectl annotate failed: %s", out)
164+
// Post-create: apply annotations, labels, and managed namespace metadata via k8s client
165+
if len(cfg.annotations) > 0 || len(cfg.labels) > 0 || len(cfg.managedNSLabels) > 0 {
166+
patchApp(name, namespace, cfg.annotations, cfg.labels, cfg.managedNSLabels)
158167
}
159168

160-
// Post-create: labels via kubectl
161-
for k, v := range cfg.labels {
162-
out, err := runKubectl("label", "application.argoproj.io", name, "-n", namespace,
163-
fmt.Sprintf("%s=%s", k, v))
164-
Expect(err).ToNot(HaveOccurred(), "kubectl label failed: %s", out)
169+
return ref
170+
}
171+
172+
// patchApp applies annotations, labels, and managed namespace metadata to an Application using the k8s client.
173+
func patchApp(name, namespace string, annotations, labels map[string]string, managedNSLabels map[string]string) {
174+
k8sClient, _ := fixtureUtils.GetE2ETestKubeClient()
175+
ctx := context.Background()
176+
177+
app := &unstructured.Unstructured{}
178+
app.SetGroupVersionKind(applicationGVR.GroupVersion().WithKind("Application"))
179+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, app)).To(Succeed(), "failed to get Application %s/%s", namespace, name)
180+
181+
if len(annotations) > 0 {
182+
existing := app.GetAnnotations()
183+
if existing == nil {
184+
existing = make(map[string]string)
185+
}
186+
for k, v := range annotations {
187+
existing[k] = v
188+
}
189+
app.SetAnnotations(existing)
165190
}
166191

167-
// Post-create: managed namespace metadata labels via kubectl patch
168-
if len(cfg.managedNSLabels) > 0 {
169-
patch := map[string]any{
170-
"spec": map[string]any{
171-
"syncPolicy": map[string]any{
172-
"managedNamespaceMetadata": map[string]any{
173-
"labels": cfg.managedNSLabels,
174-
},
175-
},
176-
},
192+
if len(labels) > 0 {
193+
existing := app.GetLabels()
194+
if existing == nil {
195+
existing = make(map[string]string)
177196
}
178-
patchBytes, _ := json.Marshal(patch)
179-
out, err := runKubectl("patch", "application.argoproj.io", name, "-n", namespace,
180-
"--type=merge", "-p", string(patchBytes))
181-
Expect(err).ToNot(HaveOccurred(), "kubectl patch failed: %s", out)
197+
for k, v := range labels {
198+
existing[k] = v
199+
}
200+
app.SetLabels(existing)
182201
}
183202

184-
return ref
203+
if len(managedNSLabels) > 0 {
204+
spec, _, _ := unstructured.NestedMap(app.Object, "spec")
205+
if spec == nil {
206+
spec = map[string]interface{}{}
207+
}
208+
labelsMap := make(map[string]interface{}, len(managedNSLabels))
209+
for k, v := range managedNSLabels {
210+
labelsMap[k] = v
211+
}
212+
Expect(unstructured.SetNestedField(app.Object, labelsMap, "spec", "syncPolicy", "managedNamespaceMetadata", "labels")).To(Succeed())
213+
}
214+
215+
Expect(k8sClient.Update(ctx, app)).To(Succeed(), "failed to update Application %s/%s", namespace, name)
185216
}
186217

187218
// Delete deletes an Argo CD Application.
@@ -192,7 +223,7 @@ func Delete(ref *AppRef) {
192223
}
193224

194225
// Ref creates a reference to an existing Application without creating it.
195-
// Session is optional — when nil, kubectl is used for get operations (matchers).
226+
// Session is optional — when nil, argocd CLI get falls back to the k8s client.
196227
func Ref(name, namespace string, sessions ...*argocdFixture.Session) *AppRef {
197228
var session *argocdFixture.Session
198229
if len(sessions) > 0 {
@@ -311,11 +342,14 @@ func getAppJSON(ref *AppRef) (map[string]any, error) {
311342
return nil, fmt.Errorf("argocd app get failed: %v, output: %s", err, output)
312343
}
313344
} else {
314-
// No session — use kubectl directly (for Ref-only usage without CLI login)
315-
output, err = runKubectl("get", "application.argoproj.io", ref.Name, "-n", ref.Namespace, "-o", "json")
316-
if err != nil {
317-
return nil, fmt.Errorf("kubectl get application failed: %v, output: %s", err, output)
345+
// No session — use k8s client directly (for Ref-only usage without CLI login)
346+
k8sClient, _ := fixtureUtils.GetE2ETestKubeClient()
347+
app := &unstructured.Unstructured{}
348+
app.SetGroupVersionKind(applicationGVR.GroupVersion().WithKind("Application"))
349+
if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ref.Name, Namespace: ref.Namespace}, app); err != nil {
350+
return nil, fmt.Errorf("k8s client get application failed: %v", err)
318351
}
352+
return app.Object, nil
319353
}
320354

321355
var result map[string]any
@@ -353,12 +387,3 @@ func runArgoCDCLI(session *argocdFixture.Session, args ...string) (string, error
353387
GinkgoWriter.Println(string(output))
354388
return string(output), err
355389
}
356-
357-
func runKubectl(args ...string) (string, error) {
358-
GinkgoWriter.Println("executing kubectl", args)
359-
// #nosec G204 -- test code
360-
cmd := exec.Command("kubectl", args...)
361-
output, err := cmd.CombinedOutput()
362-
GinkgoWriter.Println(string(output))
363-
return string(output), err
364-
}

tests/ginkgo/fixture/appproject/fixture.go

Lines changed: 7 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package appproject
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"os/exec"
7-
"strings"
86

97
. "github.com/onsi/ginkgo/v2"
108
. "github.com/onsi/gomega"
@@ -65,27 +63,17 @@ func WithSession(s *argocdFixture.Session) ProjOption {
6563
return func(c *projConfig) { c.session = s }
6664
}
6765

68-
// Create creates an Argo CD AppProject.
69-
// Uses kubectl for projects with DestinationServiceAccounts (not supported by argocd CLI).
66+
// Create creates an Argo CD AppProject via the argocd CLI.
7067
func Create(name, namespace string, opts ...ProjOption) *ProjRef {
7168
cfg := &projConfig{}
7269
for _, o := range opts {
7370
o(cfg)
7471
}
7572

76-
ref := &ProjRef{Name: name, Namespace: namespace, session: cfg.session}
77-
78-
if len(cfg.destinationServiceAccounts) > 0 {
79-
createViaKubectl(name, namespace, cfg)
80-
} else {
81-
Expect(cfg.session).ToNot(BeNil(), "WithSession is required for appproject.Create via CLI")
82-
createViaCLI(name, namespace, cfg)
83-
}
73+
Expect(cfg.session).ToNot(BeNil(), "WithSession is required for appproject.Create")
8474

85-
return ref
86-
}
75+
ref := &ProjRef{Name: name, Namespace: namespace, session: cfg.session}
8776

88-
func createViaCLI(name, namespace string, cfg *projConfig) {
8977
args := []string{"proj", "create", name}
9078
for _, repo := range cfg.sourceRepos {
9179
args = append(args, "--src", repo)
@@ -96,6 +84,9 @@ func createViaCLI(name, namespace string, cfg *projConfig) {
9684
for _, cr := range cfg.clusterResources {
9785
args = append(args, "--allow-cluster-resource", fmt.Sprintf("%s/%s", cr[0], cr[1]))
9886
}
87+
for _, d := range cfg.destinationServiceAccounts {
88+
args = append(args, "--dest-service-accounts", fmt.Sprintf("%s,%s,%s", d.Server, d.Namespace, d.Account))
89+
}
9990

10091
output, err := runArgoCDCLI(cfg.session, args...)
10192
Expect(err).ToNot(HaveOccurred(), "argocd proj create failed: %s", output)
@@ -105,62 +96,8 @@ func createViaCLI(name, namespace string, cfg *projConfig) {
10596
out, err := runArgoCDCLI(cfg.session, "proj", "add-source-namespace", name, ns)
10697
Expect(err).ToNot(HaveOccurred(), "argocd proj add-source-namespace failed: %s", out)
10798
}
108-
}
109-
110-
func createViaKubectl(name, namespace string, cfg *projConfig) {
111-
spec := map[string]any{}
112-
113-
if len(cfg.sourceRepos) > 0 {
114-
spec["sourceRepos"] = cfg.sourceRepos
115-
}
116-
117-
if len(cfg.destinations) > 0 {
118-
dests := make([]map[string]string, len(cfg.destinations))
119-
for i, d := range cfg.destinations {
120-
dests[i] = map[string]string{"server": d[0], "namespace": d[1]}
121-
}
122-
spec["destinations"] = dests
123-
}
124-
125-
if len(cfg.clusterResources) > 0 {
126-
crs := make([]map[string]string, len(cfg.clusterResources))
127-
for i, cr := range cfg.clusterResources {
128-
crs[i] = map[string]string{"group": cr[0], "kind": cr[1]}
129-
}
130-
spec["clusterResourceWhitelist"] = crs
131-
}
13299

133-
if len(cfg.destinationServiceAccounts) > 0 {
134-
dsas := make([]map[string]string, len(cfg.destinationServiceAccounts))
135-
for i, d := range cfg.destinationServiceAccounts {
136-
dsas[i] = map[string]string{
137-
"server": d.Server,
138-
"namespace": d.Namespace,
139-
"defaultServiceAccount": d.Account,
140-
}
141-
}
142-
spec["destinationServiceAccounts"] = dsas
143-
}
144-
145-
if len(cfg.sourceNamespaces) > 0 {
146-
spec["sourceNamespaces"] = cfg.sourceNamespaces
147-
}
148-
149-
resource := map[string]any{
150-
"apiVersion": "argoproj.io/v1alpha1",
151-
"kind": "AppProject",
152-
"metadata": map[string]any{
153-
"name": name,
154-
"namespace": namespace,
155-
},
156-
"spec": spec,
157-
}
158-
159-
jsonBytes, err := json.Marshal(resource)
160-
Expect(err).ToNot(HaveOccurred())
161-
162-
out, err := runKubectlWithStdin(string(jsonBytes), "apply", "-f", "-")
163-
Expect(err).ToNot(HaveOccurred(), "kubectl apply failed: %s", out)
100+
return ref
164101
}
165102

166103
// AddDestination adds a destination to an existing AppProject.
@@ -198,13 +135,3 @@ func runArgoCDCLI(session *argocdFixture.Session, args ...string) (string, error
198135
GinkgoWriter.Println(string(output))
199136
return string(output), err
200137
}
201-
202-
func runKubectlWithStdin(stdin string, args ...string) (string, error) {
203-
GinkgoWriter.Println("executing kubectl", args)
204-
// #nosec G204 -- test code
205-
cmd := exec.Command("kubectl", args...)
206-
cmd.Stdin = strings.NewReader(stdin)
207-
output, err := cmd.CombinedOutput()
208-
GinkgoWriter.Println(string(output))
209-
return string(output), err
210-
}

tests/ginkgo/fixture/argocd/fixture.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func findFreePort() int {
7777
listener, err := net.Listen("tcp", "127.0.0.1:0")
7878
Expect(err).ToNot(HaveOccurred(), "failed to find free port")
7979
port := listener.Addr().(*net.TCPAddr).Port
80-
listener.Close()
80+
Expect(err).ToNot(HaveOccurred(), "error listening on port %d", port)
8181
return port
8282
}
8383

0 commit comments

Comments
 (0)