Skip to content

Commit e77aad8

Browse files
add middleware version as image label (#3410)
Co-authored-by: David Fridrich <fridrich.david19@gmail.com>
1 parent 7d24048 commit e77aad8

5 files changed

Lines changed: 248 additions & 8 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//go:build integration
2+
3+
package builders_test
4+
5+
import (
6+
"context"
7+
"testing"
8+
"time"
9+
10+
"github.com/docker/docker/client"
11+
12+
"knative.dev/func/pkg/buildpacks"
13+
fn "knative.dev/func/pkg/functions"
14+
"knative.dev/func/pkg/s2i"
15+
)
16+
17+
// Scaffolder scaffolds a function for building
18+
type Scaffolder interface {
19+
Scaffold(ctx context.Context, f fn.Function, path string) error
20+
}
21+
22+
// Builder builds a function image
23+
type Builder interface {
24+
Build(ctx context.Context, f fn.Function, platforms []fn.Platform) error
25+
}
26+
27+
// TestInt_MiddlewareLabels verifies that the middleware-version label is set
28+
// on function images built by each builder type.
29+
func TestInt_MiddlewareLabels(t *testing.T) {
30+
tests := []struct {
31+
name string
32+
timeout time.Duration
33+
scaffolder Scaffolder
34+
builder Builder
35+
}{
36+
{
37+
name: "s2i",
38+
timeout: 5 * time.Minute,
39+
scaffolder: s2i.NewScaffolder(true),
40+
builder: s2i.NewBuilder(s2i.WithVerbose(true)),
41+
},
42+
{
43+
name: "buildpacks",
44+
timeout: 10 * time.Minute,
45+
scaffolder: buildpacks.NewScaffolder(true),
46+
builder: buildpacks.NewBuilder(buildpacks.WithVerbose(true)),
47+
},
48+
}
49+
50+
for _, tt := range tests {
51+
t.Run(tt.name, func(t *testing.T) {
52+
f := initFunction(t, "test-"+tt.name+"-labels")
53+
ctx, cancel := context.WithTimeout(context.Background(), tt.timeout)
54+
defer cancel()
55+
56+
if err := tt.scaffolder.Scaffold(ctx, f, ""); err != nil {
57+
t.Fatal(err)
58+
}
59+
if err := tt.builder.Build(ctx, f, nil); err != nil {
60+
t.Fatal(err)
61+
}
62+
63+
assertMiddlewareLabel(t, ctx, f.Build.Image)
64+
})
65+
}
66+
}
67+
68+
func initFunction(t *testing.T, name string) fn.Function {
69+
t.Helper()
70+
f := fn.Function{
71+
Name: name,
72+
Root: t.TempDir(),
73+
Runtime: "go",
74+
Registry: "localhost:50000",
75+
}
76+
f, err := fn.New().Init(f)
77+
if err != nil {
78+
t.Fatal(err)
79+
}
80+
f.Build.Image = "localhost:50000/" + name + ":latest"
81+
return f
82+
}
83+
84+
func assertMiddlewareLabel(t *testing.T, ctx context.Context, image string) {
85+
t.Helper()
86+
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
87+
if err != nil {
88+
t.Fatal(err)
89+
}
90+
defer cli.Close()
91+
92+
inspect, _, err := cli.ImageInspectWithRaw(ctx, image)
93+
if err != nil {
94+
t.Fatalf("failed to inspect image %s: %v", image, err)
95+
}
96+
97+
middlewareVersion, ok := inspect.Config.Labels[fn.MiddlewareVersionLabelKey]
98+
if !ok {
99+
t.Fatalf("label %q not found in image. Labels: %v", fn.MiddlewareVersionLabelKey, inspect.Config.Labels)
100+
}
101+
if middlewareVersion == "" {
102+
t.Fatalf("label %q is empty", fn.MiddlewareVersionLabelKey)
103+
}
104+
t.Logf("middleware-version label: %s", middlewareVersion)
105+
}

pkg/buildpacks/builder.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"knative.dev/func/pkg/builders"
2424
"knative.dev/func/pkg/docker"
2525
fn "knative.dev/func/pkg/functions"
26+
"knative.dev/func/pkg/scaffolding"
2627
)
2728

2829
// DefaultName when no WithName option is provided to NewBuilder
@@ -191,6 +192,15 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf
191192
opts.Env["BP_GO_WORKDIR"] = ".func/build"
192193
}
193194

