Skip to content

Commit 1188a7b

Browse files
authored
Add e2e tests for private Git repository authentication (#40)
1 parent d18b3ac commit 1188a7b

4 files changed

Lines changed: 295 additions & 93 deletions

File tree

test/e2e/bundle_test.go

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -311,21 +311,7 @@ func CreateFunctionAndWaitForReady(testNs TestNamespace) {
311311
err := k8sClient.Create(ctx, function)
312312
Expect(err).NotTo(HaveOccurred())
313313

314-
funcBecomeReady := func(g Gomega) {
315-
fn := &functionsdevv1alpha1.Function{}
316-
err := k8sClient.Get(ctx, types.NamespacedName{Name: function.Name, Namespace: function.Namespace}, fn)
317-
g.Expect(err).NotTo(HaveOccurred())
318-
319-
for _, cond := range fn.Status.Conditions {
320-
if cond.Type == functionsdevv1alpha1.TypeReady {
321-
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
322-
return
323-
}
324-
}
325-
g.Expect(false).To(BeTrue(), "Ready condition not found")
326-
}
327-
328-
Eventually(funcBecomeReady, 5*time.Minute).Should(Succeed())
314+
Eventually(functionBecomesReady(function.Name, function.Namespace), 5*time.Minute).Should(Succeed())
329315
}
330316

331317
func CreateFunctionAndWaitForConsistentlyNotReconciled(testNs TestNamespace) {

test/e2e/func_deploy_test.go

Lines changed: 289 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,103 @@ import (
2727
"github.com/functions-dev/func-operator/test/utils"
2828
. "github.com/onsi/ginkgo/v2"
2929
. "github.com/onsi/gomega"
30+
v1 "k8s.io/api/core/v1"
3031
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3132
"k8s.io/apimachinery/pkg/types"
3233
)
3334

35+
// expectFunctionConditionTrue returns a Gomega function that checks if a Function
36+
// has the specified condition type with status True
37+
func expectFunctionConditionTrue(functionName, functionNamespace string, conditionType string) func(g Gomega) {
38+
return func(g Gomega) {
39+
fn := &functionsdevv1alpha1.Function{}
40+
err := k8sClient.Get(ctx, types.NamespacedName{Name: functionName, Namespace: functionNamespace}, fn)
41+
g.Expect(err).NotTo(HaveOccurred())
42+
43+
for _, cond := range fn.Status.Conditions {
44+
if cond.Type == conditionType {
45+
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
46+
return
47+
}
48+
}
49+
g.Expect(false).To(BeTrue(), conditionType+" condition not found")
50+
}
51+
}
52+
53+
// expectFunctionConditionFalseWithReason returns a Gomega function that checks if a Function
54+
// has the specified condition type with status False, specific reason, and message substring
55+
func expectFunctionConditionFalseWithReason(
56+
functionName,
57+
functionNamespace,
58+
conditionType,
59+
reason,
60+
messageSubstring string) func(g Gomega) {
61+
return func(g Gomega) {
62+
fn := &functionsdevv1alpha1.Function{}
63+
err := k8sClient.Get(ctx, types.NamespacedName{Name: functionName, Namespace: functionNamespace}, fn)
64+
g.Expect(err).NotTo(HaveOccurred())
65+
66+
for _, cond := range fn.Status.Conditions {
67+
if cond.Type == conditionType {
68+
g.Expect(cond.Status).To(Equal(metav1.ConditionFalse))
69+
g.Expect(cond.Reason).To(Equal(reason))
70+
g.Expect(cond.Message).To(ContainSubstring(messageSubstring))
71+
return
72+
}
73+
}
74+
g.Expect(false).To(BeTrue(), conditionType+" condition not found")
75+
}
76+
}
77+
78+
// functionBecomesReady is a convenience wrapper for checking if a Function becomes Ready
79+
func functionBecomesReady(functionName, functionNamespace string) func(g Gomega) {
80+
return expectFunctionConditionTrue(functionName, functionNamespace, functionsdevv1alpha1.TypeReady)
81+
}
82+
83+
// functionMiddlewareUpToDate checks if the middleware condition is true
84+
func functionMiddlewareUpToDate(functionName, functionNamespace string) func(g Gomega) {
85+
return expectFunctionConditionTrue(functionName, functionNamespace, functionsdevv1alpha1.TypeMiddlewareUpToDate)
86+
}
87+
88+
// functionNotReadyWithAuthError returns a Gomega function that checks if a Function
89+
// is NOT Ready and has an auth error in SourceReady condition
90+
func functionNotReadyWithAuthError(functionName, functionNamespace string) func(g Gomega) {
91+
return func(g Gomega) {
92+
fn := &functionsdevv1alpha1.Function{}
93+
err := k8sClient.Get(ctx, types.NamespacedName{Name: functionName, Namespace: functionNamespace}, fn)
94+
g.Expect(err).NotTo(HaveOccurred())
95+
96+
for _, cond := range fn.Status.Conditions {
97+
if cond.Type == functionsdevv1alpha1.TypeReady {
98+
g.Expect(cond.Status).NotTo(Equal(metav1.ConditionTrue))
99+
}
100+
101+
// Check for SourceReady condition with auth error
102+
if cond.Type == functionsdevv1alpha1.TypeSourceReady {
103+
g.Expect(cond.Status).To(Equal(metav1.ConditionFalse))
104+
g.Expect(cond.Message).To(Or(
105+
ContainSubstring("authentication"),
106+
ContainSubstring("Authentication"),
107+
ContainSubstring("401"),
108+
ContainSubstring("Unauthorized"),
109+
))
110+
return
111+
}
112+
}
113+
g.Expect(false).To(BeTrue(), "SourceReady condition not found")
114+
}
115+
}
116+
117+
// functionNotDeployed check if the function is not ready as the function was not deployed yet
118+
func functionNotDeployed(functionName, functionNamespace string) func(g Gomega) {
119+
return expectFunctionConditionFalseWithReason(
120+
functionName,
121+
functionNamespace,
122+
functionsdevv1alpha1.TypeDeployed,
123+
"NotDeployed",
124+
"Function not deployed yet")
125+
}
126+
34127
var _ = Describe("Operator", func() {
35128

36129
SetDefaultEventuallyTimeout(2 * time.Minute)
@@ -109,22 +202,8 @@ var _ = Describe("Operator", func() {
109202

110203
functionName = function.Name
111204

112-
funcBecomeReady := func(g Gomega) {
113-
fn := &functionsdevv1alpha1.Function{}
114-
err := k8sClient.Get(ctx, types.NamespacedName{Name: function.Name, Namespace: function.Namespace}, fn)
115-
g.Expect(err).NotTo(HaveOccurred())
116-
117-
for _, cond := range fn.Status.Conditions {
118-
if cond.Type == functionsdevv1alpha1.TypeReady {
119-
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
120-
return
121-
}
122-
}
123-
g.Expect(false).To(BeTrue(), "Ready condition not found")
124-
}
125-
126205
// redeploy could take a bit longer therefore give a bit more time
127-
Eventually(funcBecomeReady, 6*time.Minute).Should(Succeed())
206+
Eventually(functionBecomesReady(functionName, functionNamespace), 6*time.Minute).Should(Succeed())
128207
})
129208
})
130209
Context("with a function in a subdirectory in a monorepo", func() {
@@ -204,22 +283,8 @@ var _ = Describe("Operator", func() {
204283

205284
functionName = function.Name
206285

207-
funcBecomeReady := func(g Gomega) {
208-
fn := &functionsdevv1alpha1.Function{}
209-
err := k8sClient.Get(ctx, types.NamespacedName{Name: function.Name, Namespace: function.Namespace}, fn)
210-
g.Expect(err).NotTo(HaveOccurred())
211-
212-
for _, cond := range fn.Status.Conditions {
213-
if cond.Type == functionsdevv1alpha1.TypeReady {
214-
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
215-
return
216-
}
217-
}
218-
g.Expect(false).To(BeTrue(), "Ready condition not found")
219-
}
220-
221286
// redeploy could take a bit longer therefore give a bit more time
222-
Eventually(funcBecomeReady, 6*time.Minute).Should(Succeed())
287+
Eventually(functionBecomesReady(functionName, functionNamespace), 6*time.Minute).Should(Succeed())
223288
})
224289
})
225290
Context("with a not yet deployed function", func() {
@@ -277,23 +342,202 @@ var _ = Describe("Operator", func() {
277342

278343
functionName = function.Name
279344

280-
funcBecomeReady := func(g Gomega) {
281-
fn := &functionsdevv1alpha1.Function{}
282-
err := k8sClient.Get(ctx, types.NamespacedName{Name: function.Name, Namespace: function.Namespace}, fn)
283-
g.Expect(err).NotTo(HaveOccurred())
284-
285-
for _, cond := range fn.Status.Conditions {
286-
if cond.Type == functionsdevv1alpha1.TypeDeployed {
287-
g.Expect(cond.Status).To(Equal(metav1.ConditionFalse))
288-
g.Expect(cond.Reason).To(Equal("NotDeployed"))
289-
g.Expect(cond.Message).To(ContainSubstring("Function not deployed yet"))
290-
return
291-
}
292-
}
293-
g.Expect(false).To(BeTrue(), "Deployed condition not found")
345+
Eventually(functionNotDeployed(functionName, functionNamespace), 2*time.Minute).Should(Succeed())
346+
})
347+
})
348+
Context("with a private repository", func() {
349+
var repoURL string
350+
var repoDir string
351+
var username, password, token string
352+
var functionName, functionNamespace string
353+
354+
BeforeEach(func() {
355+
// Create repository provider resources with automatic cleanup
356+
var cleanup func()
357+
var err error
358+
359+
username, password, _, cleanup, err = repoProvider.CreateRandomUser()
360+
Expect(err).NotTo(HaveOccurred())
361+
DeferCleanup(cleanup)
362+
363+
_, repoURL, cleanup, err = repoProvider.CreateRandomRepo(username, true) // private repo
364+
Expect(err).NotTo(HaveOccurred())
365+
DeferCleanup(cleanup)
366+
367+
// Create access token for the user
368+
token, err = repoProvider.CreateAccessToken(username, password, "e2e-token")
369+
Expect(err).NotTo(HaveOccurred())
370+
371+
// Initialize repository with function code
372+
repoDir, err = utils.InitializeRepoWithFunction(repoURL, username, password, "go")
373+
Expect(err).NotTo(HaveOccurred())
374+
DeferCleanup(os.RemoveAll, repoDir)
375+
376+
functionNamespace, err = utils.GetTestNamespace()
377+
Expect(err).NotTo(HaveOccurred())
378+
DeferCleanup(cleanupNamespaces, functionNamespace)
379+
380+
// Deploy function using func CLI
381+
out, err := utils.RunFunc("deploy",
382+
"--namespace", functionNamespace,
383+
"--path", repoDir,
384+
"--registry", registry,
385+
fmt.Sprintf("--registry-insecure=%t", registryInsecure))
386+
Expect(err).NotTo(HaveOccurred())
387+
_, _ = fmt.Fprint(GinkgoWriter, out)
388+
389+
// Cleanup func deployment
390+
DeferCleanup(func() {
391+
_, _ = utils.RunFunc("delete", "--path", repoDir, "--namespace", functionNamespace)
392+
})
393+
394+
// Commit func.yaml changes
395+
err = utils.CommitAndPush(repoDir, "Update func.yaml after deploy", "func.yaml")
396+
Expect(err).NotTo(HaveOccurred())
397+
})
398+
399+
AfterEach(func() {
400+
logFailedTestDetails(functionName, functionNamespace)
401+
402+
// Cleanup function resource
403+
if functionName != "" {
404+
cmd := exec.Command("kubectl", "delete", "function", functionName, "-n", functionNamespace, "--ignore-not-found")
405+
_, err := utils.Run(cmd)
406+
Expect(err).NotTo(HaveOccurred())
294407
}
408+
})
295409

296-
Eventually(funcBecomeReady, 2*time.Minute).Should(Succeed())
410+
Context("using token authentication", func() {
411+
It("should mark the function as ready when authSecretRef is provided", func() {
412+
// Create auth secret with token
413+
secret := &v1.Secret{
414+
ObjectMeta: metav1.ObjectMeta{
415+
GenerateName: "git-auth-",
416+
Namespace: functionNamespace,
417+
},
418+
Data: map[string][]byte{
419+
"token": []byte(token),
420+
},
421+
}
422+
err := k8sClient.Create(ctx, secret)
423+
Expect(err).NotTo(HaveOccurred())
424+
DeferCleanup(func() {
425+
_ = k8sClient.Delete(ctx, secret)
426+
})
427+
428+
// Create a Function resource with authSecretRef
429+
function := &functionsdevv1alpha1.Function{
430+
ObjectMeta: metav1.ObjectMeta{
431+
GenerateName: "my-private-function-",
432+
Namespace: functionNamespace,
433+
},
434+
Spec: functionsdevv1alpha1.FunctionSpec{
435+
Repository: functionsdevv1alpha1.FunctionSpecRepository{
436+
URL: repoURL,
437+
AuthSecretRef: &v1.LocalObjectReference{
438+
Name: secret.Name,
439+
},
440+
},
441+
},
442+
}
443+
444+
err = k8sClient.Create(ctx, function)
445+
Expect(err).NotTo(HaveOccurred())
446+
447+
functionName = function.Name
448+
449+
Eventually(functionBecomesReady(functionName, functionNamespace), 6*time.Minute).Should(Succeed())
450+
})
451+
452+
It("should fail with authentication error when authSecretRef is not provided", func() {
453+
// Create a Function resource WITHOUT authSecretRef for private repo
454+
function := &functionsdevv1alpha1.Function{
455+
ObjectMeta: metav1.ObjectMeta{
456+
GenerateName: "my-private-function-noauth-",
457+
Namespace: functionNamespace,
458+
},
459+
Spec: functionsdevv1alpha1.FunctionSpec{
460+
Repository: functionsdevv1alpha1.FunctionSpecRepository{
461+
URL: repoURL,
462+
// No AuthSecretRef
463+
},
464+
},
465+
}
466+
467+
err := k8sClient.Create(ctx, function)
468+
Expect(err).NotTo(HaveOccurred())
469+
470+
functionName = function.Name
471+
472+
Eventually(functionNotReadyWithAuthError(functionName, functionNamespace), 2*time.Minute).Should(Succeed())
473+
})
474+
})
475+
476+
Context("using username/password authentication", func() {
477+
It("should mark the function as ready when authSecretRef is provided", func() {
478+
// Create auth secret with username and password
479+
secret := &v1.Secret{
480+
ObjectMeta: metav1.ObjectMeta{
481+
GenerateName: "git-auth-",
482+
Namespace: functionNamespace,
483+
},
484+
Data: map[string][]byte{
485+
"username": []byte(username),
486+
"password": []byte(password),
487+
},
488+
}
489+
err := k8sClient.Create(ctx, secret)
490+
Expect(err).NotTo(HaveOccurred())
491+
DeferCleanup(func() {
492+
_ = k8sClient.Delete(ctx, secret)
493+
})
494+
495+
// Create a Function resource with authSecretRef
496+
function := &functionsdevv1alpha1.Function{
497+
ObjectMeta: metav1.ObjectMeta{
498+
GenerateName: "my-private-function-",
499+
Namespace: functionNamespace,
500+
},
501+
Spec: functionsdevv1alpha1.FunctionSpec{
502+
Repository: functionsdevv1alpha1.FunctionSpecRepository{
503+
URL: repoURL,
504+
AuthSecretRef: &v1.LocalObjectReference{
505+
Name: secret.Name,
506+
},
507+
},
508+
},
509+
}
510+
511+
err = k8sClient.Create(ctx, function)
512+
Expect(err).NotTo(HaveOccurred())
513+
514+
functionName = function.Name
515+
516+
Eventually(functionBecomesReady(functionName, functionNamespace), 6*time.Minute).Should(Succeed())
517+
})
518+
519+
It("should fail with authentication error when authSecretRef is not provided", func() {
520+
// Create a Function resource WITHOUT authSecretRef for private repo
521+
function := &functionsdevv1alpha1.Function{
522+
ObjectMeta: metav1.ObjectMeta{
523+
GenerateName: "my-private-function-noauth-",
524+
Namespace: functionNamespace,
525+
},
526+
Spec: functionsdevv1alpha1.FunctionSpec{
527+
Repository: functionsdevv1alpha1.FunctionSpecRepository{
528+
URL: repoURL,
529+
// No AuthSecretRef
530+
},
531+
},
532+
}
533+
534+
err := k8sClient.Create(ctx, function)
535+
Expect(err).NotTo(HaveOccurred())
536+
537+
functionName = function.Name
538+
539+
Eventually(functionNotReadyWithAuthError(functionName, functionNamespace), 2*time.Minute).Should(Succeed())
540+
})
297541
})
298542
})
299543
})

0 commit comments

Comments
 (0)