@@ -30,7 +30,10 @@ import (
3030 . "github.com/onsi/ginkgo/v2"
3131 . "github.com/onsi/gomega"
3232 "gopkg.in/yaml.v3"
33+ v1 "k8s.io/api/core/v1"
3334 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35+ "k8s.io/apimachinery/pkg/types"
36+ "k8s.io/utils/ptr"
3437 funcfn "knative.dev/func/pkg/functions"
3538)
3639
@@ -241,4 +244,328 @@ var _ = Describe("Middleware Update", func() {
241244 Eventually (functionMiddlewareUpToDate (functionName , functionNamespace ), 2 * time .Minute ).Should (Succeed ())
242245 })
243246 })
247+
248+ Context ("when ConfigMap autoUpdateMiddleware setting changes" , func () {
249+ Skip ("Skip for now, as the old used CLI for this test (1.20.1), does not have " +
250+ "https://github.com/knative/func/pull/3490 yet" )
251+
252+ const (
253+ operatorNamespace = "func-operator-system"
254+ controllerConfigName = "func-operator-controller-config"
255+ )
256+
257+ var repoURL string
258+ var repoDir string
259+ var functionName , functionNamespace string
260+ var originalConfigMapData map [string ]string
261+
262+ BeforeEach (func () {
263+ var err error
264+
265+ // Save original ConfigMap data to restore later
266+ cm := & v1.ConfigMap {}
267+ err = k8sClient .Get (ctx , types.NamespacedName {
268+ Name : controllerConfigName ,
269+ Namespace : operatorNamespace ,
270+ }, cm )
271+ Expect (err ).NotTo (HaveOccurred ())
272+ originalConfigMapData = make (map [string ]string )
273+ for k , v := range cm .Data {
274+ originalConfigMapData [k ] = v
275+ }
276+
277+ // Restore original ConfigMap data on cleanup
278+ DeferCleanup (func () {
279+ By ("Restoring original ConfigMap data" )
280+ cm := & v1.ConfigMap {}
281+ err := k8sClient .Get (ctx , types.NamespacedName {
282+ Name : controllerConfigName ,
283+ Namespace : operatorNamespace ,
284+ }, cm )
285+ Expect (err ).NotTo (HaveOccurred ())
286+
287+ cm .Data = originalConfigMapData
288+ err = k8sClient .Update (ctx , cm )
289+ Expect (err ).NotTo (HaveOccurred ())
290+ })
291+
292+ // Create repository provider resources with automatic cleanup
293+ username , password , _ , cleanup , err := repoProvider .CreateRandomUser ()
294+ Expect (err ).NotTo (HaveOccurred ())
295+ DeferCleanup (cleanup )
296+
297+ _ , repoURL , cleanup , err = repoProvider .CreateRandomRepo (username , false )
298+ Expect (err ).NotTo (HaveOccurred ())
299+ DeferCleanup (cleanup )
300+
301+ functionNamespace , err = utils .GetTestNamespace ()
302+ Expect (err ).NotTo (HaveOccurred ())
303+ DeferCleanup (cleanupNamespaces , functionNamespace )
304+
305+ // Initialize repository with function code using OLD func CLI version
306+ // to ensure middleware will be outdated
307+ oldFuncVersion := "v1.20.1"
308+ repoDir , err = utils .InitializeRepoWithFunction (
309+ repoURL ,
310+ username ,
311+ password ,
312+ "go" ,
313+ utils .WithCliVersion (oldFuncVersion ))
314+ Expect (err ).NotTo (HaveOccurred ())
315+ DeferCleanup (os .RemoveAll , repoDir )
316+
317+ // Deploy function using the same OLD func CLI version
318+ out , err := utils .RunFuncDeploy (repoDir ,
319+ utils .WithNamespace (functionNamespace ),
320+ utils .WithDeployCliVersion (oldFuncVersion ))
321+ Expect (err ).NotTo (HaveOccurred ())
322+ _ , _ = fmt .Fprint (GinkgoWriter , out )
323+
324+ // Cleanup func deployment
325+ DeferCleanup (func () {
326+ _ , _ = utils .RunFunc ("delete" , "--path" , repoDir , "--namespace" , functionNamespace )
327+ })
328+
329+ // Commit func.yaml changes
330+ err = utils .CommitAndPush (repoDir , "Update func.yaml after deploy" , "func.yaml" )
331+ Expect (err ).NotTo (HaveOccurred ())
332+ })
333+
334+ AfterEach (func () {
335+ logFailedTestDetails (functionName , functionNamespace )
336+
337+ // Cleanup function resource
338+ if functionName != "" {
339+ cmd := exec .Command ("kubectl" , "delete" , "function" , functionName , "-n" , functionNamespace , "--ignore-not-found" )
340+ _ , err := utils .Run (cmd )
341+ Expect (err ).NotTo (HaveOccurred ())
342+ }
343+ })
344+
345+ It ("should reconcile functions without explicit autoUpdateMiddleware when ConfigMap changes" , func () {
346+ // Set ConfigMap to disable middleware updates
347+ By ("Disabling middleware updates in ConfigMap" )
348+ cm := & v1.ConfigMap {}
349+ err := k8sClient .Get (ctx , types.NamespacedName {
350+ Name : controllerConfigName ,
351+ Namespace : operatorNamespace ,
352+ }, cm )
353+ Expect (err ).NotTo (HaveOccurred ())
354+
355+ cm .Data ["autoUpdateMiddleware" ] = "false"
356+ err = k8sClient .Update (ctx , cm )
357+ Expect (err ).NotTo (HaveOccurred ())
358+
359+ // Wait for the ConfigMap update to propagate
360+ time .Sleep (2 * time .Second )
361+
362+ // Create a Function resource WITHOUT autoUpdateMiddleware setting
363+ // (so it will use the operator default)
364+ fn := & functionsdevv1alpha1.Function {
365+ ObjectMeta : metav1.ObjectMeta {
366+ GenerateName : "my-function-" ,
367+ Namespace : functionNamespace ,
368+ },
369+ Spec : functionsdevv1alpha1.FunctionSpec {
370+ Repository : functionsdevv1alpha1.FunctionSpecRepository {
371+ URL : repoURL ,
372+ },
373+ // NOTE: autoUpdateMiddleware is intentionally not set,
374+ // so it will use the operator default (currently "false")
375+ },
376+ }
377+
378+ err = k8sClient .Create (ctx , fn )
379+ Expect (err ).NotTo (HaveOccurred ())
380+ functionName = fn .Name
381+
382+ By ("Waiting for Function to become ready with middleware updates disabled" )
383+ Eventually (functionBecomesReady (functionName , functionNamespace )).Should (Succeed ())
384+
385+ // Verify the MiddlewareUpToDate condition reflects that updates are disabled
386+ By ("Verifying middleware update is skipped due to ConfigMap setting" )
387+ Eventually (func (g Gomega ) {
388+ fn := & functionsdevv1alpha1.Function {}
389+ err := k8sClient .Get (ctx , types.NamespacedName {
390+ Name : functionName ,
391+ Namespace : functionNamespace ,
392+ }, fn )
393+ g .Expect (err ).NotTo (HaveOccurred ())
394+
395+ // Find the MiddlewareUpToDate condition
396+ for _ , cond := range fn .Status .Conditions {
397+ if cond .Type == functionsdevv1alpha1 .TypeMiddlewareUpToDate {
398+ // When middleware updates are disabled, the condition should be True
399+ // with reason "SkipMiddlewareUpdate"
400+ g .Expect (cond .Status ).To (Equal (metav1 .ConditionTrue ))
401+ g .Expect (cond .Reason ).To (Equal ("SkipMiddlewareUpdate" ))
402+ g .Expect (cond .Message ).To (ContainSubstring ("operator" ))
403+ return
404+ }
405+ }
406+ g .Expect (false ).To (BeTrue (), "MiddlewareUpToDate condition not found" )
407+ }, 1 * time .Minute ).Should (Succeed ())
408+
409+ // Get the initial image to compare later
410+ fnBefore := & functionsdevv1alpha1.Function {}
411+ err = k8sClient .Get (ctx , types.NamespacedName {
412+ Name : functionName ,
413+ Namespace : functionNamespace ,
414+ }, fnBefore )
415+ Expect (err ).NotTo (HaveOccurred ())
416+ imageBefore := fnBefore .Status .Deployment .Image
417+
418+ // Now enable middleware updates in the ConfigMap
419+ By ("Enabling middleware updates in ConfigMap" )
420+ err = k8sClient .Get (ctx , types.NamespacedName {
421+ Name : controllerConfigName ,
422+ Namespace : operatorNamespace ,
423+ }, cm )
424+ Expect (err ).NotTo (HaveOccurred ())
425+
426+ cm .Data ["autoUpdateMiddleware" ] = "true"
427+ err = k8sClient .Update (ctx , cm )
428+ Expect (err ).NotTo (HaveOccurred ())
429+
430+ // The Function should be automatically reconciled and middleware should be updated
431+ By ("Waiting for Function to be reconciled and middleware updated after ConfigMap change" )
432+ Eventually (func (g Gomega ) {
433+ fn := & functionsdevv1alpha1.Function {}
434+ err := k8sClient .Get (ctx , types.NamespacedName {
435+ Name : functionName ,
436+ Namespace : functionNamespace ,
437+ }, fn )
438+ g .Expect (err ).NotTo (HaveOccurred ())
439+
440+ // The image should have changed (middleware was updated)
441+ g .Expect (fn .Status .Deployment .Image ).NotTo (BeEmpty ())
442+ g .Expect (fn .Status .Deployment .Image ).NotTo (Equal (imageBefore ),
443+ "Image should have changed after middleware update" )
444+
445+ // The MiddlewareUpToDate condition should now indicate middleware is up to date
446+ for _ , cond := range fn .Status .Conditions {
447+ if cond .Type == functionsdevv1alpha1 .TypeMiddlewareUpToDate {
448+ g .Expect (cond .Status ).To (Equal (metav1 .ConditionTrue ))
449+ g .Expect (cond .Reason ).To (Equal ("MiddlewareUpToDate" ))
450+ return
451+ }
452+ }
453+ g .Expect (false ).To (BeTrue (), "MiddlewareUpToDate condition not found" )
454+ }, 5 * time .Minute ).Should (Succeed ())
455+
456+ Eventually (functionBecomesReady (functionName , functionNamespace )).Should (Succeed ())
457+ })
458+
459+ It ("should not reconcile functions with explicit autoUpdateMiddleware when ConfigMap changes" , func () {
460+ // Set ConfigMap to enable middleware updates
461+ By ("Setting ConfigMap to enable middleware updates" )
462+ cm := & v1.ConfigMap {}
463+ err := k8sClient .Get (ctx , types.NamespacedName {
464+ Name : controllerConfigName ,
465+ Namespace : operatorNamespace ,
466+ }, cm )
467+ Expect (err ).NotTo (HaveOccurred ())
468+
469+ cm .Data ["autoUpdateMiddleware" ] = "true"
470+ err = k8sClient .Update (ctx , cm )
471+ Expect (err ).NotTo (HaveOccurred ())
472+
473+ time .Sleep (2 * time .Second )
474+
475+ // Create a Function resource WITH explicit autoUpdateMiddleware=false
476+ // This should NOT be affected by ConfigMap changes
477+ fn := & functionsdevv1alpha1.Function {
478+ ObjectMeta : metav1.ObjectMeta {
479+ GenerateName : "my-function-explicit-" ,
480+ Namespace : functionNamespace ,
481+ },
482+ Spec : functionsdevv1alpha1.FunctionSpec {
483+ Repository : functionsdevv1alpha1.FunctionSpecRepository {
484+ URL : repoURL ,
485+ },
486+ // Explicitly set autoUpdateMiddleware
487+ AutoUpdateMiddleware : ptr .To (false ),
488+ },
489+ }
490+
491+ err = k8sClient .Create (ctx , fn )
492+ Expect (err ).NotTo (HaveOccurred ())
493+ functionName = fn .Name
494+
495+ By ("Waiting for Function to become ready" )
496+ Eventually (functionBecomesReady (functionName , functionNamespace )).Should (Succeed ())
497+
498+ // Verify the MiddlewareUpToDate condition shows updates are skipped
499+ By ("Verifying middleware update is skipped due to function setting" )
500+ Eventually (func (g Gomega ) {
501+ fn := & functionsdevv1alpha1.Function {}
502+ err := k8sClient .Get (ctx , types.NamespacedName {
503+ Name : functionName ,
504+ Namespace : functionNamespace ,
505+ }, fn )
506+ g .Expect (err ).NotTo (HaveOccurred ())
507+
508+ for _ , cond := range fn .Status .Conditions {
509+ if cond .Type == functionsdevv1alpha1 .TypeMiddlewareUpToDate {
510+ g .Expect (cond .Status ).To (Equal (metav1 .ConditionTrue ))
511+ g .Expect (cond .Reason ).To (Equal ("SkipMiddlewareUpdate" ))
512+ // Message should indicate the source is the function spec
513+ g .Expect (cond .Message ).To (ContainSubstring ("function" ))
514+ return
515+ }
516+ }
517+ g .Expect (false ).To (BeTrue (), "MiddlewareUpToDate condition not found" )
518+ }, 1 * time .Minute ).Should (Succeed ())
519+
520+ // Get the current image
521+ fnBefore := & functionsdevv1alpha1.Function {}
522+ err = k8sClient .Get (ctx , types.NamespacedName {
523+ Name : functionName ,
524+ Namespace : functionNamespace ,
525+ }, fnBefore )
526+ Expect (err ).NotTo (HaveOccurred ())
527+ imageBefore := fnBefore .Status .Deployment .Image
528+
529+ // Update ConfigMap to disable middleware updates
530+ // (opposite of the function's explicit setting, but should have no effect)
531+ By ("Changing ConfigMap to disable middleware updates" )
532+ err = k8sClient .Get (ctx , types.NamespacedName {
533+ Name : controllerConfigName ,
534+ Namespace : operatorNamespace ,
535+ }, cm )
536+ Expect (err ).NotTo (HaveOccurred ())
537+
538+ cm .Data ["autoUpdateMiddleware" ] = "false"
539+ err = k8sClient .Update (ctx , cm )
540+ Expect (err ).NotTo (HaveOccurred ())
541+
542+ // Wait a bit to ensure controller has time to process ConfigMap change
543+ time .Sleep (10 * time .Second )
544+
545+ // The Function should NOT be reconciled and image should not change
546+ By ("Verifying Function was not reconciled after ConfigMap change" )
547+ Consistently (func (g Gomega ) {
548+ fn := & functionsdevv1alpha1.Function {}
549+ err := k8sClient .Get (ctx , types.NamespacedName {
550+ Name : functionName ,
551+ Namespace : functionNamespace ,
552+ }, fn )
553+ g .Expect (err ).NotTo (HaveOccurred ())
554+
555+ // Image should not change
556+ g .Expect (fn .Status .Deployment .Image ).To (Equal (imageBefore ))
557+
558+ // Condition should still show updates are skipped with "function" source
559+ for _ , cond := range fn .Status .Conditions {
560+ if cond .Type == functionsdevv1alpha1 .TypeMiddlewareUpToDate {
561+ g .Expect (cond .Status ).To (Equal (metav1 .ConditionTrue ))
562+ g .Expect (cond .Reason ).To (Equal ("SkipMiddlewareUpdate" ))
563+ g .Expect (cond .Message ).To (ContainSubstring ("function" ))
564+ return
565+ }
566+ }
567+ g .Expect (false ).To (BeTrue (), "MiddlewareUpToDate condition not found" )
568+ }, 30 * time .Second , 5 * time .Second ).Should (Succeed ())
569+ })
570+ })
244571})
0 commit comments