@@ -17,18 +17,23 @@ limitations under the License.
1717package e2e
1818
1919import (
20+ "encoding/json"
2021 "fmt"
2122 "os"
2223 "os/exec"
2324 "strconv"
25+ "strings"
2426 "time"
2527
2628 functionsdevv1alpha1 "github.com/functions-dev/func-operator/api/v1alpha1"
29+ "github.com/functions-dev/func-operator/internal/function"
2730 "github.com/functions-dev/func-operator/test/utils"
2831 . "github.com/onsi/ginkgo/v2"
2932 . "github.com/onsi/gomega"
33+ "gopkg.in/yaml.v3"
3034 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3135 "k8s.io/apimachinery/pkg/types"
36+ funcfn "knative.dev/func/pkg/functions"
3237)
3338
3439var _ = Describe ("Middleware Update" , func () {
@@ -113,10 +118,70 @@ var _ = Describe("Middleware Update", func() {
113118 })
114119
115120 It ("should update the middleware and mark the function as ready" , func () {
116- // Note: We can't reliably check middleware version via func describe in e2e tests
117- // because func describe requires inspecting OCI image labels, which doesn't work
118- // with insecure registries (kind-registry:5000).
119- // Instead, we'll verify the middleware update by checking the CR conditions.
121+ // Get function metadata to retrieve the deployed function name
122+ funcMetadata , err := function .Metadata (repoDir )
123+ Expect (err ).NotTo (HaveOccurred ())
124+ deployedFunctionName := funcMetadata .Name
125+
126+ // NOTE: We use skopeo to verify the middleware version because func describe
127+ // cannot access the image registry in our test environment.
128+ //
129+ // func describe calls MiddlewareVersion() which uses go-containerregistry's
130+ // remote.Get() to fetch the image manifest and read the middleware-version label.
131+ // However, the image reference is kind-registry:5000/..., where "kind-registry"
132+ // is a Docker container name, not a real hostname.
133+ //
134+ // Docker CLI can resolve container names on Docker networks (which is why
135+ // func deploy --registry kind-registry:5000 works for pushing images), but
136+ // go-containerregistry is not Docker-aware and tries regular DNS resolution,
137+ // which fails. The error is silently ignored in describers.
138+ //
139+ // We use skopeo with localhost:5001 (port-forward to the registry) to
140+ // directly inspect the OCI image labels and verify the middleware was updated.
141+
142+ // Get initial image digest from func describe (deployed with old v1.20.0)
143+ out , err := utils .RunFunc ("describe" , deployedFunctionName , "-n" , functionNamespace , "-o" , "yaml" )
144+ Expect (err ).NotTo (HaveOccurred ())
145+
146+ var initialInstance funcfn.Instance
147+ err = yaml .Unmarshal ([]byte (out ), & initialInstance )
148+ Expect (err ).NotTo (HaveOccurred ())
149+
150+ initialImage := initialInstance .Image
151+ Expect (initialImage ).NotTo (BeEmpty (), "Initial image should be available from func describe" )
152+ _ , _ = fmt .Fprintf (GinkgoWriter , "Initial image (deployed with v1.20.0): %s\n " , initialImage )
153+
154+ // Verify initial image has no middleware-version label (old func CLI)
155+ initialImageLocal := strings .Replace (initialImage , "kind-registry:5000" , "localhost:5001" , 1 )
156+ // Remove tag if both tag and digest are present (skopeo doesn't support this format)
157+ if strings .Contains (initialImageLocal , "@" ) {
158+ atIndex := strings .Index (initialImageLocal , "@" )
159+ slashIndex := strings .LastIndex (initialImageLocal [:atIndex ], "/" )
160+ if slashIndex != - 1 {
161+ betweenSlashAndAt := initialImageLocal [slashIndex + 1 : atIndex ]
162+ if strings .Contains (betweenSlashAndAt , ":" ) {
163+ colonIndex := strings .Index (betweenSlashAndAt , ":" )
164+ initialImageLocal = initialImageLocal [:slashIndex + 1 + colonIndex ] + initialImageLocal [atIndex :]
165+ }
166+ }
167+ }
168+ cmd := exec .Command ("skopeo" ,
169+ "inspect" ,
170+ "--tls-verify=false" ,
171+ "--no-tags" ,
172+ "docker://" + initialImageLocal )
173+ skopeoOutput , err := utils .Run (cmd )
174+ Expect (err ).NotTo (HaveOccurred ())
175+
176+ var initialImageLabels struct {
177+ Labels map [string ]string `json:"Labels"`
178+ }
179+ err = json .Unmarshal ([]byte (skopeoOutput ), & initialImageLabels )
180+ Expect (err ).NotTo (HaveOccurred ())
181+
182+ initialMiddlewareVersion := initialImageLabels .Labels ["middleware-version" ]
183+ _ , _ = fmt .Fprintf (GinkgoWriter , "Initial middleware-version label: '%s' (expected empty for v1.20.0)\n " ,
184+ initialMiddlewareVersion )
120185
121186 // Create a Function resource
122187 fn := & functionsdevv1alpha1.Function {
@@ -135,7 +200,7 @@ var _ = Describe("Middleware Update", func() {
135200 },
136201 }
137202
138- err : = k8sClient .Create (ctx , fn )
203+ err = k8sClient .Create (ctx , fn )
139204 Expect (err ).NotTo (HaveOccurred ())
140205
141206 functionName = fn .Name
@@ -157,26 +222,69 @@ var _ = Describe("Middleware Update", func() {
157222 // Middleware update could take a bit longer therefore give more time
158223 Eventually (funcBecomeReady , 6 * time .Minute ).Should (Succeed ())
159224
160- // Verify middleware was updated by checking the MiddlewareUpToDate condition
225+ // Verify middleware was actually updated by inspecting the new image
226+ out , err = utils .RunFunc ("describe" , deployedFunctionName , "-n" , functionNamespace , "-o" , "yaml" )
227+ Expect (err ).NotTo (HaveOccurred ())
228+
229+ var updatedInstance funcfn.Instance
230+ err = yaml .Unmarshal ([]byte (out ), & updatedInstance )
231+ Expect (err ).NotTo (HaveOccurred ())
232+
233+ updatedImage := updatedInstance .Image
234+ Expect (updatedImage ).NotTo (BeEmpty (), "Updated image should be available from func describe" )
235+ _ , _ = fmt .Fprintf (GinkgoWriter , "Updated image (redeployed by operator): %s\n " , updatedImage )
236+
237+ // Verify the image actually changed
238+ Expect (updatedImage ).NotTo (Equal (initialImage ), "Image should have changed after operator redeploy" )
239+
240+ // Verify updated image has middleware-version label set
241+ updatedImageLocal := strings .Replace (updatedImage , "kind-registry:5000" , "localhost:5001" , 1 )
242+ // Remove tag if both tag and digest are present (skopeo doesn't support this format)
243+ // Format: registry/name:tag@digest -> registry/name@digest
244+ if strings .Contains (updatedImageLocal , "@" ) {
245+ atIndex := strings .Index (updatedImageLocal , "@" )
246+ slashIndex := strings .LastIndex (updatedImageLocal [:atIndex ], "/" )
247+ if slashIndex != - 1 {
248+ // Check if there's a colon between last slash and @
249+ betweenSlashAndAt := updatedImageLocal [slashIndex + 1 : atIndex ]
250+ if strings .Contains (betweenSlashAndAt , ":" ) {
251+ // Remove the :tag part
252+ colonIndex := strings .Index (betweenSlashAndAt , ":" )
253+ updatedImageLocal = updatedImageLocal [:slashIndex + 1 + colonIndex ] + updatedImageLocal [atIndex :]
254+ }
255+ }
256+ }
257+ cmd = exec .Command ("skopeo" , "inspect" , "--tls-verify=false" , "--no-tags" , "docker://" + updatedImageLocal )
258+ skopeoOutput , err = utils .Run (cmd )
259+ Expect (err ).NotTo (HaveOccurred ())
260+
261+ var updatedImageLabels struct {
262+ Labels map [string ]string `json:"Labels"`
263+ }
264+ err = json .Unmarshal ([]byte (skopeoOutput ), & updatedImageLabels )
265+ Expect (err ).NotTo (HaveOccurred ())
266+
267+ updatedMiddlewareVersion := updatedImageLabels .Labels ["middleware-version" ]
268+ _ , _ = fmt .Fprintf (GinkgoWriter , "Updated middleware-version label: '%s'\n " , updatedMiddlewareVersion )
269+
270+ // The operator should have set a middleware version
271+ Expect (updatedMiddlewareVersion ).NotTo (BeEmpty (), "Operator should have deployed with middleware-version label set" )
272+
273+ // Verify MiddlewareUpToDate condition is True
161274 updatedFunction := & functionsdevv1alpha1.Function {}
162275 err = k8sClient .Get (ctx , types.NamespacedName {Name : fn .Name , Namespace : fn .Namespace }, updatedFunction )
163276 Expect (err ).NotTo (HaveOccurred ())
164277
165- // Check that MiddlewareUpToDate condition is now True
166278 var middlewareUpToDate bool
167- var middlewareCondition metav1.Condition
168279 for _ , cond := range updatedFunction .Status .Conditions {
169280 if cond .Type == functionsdevv1alpha1 .TypeMiddlewareUpToDate {
170281 middlewareUpToDate = cond .Status == metav1 .ConditionTrue
171- middlewareCondition = cond
282+ _ , _ = fmt .Fprintf (GinkgoWriter , "MiddlewareUpToDate condition: status=%s, reason=%s\n " ,
283+ cond .Status , cond .Reason )
172284 break
173285 }
174286 }
175- _ , _ = fmt .Fprintf (GinkgoWriter , "MiddlewareUpToDate condition: status=%s, reason=%s\n " ,
176- middlewareCondition .Status , middlewareCondition .Reason )
177-
178- Expect (middlewareUpToDate ).To (BeTrue (),
179- "MiddlewareUpToDate condition should be True after operator redeploys" )
287+ Expect (middlewareUpToDate ).To (BeTrue (), "MiddlewareUpToDate condition should be True" )
180288 })
181289 })
182290})
0 commit comments