Skip to content

Commit cdb859b

Browse files
committed
Add end-to-end verification of middleware update via skopeo
- Use skopeo to inspect OCI image labels and verify middleware-version - Initial image (v1.20.0 deploy) has no middleware-version label - Updated image (operator redeploy) has middleware-version set - Handle image references with both tag and digest (remove tag for skopeo) - Test now fully verifies the core operator feature end-to-end Why skopeo instead of func describe: - func describe uses go-containerregistry to read middleware-version from image - kind-registry is a Docker container name, not a real hostname - Docker CLI can resolve it (func deploy works), but go-containerregistry cannot - func describe silently fails to populate middleware.version field - skopeo with localhost:5001 successfully inspects the image labels
1 parent 279d70b commit cdb859b

1 file changed

Lines changed: 122 additions & 14 deletions

File tree

test/e2e/func_middleware_update_test.go

Lines changed: 122 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,23 @@ limitations under the License.
1717
package e2e
1818

1919
import (
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

3439
var _ = 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

Comments
 (0)