@@ -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+
34127var _ = 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