195+
// Get middleware version and set as image label via BP_IMAGE_LABELS
196+
middlewareVersion, err := scaffolding.MiddlewareVersion(f.Root, f.Runtime, f.Invoke, fn.EmbeddedTemplatesFS)
197+
if err != nil {
198+
return fmt.Errorf("cannot get middleware version: %w", err)
199+
}
200+
if middlewareVersion != "" {
201+
opts.Env["BP_IMAGE_LABELS"] = fmt.Sprintf("%s=%s", fn.MiddlewareVersionLabelKey, middlewareVersion)
202+
}
203+
194204
var bindings = make([]string, 0, len(f.Build.Mounts))
195205
for _, m := range f.Build.Mounts {
196206
bindings = append(bindings, fmt.Sprintf("%s:%s", m.Source, m.Destination))

pkg/buildpacks/builder_test.go

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package buildpacks
22

33
import (
44
"context"
5+
"fmt"
56
"os"
67
"path/filepath"
78
"reflect"
@@ -10,6 +11,8 @@ import (
1011
pack "github.com/buildpacks/pack/pkg/client"
1112
"knative.dev/func/pkg/builders"
1213
fn "knative.dev/func/pkg/functions"
14+
"knative.dev/func/pkg/scaffolding"
15+
. "knative.dev/func/pkg/testing"
1316
)
1417

1518
// TestBuild_BuilderImageUntrusted ensures that only known builder images
@@ -83,12 +86,21 @@ func TestBuild_BuilderImageDefault(t *testing.T) {
8386
// defined in-code, but none defined on the function, the defaults will be
8487
// used.
8588
func TestBuild_BuildpacksDefault(t *testing.T) {
89+
root, done := Mktemp(t)
90+
defer done()
91+
8692
var (
87-
i = &mockImpl{}
88-
b = NewBuilder(WithImpl(i))
89-
f = fn.Function{Runtime: "go"}
93+
i = &mockImpl{}
94+
b = NewBuilder(WithImpl(i))
95+
f = fn.Function{Runtime: "go", Root: root, Registry: "example.com/alice"}
96+
err error
9097
)
9198

99+
// Initialize the function to create proper source files
100+
if f, err = fn.New().Init(f); err != nil {
101+
t.Fatal(err)
102+
}
103+
92104
i.BuildFn = func(ctx context.Context, opts pack.BuildOptions) error {
93105
expected := defaultBuildpacks["go"]
94106
if !reflect.DeepEqual(expected, opts.Buildpacks) {
@@ -136,23 +148,32 @@ func TestBuild_BuilderImageConfigurable(t *testing.T) {
136148
// TestBuild_BuilderImageExclude ensures that ignored files are not added to the func
137149
// image
138150
func TestBuild_BuilderImageExclude(t *testing.T) {
151+
root, done := Mktemp(t)
152+
defer done()
153+
139154
var (
140155
i = &mockImpl{} // mock underlying implementation
141156
b = NewBuilder( // Func Builder logic
142157
WithName(builders.Pack), WithImpl(i))
143158
f = fn.Function{
144-
Runtime: "go",
159+
Runtime: "go",
160+
Root: root,
161+
Registry: "example.com/alice",
145162
}
163+
err error
146164
)
165+
166+
// Initialize the function to create proper source files
167+
if f, err = fn.New().Init(f); err != nil {
168+
t.Fatal(err)
169+
}
170+
147171
funcIgnoreContent := []byte(`#testing comments
148172
hello.txt`)
149173
expected := []string{"hello.txt"}
150174

151-
tempdir := t.TempDir()
152-
f.Root = tempdir
153-
154175
//create a .funcignore file containing the details of the files to be ignored
155-
err := os.WriteFile(filepath.Join(f.Root, ".funcignore"), funcIgnoreContent, 0644)
176+
err = os.WriteFile(filepath.Join(f.Root, ".funcignore"), funcIgnoreContent, 0644)
156177
if err != nil {
157178
t.Fatal(err)
158179
}
@@ -204,6 +225,57 @@ func TestBuild_Envs(t *testing.T) {
204225
}
205226
}
206227

228+
// TestBuild_MiddlewareLabel ensures that the middleware-version label is set
229+
// on the build options for runtimes that support scaffolding.
230+
func TestBuild_MiddlewareLabel(t *testing.T) {
231+
root, done := Mktemp(t)
232+
defer done()
233+
234+
var (
235+
i = &mockImpl{}
236+
b = NewBuilder(WithImpl(i))
237+
f = fn.Function{
238+
Name: "test-middleware-label",
239+
Root: root,
240+
Runtime: "go",
241+
Registry: "example.com/alice",
242+
}
243+
err error
244+
)
245+
246+
// Initialize the function to create proper source files
247+
if f, err = fn.New().Init(f); err != nil {
248+
t.Fatal(err)
249+
}
250+
251+
// Get expected middleware version
252+
expectedVersion, err := scaffolding.MiddlewareVersion(f.Root, f.Runtime, f.Invoke, fn.EmbeddedTemplatesFS)
253+
if err != nil {
254+
t.Fatalf("failed to get expected middleware version: %v", err)
255+
}
256+
if expectedVersion == "" {
257+
t.Fatal("expected middleware version to be non-empty for go runtime")
258+
}
259+
260+
expectedLabel := fmt.Sprintf("%s=%s", fn.MiddlewareVersionLabelKey, expectedVersion)
261+
262+
i.BuildFn = func(ctx context.Context, opts pack.BuildOptions) error {
263+
bpLabels, ok := opts.Env["BP_IMAGE_LABELS"]
264+
if !ok {
265+
t.Fatal("expected BP_IMAGE_LABELS to be set")
266+
}
267+
if bpLabels != expectedLabel {
268+
t.Fatalf("expected BP_IMAGE_LABELS to be %q, got: %q", expectedLabel, bpLabels)
269+
}
270+
t.Logf("BP_IMAGE_LABELS: %s", bpLabels)
271+
return nil
272+
}
273+
274+
if err := b.Build(context.Background(), f, nil); err != nil {
275+
t.Fatal(err)
276+
}
277+
}
278+
207279
// TestBuild_Errors confirms error scenarios.
208280
func TestBuild_Errors(t *testing.T) {
209281
testCases := []struct {

pkg/s2i/builder.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"knative.dev/func/pkg/builders"
2222
"knative.dev/func/pkg/docker"
2323
fn "knative.dev/func/pkg/functions"
24+
"knative.dev/func/pkg/scaffolding"
2425
)
2526

2627
// DefaultName when no WithName option is provided to NewBuilder
@@ -173,6 +174,15 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf
173174
ExcludeRegExp: "(^|/)\\.git|\\.env|\\.func|node_modules(/|$)",
174175
}
175176

177+
// Set middleware version label
178+
middlewareVersion, err := scaffolding.MiddlewareVersion(f.Root, f.Runtime, f.Invoke, fn.EmbeddedTemplatesFS)
179+
if err != nil {
180+
return fmt.Errorf("cannot get middleware version: %w", err)
181+
}
182+
if middlewareVersion != "" {
183+
cfg.Labels = map[string]string{fn.MiddlewareVersionLabelKey: middlewareVersion}
184+
}
185+
176186
// Environment variables
177187
// Build Envs have local env var references interpolated then added to the
178188
// config as an S2I EnvironmentList struct

pkg/s2i/builder_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,49 @@ func Test_BuildEnvs(t *testing.T) {
210210
}
211211
}
212212

213+
// Test_MiddlewareLabel ensures that the middleware-version label is set
214+
// on the S2I build config for runtimes that support scaffolding.
215+
func Test_MiddlewareLabel(t *testing.T) {
216+
root, done := Mktemp(t)
217+
defer done()
218+
219+
f := fn.Function{
220+
Name: "test-middleware-label",
221+
Root: root,
222+
Runtime: "go",
223+
Registry: "example.com/alice",
224+
}
225+
226+
var err error
227+
if f, err = fn.New().Init(f); err != nil {
228+
t.Fatal(err)
229+
}
230+
231+
i := &mockImpl{}
232+
c := mockDocker{}
233+
b := s2i.NewBuilder(s2i.WithImpl(i), s2i.WithDockerClient(c))
234+
235+
i.BuildFn = func(cfg *api.Config) (*api.Result, error) {
236+
// Verify middleware-version label is set
237+
if cfg.Labels == nil {
238+
t.Fatal("expected Labels to be set on config")
239+
}
240+
middlewareVersion, ok := cfg.Labels[fn.MiddlewareVersionLabelKey]
241+
if !ok {
242+
t.Fatalf("expected label %q to be set", fn.MiddlewareVersionLabelKey)
243+
}
244+
if middlewareVersion == "" {
245+
t.Fatalf("expected label %q to have a non-empty value", fn.MiddlewareVersionLabelKey)
246+
}
247+
t.Logf("middleware-version label: %s", middlewareVersion)
248+
return nil, nil
249+
}
250+
251+
if err := b.Build(context.Background(), f, nil); err != nil {
252+
t.Fatal(err)
253+
}
254+
}
255+
213256
func TestBuildFail(t *testing.T) {
214257
cli := mockDocker{
215258
inspect: func(ctx context.Context, image string) (typesImage.InspectResponse, []byte, error) {

0 commit comments

Comments
 (0)