From f8ca4acd830c3f86f082e7480f4a9dd233eefce8 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 11 Sep 2024 14:28:40 -0700 Subject: [PATCH] fix: Use correct key for setting main build context The signer implementation was not using standard docker keys for setting the main build context, this made it so you can't use signers as standalone frontends executing by `docker build`. This is a breaking change as anything implementing a signer will need to be updated to use the correct key. Signed-off-by: Brian Goff --- frontend/azlinux/handle_rpm.go | 2 +- frontend/jammy/handle_deb.go | 2 +- frontend/request.go | 37 ++++------ frontend/windows/handle_zip.go | 18 +++-- test/azlinux_test.go | 36 ++++++++- test/fixtures/signer/main.go | 57 ++++++++++----- test/jammy_test.go | 21 ++++++ test/signing_test.go | 65 +++++++++++++++-- test/windows_test.go | 129 ++++++++++++++------------------- 9 files changed, 234 insertions(+), 133 deletions(-) diff --git a/frontend/azlinux/handle_rpm.go b/frontend/azlinux/handle_rpm.go index 87e89f54..fe1a3eab 100644 --- a/frontend/azlinux/handle_rpm.go +++ b/frontend/azlinux/handle_rpm.go @@ -133,5 +133,5 @@ func specToRpmLLB(ctx context.Context, w worker, client gwclient.Client, spec *d specPath := filepath.Join("SPECS", spec.Name, spec.Name+".spec") st := rpm.Build(br, base, specPath, opts...) - return frontend.MaybeSign(ctx, client, st, spec, targetKey, sOpt) + return frontend.MaybeSign(ctx, client, st, spec, targetKey, sOpt, opts...) } diff --git a/frontend/jammy/handle_deb.go b/frontend/jammy/handle_deb.go index 96993f41..fb933ae2 100644 --- a/frontend/jammy/handle_deb.go +++ b/frontend/jammy/handle_deb.go @@ -96,7 +96,7 @@ func buildDeb(ctx context.Context, client gwclient.Client, spec *dalec.Spec, sOp return llb.Scratch(), err } - signed, err := frontend.MaybeSign(ctx, client, st, spec, targetKey, sOpt) + signed, err := frontend.MaybeSign(ctx, client, st, spec, targetKey, sOpt, opts...) if err != nil { return llb.Scratch(), err } diff --git a/frontend/request.go b/frontend/request.go index 5701fe7f..3c2aaf6c 100644 --- a/frontend/request.go +++ b/frontend/request.go @@ -2,7 +2,6 @@ package frontend import ( "context" - "fmt" "strconv" "github.com/Azure/dalec" @@ -141,9 +140,9 @@ func marshalDockerfile(ctx context.Context, dt []byte, opts ...llb.ConstraintsOp return st.Marshal(ctx) } -func getSigningConfigFromContext(ctx context.Context, client gwclient.Client, cfgPath string, configCtxName string, sOpt dalec.SourceOpts) (*dalec.PackageSigner, error) { +func getSigningConfigFromContext(ctx context.Context, client gwclient.Client, cfgPath string, configCtxName string, sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (*dalec.PackageSigner, error) { sc := dalec.SourceContext{Name: configCtxName} - signConfigState, err := sc.AsState([]string{cfgPath}, nil, sOpt) + signConfigState, err := sc.AsState([]string{cfgPath}, nil, sOpt, opts...) if err != nil { return nil, err } @@ -180,7 +179,7 @@ func getSigningConfigFromContext(ctx context.Context, client gwclient.Client, cf return pc.Signer, nil } -func MaybeSign(ctx context.Context, client gwclient.Client, st llb.State, spec *dalec.Spec, targetKey string, sOpt dalec.SourceOpts) (llb.State, error) { +func MaybeSign(ctx context.Context, client gwclient.Client, st llb.State, spec *dalec.Spec, targetKey string, sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.State, error) { if signingDisabled(client) { Warnf(ctx, client, st, "Signing disabled by build-arg %q", keySkipSigningArg) return st, nil @@ -198,7 +197,7 @@ func MaybeSign(ctx context.Context, client gwclient.Client, st llb.State, spec * Warnf(ctx, client, st, "Root signing spec overridden by target signing spec: target %q", targetKey) } - return forwardToSigner(ctx, client, cfg, st) + return forwardToSigner(ctx, client, cfg, st, opts...) } configCtxName := getSignContextNameWithDefault(client) @@ -211,7 +210,7 @@ func MaybeSign(ctx context.Context, client gwclient.Client, st llb.State, spec * return llb.Scratch(), err } - return forwardToSigner(ctx, client, cfg, st) + return forwardToSigner(ctx, client, cfg, st, opts...) } func getSignContextNameWithDefault(client gwclient.Client) string { @@ -245,21 +244,20 @@ func getSignConfigCtxName(client gwclient.Client) string { return client.BuildOpts().Opts["build-arg:"+buildArgDalecSigningConfigContextName] } -func forwardToSigner(ctx context.Context, client gwclient.Client, cfg *dalec.PackageSigner, s llb.State) (llb.State, error) { +func forwardToSigner(ctx context.Context, client gwclient.Client, cfg *dalec.PackageSigner, s llb.State, opts ...llb.ConstraintsOpt) (llb.State, error) { const ( - sourceKey = "source" - contextKey = "context" - inputKey = "input" + // See https://github.com/moby/buildkit/blob/d8d946b85c52095d34a52ce210960832f4e06775/frontend/dockerui/context.go#L29 + contextKey = "contextkey" ) - opts := client.BuildOpts().Opts + bopts := client.BuildOpts().Opts req, err := newSolveRequest(toFrontend(cfg.Frontend), withBuildArgs(cfg.Args)) if err != nil { return llb.Scratch(), err } - for k, v := range opts { + for k, v := range bopts { if k == "source" || k == "cmdline" { continue } @@ -282,18 +280,19 @@ func forwardToSigner(ctx context.Context, client gwclient.Client, cfg *dalec.Pac } req.FrontendInputs = m - stateDef, err := s.Marshal(ctx) + opts = append(opts, dalec.ProgressGroup("Sign package")) + stateDef, err := s.Marshal(ctx, opts...) if err != nil { return llb.Scratch(), err } - req.FrontendOpt[contextKey] = compound(inputKey, contextKey) - req.FrontendInputs[contextKey] = stateDef.ToPB() - req.FrontendOpt["dalec.target"] = opts["dalec.target"] + req.FrontendOpt[contextKey] = dockerui.DefaultLocalNameContext + req.FrontendInputs[dockerui.DefaultLocalNameContext] = stateDef.ToPB() + req.FrontendOpt["dalec.target"] = bopts["dalec.target"] res, err := client.Solve(ctx, req) if err != nil { - return llb.Scratch(), err + return llb.Scratch(), errors.Wrap(err, "error signing packages") } ref, err := res.SingleRef() @@ -303,7 +302,3 @@ func forwardToSigner(ctx context.Context, client gwclient.Client, cfg *dalec.Pac return ref.ToState() } - -func compound(k, v string) string { - return fmt.Sprintf("%s:%s", k, v) -} diff --git a/frontend/windows/handle_zip.go b/frontend/windows/handle_zip.go index cc95b05a..e52a8d77 100644 --- a/frontend/windows/handle_zip.go +++ b/frontend/windows/handle_zip.go @@ -38,12 +38,12 @@ func handleZip(ctx context.Context, client gwclient.Client) (*gwclient.Result, e return nil, nil, err } - bin, err := buildBinaries(ctx, spec, worker, client, sOpt, targetKey) + bin, err := buildBinaries(ctx, spec, worker, client, sOpt, targetKey, pg) if err != nil { return nil, nil, fmt.Errorf("unable to build binaries: %w", err) } - st := getZipLLB(worker, spec.Name, bin) + st := getZipLLB(worker, spec.Name, bin, pg) def, err := st.Marshal(ctx) if err != nil { @@ -82,7 +82,7 @@ func specToSourcesLLB(worker llb.State, spec *dalec.Spec, sOpt dalec.SourceOpts, return out, nil } -func installBuildDeps(deps []string) llb.StateOption { +func installBuildDeps(deps []string, opts ...llb.ConstraintsOpt) llb.StateOption { return func(s llb.State) llb.State { if len(deps) == 0 { return s @@ -94,6 +94,7 @@ func installBuildDeps(deps []string) llb.StateOption { return s.Run( dalec.ShArgs("apt-get update && apt-get install -y "+strings.Join(sorted, " ")), dalec.WithMountedAptCache(aptCachePrefix), + dalec.WithConstraints(opts...), ).Root() } } @@ -127,13 +128,13 @@ func withSourcesMounted(dst string, states map[string]llb.State, sources map[str return dalec.WithRunOptions(ordered...) } -func buildBinaries(ctx context.Context, spec *dalec.Spec, worker llb.State, client gwclient.Client, sOpt dalec.SourceOpts, targetKey string) (llb.State, error) { +func buildBinaries(ctx context.Context, spec *dalec.Spec, worker llb.State, client gwclient.Client, sOpt dalec.SourceOpts, targetKey string, opts ...llb.ConstraintsOpt) (llb.State, error) { deps := dalec.SortMapKeys(spec.GetBuildDeps(targetKey)) // note: we do not yet support pinning build dependencies for windows workers - worker = worker.With(installBuildDeps(deps)) + worker = worker.With(installBuildDeps(deps, opts...)) - sources, err := specToSourcesLLB(worker, spec, sOpt) + sources, err := specToSourcesLLB(worker, spec, sOpt, opts...) if err != nil { return llb.Scratch(), errors.Wrap(err, "could not generate sources") } @@ -149,17 +150,19 @@ func buildBinaries(ctx context.Context, spec *dalec.Spec, worker llb.State, clie withSourcesMounted("/build", patched, spec.Sources), llb.AddMount("/tmp/scripts", buildScript), llb.Network(llb.NetModeNone), + dalec.WithConstraints(opts...), ).AddMount(outputDir, llb.Scratch()) return frontend.MaybeSign(ctx, client, st, spec, targetKey, sOpt) } -func getZipLLB(worker llb.State, name string, artifacts llb.State) llb.State { +func getZipLLB(worker llb.State, name string, artifacts llb.State, opts ...llb.ConstraintsOpt) llb.State { outName := filepath.Join(outputDir, name+".zip") zipped := worker.Run( dalec.ShArgs("zip "+outName+" *"), llb.Dir("/tmp/artifacts"), llb.AddMount("/tmp/artifacts", artifacts), + dalec.WithConstraints(opts...), ).AddMount(outputDir, llb.Scratch()) return zipped } @@ -197,6 +200,7 @@ func workerImg(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.State, er Run( dalec.ShArgs("apt-get update && apt-get install -y build-essential binutils-mingw-w64 g++-mingw-w64-x86-64 gcc git make pkg-config quilt zip"), dalec.WithMountedAptCache(aptCachePrefix), + dalec.WithConstraints(opts...), ).Root(), nil } diff --git a/test/azlinux_test.go b/test/azlinux_test.go index f0e44372..6108cb8a 100644 --- a/test/azlinux_test.go +++ b/test/azlinux_test.go @@ -13,6 +13,7 @@ import ( "github.com/moby/buildkit/client/llb" gwclient "github.com/moby/buildkit/frontend/gateway/client" moby_buildkit_v1_frontend "github.com/moby/buildkit/frontend/gateway/pb" + ocispecs "github.com/opencontainers/image-spec/specs-go/v1" ) var azlinuxConstraints = constraintsSymbols{ @@ -35,6 +36,7 @@ func TestMariner2(t *testing.T) { FormatDepEqual: func(v, _ string) string { return v }, + ListExpectedSignFiles: azlinuxListSignFiles("cm2"), }, LicenseDir: "/usr/share/licenses", SystemdDir: struct { @@ -62,9 +64,10 @@ func TestAzlinux3(t *testing.T) { ctx := startTestSpan(baseCtx, t) testLinuxDistro(ctx, t, testLinuxConfig{ Target: targetConfig{ - Package: "azlinux3/rpm", - Container: "azlinux3/container", - Worker: "azlinux3/worker", + Package: "azlinux3/rpm", + Container: "azlinux3/container", + Worker: "azlinux3/worker", + ListExpectedSignFiles: azlinuxListSignFiles("azl3"), }, LicenseDir: "/usr/share/licenses", SystemdDir: struct { @@ -86,6 +89,27 @@ func TestAzlinux3(t *testing.T) { }) } +func azlinuxListSignFiles(ver string) func(*dalec.Spec, ocispecs.Platform) []string { + return func(spec *dalec.Spec, platform ocispecs.Platform) []string { + base := fmt.Sprintf("%s-%s-%s.%s", spec.Name, spec.Version, spec.Revision, ver) + + var arch string + switch platform.Architecture { + case "amd64": + arch = "x86_64" + case "arm64": + arch = "aarch64" + default: + arch = platform.Architecture + } + + return []string{ + filepath.Join("SRPMS", fmt.Sprintf("%s.src.rpm", base)), + filepath.Join("RPMS", arch, fmt.Sprintf("%s.%s.rpm", base, arch)), + } + } +} + func azlinuxWithRepo(rpms llb.State) llb.StateOption { return func(in llb.State) llb.State { localRepo := []byte(` @@ -145,6 +169,10 @@ type targetConfig struct { // what is neccessary for the target distro to set a dependency for an equals // operator. FormatDepEqual func(ver, rev string) string + + // Given a spec, list all files (including the full path) that are expected + // to be sent to be signed. + ListExpectedSignFiles func(*dalec.Spec, ocispecs.Platform) []string } type testLinuxConfig struct { @@ -491,7 +519,7 @@ echo "$BAR" > bar.txt }) }) - t.Run("test signing", linuxSigningTests(ctx, testConfig)) + t.Run("signing", linuxSigningTests(ctx, testConfig)) t.Run("test systemd unit single", func(t *testing.T) { t.Parallel() diff --git a/test/fixtures/signer/main.go b/test/fixtures/signer/main.go index fb4b1c46..3d6c7540 100644 --- a/test/fixtures/signer/main.go +++ b/test/fixtures/signer/main.go @@ -5,15 +5,18 @@ import ( _ "embed" "encoding/json" "fmt" + "io/fs" "os" "strings" - "github.com/Azure/dalec/frontend" + "github.com/Azure/dalec/frontend/pkg/bkfs" "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/frontend/dockerui" gwclient "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/frontend/gateway/grpcclient" "github.com/moby/buildkit/util/appcontext" "github.com/moby/buildkit/util/bklog" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "google.golang.org/grpc/grpclog" ) @@ -28,11 +31,6 @@ func main() { bopts := c.BuildOpts().Opts target := bopts["dalec.target"] - inputs, err := c.Inputs(ctx) - if err != nil { - return nil, err - } - type config struct { OS string } @@ -46,33 +44,56 @@ func main() { cfg.OS = "linux" } - curFrontend, ok := c.(frontend.CurrentFrontend) - if !ok { - return nil, fmt.Errorf("cast to currentFrontend failed") + dc, err := dockerui.NewClient(c) + if err != nil { + return nil, err } - basePtr, err := curFrontend.CurrentFrontend() - if err != nil || basePtr == nil { - if err == nil { - err = fmt.Errorf("base frontend ptr was nil") - } + bctx, err := dc.MainContext(ctx) + if err != nil { return nil, err } - inputId := strings.TrimPrefix(bopts["context"], "input:") - _, ok = inputs[inputId] - if !ok { + if bctx == nil { return nil, fmt.Errorf("no artifact state provided to signer") } + artifactsFS, err := bkfs.FromState(ctx, bctx, c) + if err != nil { + return nil, err + } + configBytes, err := json.Marshal(&cfg) if err != nil { return nil, err } + var files []string + err = fs.WalkDir(artifactsFS, ".", func(p string, info fs.DirEntry, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + files = append(files, p) + return nil + }) + if err != nil { + return nil, errors.Wrap(err, "error walking artifacts") + } + + mfst, err := json.Marshal(files) + if err != nil { + return nil, errors.Wrap(err, "error marshalling file manifest") + } + output := llb.Scratch(). File(llb.Mkfile("/target", 0o600, []byte(target))). - File(llb.Mkfile("/config.json", 0o600, configBytes)) + File(llb.Mkfile("/config.json", 0o600, configBytes)). + File(llb.Mkfile("/manifest.json", 0o600, mfst)) // For any build-arg seen, write a file to /env/ with the contents // being the value of the arg. diff --git a/test/jammy_test.go b/test/jammy_test.go index 04eb0e97..0544af77 100644 --- a/test/jammy_test.go +++ b/test/jammy_test.go @@ -1,11 +1,13 @@ package test import ( + "fmt" "testing" "github.com/Azure/dalec" "github.com/Azure/dalec/frontend/jammy" "github.com/moby/buildkit/client/llb" + ocispecs "github.com/opencontainers/image-spec/specs-go/v1" ) func TestJammy(t *testing.T) { @@ -20,6 +22,25 @@ func TestJammy(t *testing.T) { FormatDepEqual: func(ver, rev string) string { return ver + "-ubuntu22.04u" + rev }, + ListExpectedSignFiles: func(spec *dalec.Spec, platform ocispecs.Platform) []string { + base := fmt.Sprintf("%s_%s-%su%s", spec.Name, spec.Version, "ubuntu22.04", spec.Revision) + sourceBase := fmt.Sprintf("%s_%s.orig", spec.Name, spec.Version) + + out := []string{ + base + ".debian.tar.xz", + base + ".dsc", + fmt.Sprintf("%s_%s.deb", base, platform.Architecture), + base + "_source.buildinfo", + base + "_source.changes", + sourceBase + ".tar.xz", + } + + for src := range spec.Sources { + out = append(out, fmt.Sprintf("%s-%s.tar.gz", sourceBase, src)) + } + + return out + }, }, LicenseDir: "/usr/share/doc", SystemdDir: struct { diff --git a/test/signing_test.go b/test/signing_test.go index fcf19bdd..06cf2f91 100644 --- a/test/signing_test.go +++ b/test/signing_test.go @@ -2,16 +2,20 @@ package test import ( "context" + "encoding/json" "fmt" + "slices" "strings" "testing" "github.com/Azure/dalec" "github.com/Azure/dalec/test/testenv" + "github.com/containerd/containerd/platforms" "github.com/moby/buildkit/client" "github.com/moby/buildkit/client/llb" gwclient "github.com/moby/buildkit/frontend/gateway/client" "gotest.tools/v3/assert" + "gotest.tools/v3/assert/cmp" ) func runTest(t *testing.T, f testenv.TestFunc, opts ...testenv.TestRunnerOpt) { @@ -66,7 +70,7 @@ func linuxSigningTests(ctx context.Context, testConfig testLinuxConfig) func(*te t.Run("root config", func(t *testing.T) { t.Parallel() spec := newSigningSpec() - runTest(t, distroSigningTest(t, spec, testConfig.Target.Package)) + runTest(t, distroSigningTest(t, spec, testConfig.Target.Package, testConfig)) }) t.Run("with target config", func(t *testing.T) { @@ -82,7 +86,7 @@ func linuxSigningTests(ctx context.Context, testConfig testLinuxConfig) func(*te } spec.PackageConfig.Signer = nil - runTest(t, distroSigningTest(t, spec, testConfig.Target.Package)) + runTest(t, distroSigningTest(t, spec, testConfig.Target.Package, testConfig)) }) t.Run("target config takes precedence when root config is there", func(t *testing.T) { @@ -116,7 +120,7 @@ func linuxSigningTests(ctx context.Context, testConfig testLinuxConfig) func(*te } spec.PackageConfig.Signer.Image = "notexist" - runTest(t, distroSigningTest(t, spec, testConfig.Target.Package), testenv.WithSolveStatusFn(handleStatus)) + runTest(t, distroSigningTest(t, spec, testConfig.Target.Package, testConfig), testenv.WithSolveStatusFn(handleStatus)) assert.Assert(t, found, "Spec signing override warning message not emitted") }) @@ -129,7 +133,7 @@ func linuxSigningTests(ctx context.Context, testConfig testLinuxConfig) func(*te "HELLO": "world", "FOO": "bar", } - runTest(t, distroSigningTest(t, spec, testConfig.Target.Package)) + runTest(t, distroSigningTest(t, spec, testConfig.Target.Package, testConfig)) }) t.Run("with path build arg and build context", func(t *testing.T) { @@ -146,6 +150,7 @@ signer: t, spec, testConfig.Target.Package, + testConfig, withBuildContext(ctx, t, "dalec_signing_config", signConfig), withBuildArg("DALEC_SIGNING_CONFIG_CONTEXT_NAME", "dalec_signing_config"), withBuildArg("DALEC_SIGNING_CONFIG_PATH", "/unusual_place.yml"), @@ -180,6 +185,7 @@ signer: t, spec, testConfig.Target.Package, + testConfig, withBuildContext(ctx, t, "dalec_signing_config", signConfig), withBuildArg("DALEC_SIGNING_CONFIG_CONTEXT_NAME", "dalec_signing_config"), withBuildArg("DALEC_SIGNING_CONFIG_PATH", "/unusual_place.yml"), @@ -204,6 +210,7 @@ signer: t, spec, testConfig.Target.Package, + testConfig, withBuildContext(ctx, t, "dalec_signing_config", signConfig), withBuildArg("DALEC_SIGNING_CONFIG_CONTEXT_NAME", "dalec_signing_config"), withBuildArg("DALEC_SIGNING_CONFIG_PATH", "/unusual_place.yml"), @@ -225,6 +232,7 @@ signer: t, spec, testConfig.Target.Package, + testConfig, withMainContext(ctx, t, signConfig), withBuildArg("DALEC_SIGNING_CONFIG_PATH", "/sign_config.yml"), )) @@ -257,6 +265,7 @@ signer: t, spec, testConfig.Target.Package, + testConfig, withMainContext(ctx, t, signConfig), withBuildArg("DALEC_SIGNING_CONFIG_PATH", "/sign_config.yml"), ), testenv.WithSolveStatusFn(handleStatus)) @@ -361,7 +370,35 @@ signer: } } -func windowsSigningTests(t *testing.T) { +func windowsSigningTests(t *testing.T, tcfg targetConfig) { + runBuild := func(ctx context.Context, t *testing.T, gwc gwclient.Client, spec *dalec.Spec, srOpts ...srOpt) { + st := prepareWindowsSigningState(ctx, t, gwc, spec, srOpts...) + + def, err := st.Marshal(ctx) + if err != nil { + t.Fatal(err) + } + + res := solveT(ctx, t, gwc, gwclient.SolveRequest{ + Definition: def.ToPB(), + }) + + tgt := readFile(ctx, t, "target", res) + cfg := readFile(ctx, t, "config.json", res) + mfst := readFile(ctx, t, "manifest.json", res) + + assert.Check(t, cmp.Equal(string(tgt), "windowscross")) + assert.Check(t, cmp.Contains(string(cfg), "windows")) + + var files []string + assert.NilError(t, json.Unmarshal(mfst, &files)) + slices.Sort(files) + + expectedFiles := tcfg.ListExpectedSignFiles(spec, platforms.DefaultSpec()) + slices.Sort(expectedFiles) + assert.Assert(t, cmp.DeepEqual(files, expectedFiles)) + } + t.Run("target spec config", func(t *testing.T) { t.Parallel() runTest(t, func(ctx context.Context, gwc gwclient.Client) { @@ -462,7 +499,7 @@ signer: runTest(t, func(ctx context.Context, gwc gwclient.Client) { spec := newSimpleSpec() - st := prepareSigningState(ctx, t, gwc, spec, withBuildArg("DALEC_SKIP_SIGNING", "1")) + st := prepareWindowsSigningState(ctx, t, gwc, spec, withBuildArg("DALEC_SKIP_SIGNING", "1")) def, err := st.Marshal(ctx) if err != nil { @@ -486,7 +523,7 @@ signer: }) } -func distroSigningTest(t *testing.T, spec *dalec.Spec, buildTarget string, extraSrOpts ...srOpt) testenv.TestFunc { +func distroSigningTest(t *testing.T, spec *dalec.Spec, buildTarget string, tcfg testLinuxConfig, extraSrOpts ...srOpt) testenv.TestFunc { return func(ctx context.Context, gwc gwclient.Client) { topTgt, _, _ := strings.Cut(buildTarget, "/") @@ -501,6 +538,7 @@ func distroSigningTest(t *testing.T, spec *dalec.Spec, buildTarget string, extra tgt := readFile(ctx, t, "/target", res) cfg := readFile(ctx, t, "/config.json", res) + mfst := readFile(ctx, t, "/manifest.json", res) if string(tgt) != topTgt { t.Fatal(fmt.Errorf("target incorrect; either not sent to signer or not received back from signer")) @@ -516,6 +554,19 @@ func distroSigningTest(t *testing.T, spec *dalec.Spec, buildTarget string, extra assert.Equal(t, v, string(dt)) } } + + if tcfg.Target.ListExpectedSignFiles == nil { + t.Fatal("missing function to get list of expected files to sign") + } + + expectedFiles := tcfg.Target.ListExpectedSignFiles(spec, platforms.DefaultSpec()) + slices.Sort(expectedFiles) + + var files []string + assert.NilError(t, json.Unmarshal(mfst, &files)) + slices.Sort(files) + + assert.Assert(t, cmp.DeepEqual(files, expectedFiles)) } } diff --git a/test/windows_test.go b/test/windows_test.go index 1a17a12c..928e0649 100644 --- a/test/windows_test.go +++ b/test/windows_test.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "strings" "testing" "github.com/Azure/dalec" @@ -12,54 +11,62 @@ import ( "github.com/moby/buildkit/client/llb" gwclient "github.com/moby/buildkit/frontend/gateway/client" moby_buildkit_v1_frontend "github.com/moby/buildkit/frontend/gateway/pb" + ocispecs "github.com/opencontainers/image-spec/specs-go/v1" + "golang.org/x/exp/maps" ) func TestWindows(t *testing.T) { t.Parallel() ctx := startTestSpan(baseCtx, t) - testWindows(ctx, t, "windowscross/container") - t.Run("custom worker", func(t *testing.T) { - t.Parallel() - ctx := startTestSpan(baseCtx, t) - testCustomWindowscrossWorker(ctx, t, targetConfig{ - Container: "windowscross/container", - // The way the test uses the package target is to generate a package which - // it then feeds back into a custom repo and adds that package as a build dep - // to another package. - // We don't build system packages for the windowscross base image. - // There's also no .deb support (currently) - // So... use a mariner2 rpm and then in CreateRepo, convert the rpm to a deb package - // which we'll use to create the repo... - // We can switch to this jammy/deb when that is available. - Package: "mariner2/rpm", - Worker: "windowscross/worker", - }, workerConfig{ - ContextName: windows.WindowscrossWorkerContextName, - CreateRepo: func(pkg llb.State) llb.StateOption { - return func(in llb.State) llb.State { - dt := []byte(` + tcfg := targetConfig{ + Container: "windowscross/container", + // The way the test uses the package target is to generate a package which + // it then feeds back into a custom repo and adds that package as a build dep + // to another package. + // We don't build system packages for the windowscross base image. + // There's also no .deb support (currently) + // So... use a mariner2 rpm and then in CreateRepo, convert the rpm to a deb package + // which we'll use to create the repo... + // We can switch to this jammy/deb when that is available. + Package: "mariner2/rpm", + Worker: "windowscross/worker", + ListExpectedSignFiles: func(spec *dalec.Spec, platform ocispecs.Platform) []string { + return maps.Keys(spec.Artifacts.Binaries) + }, + } + wcfg := workerConfig{ + ContextName: windows.WindowscrossWorkerContextName, + CreateRepo: func(pkg llb.State) llb.StateOption { + return func(in llb.State) llb.State { + dt := []byte(` deb [trusted=yes] copy:/tmp/repo / `) - repo := in. - Run( - dalec.ShArgs("apt-get update && apt-get install -y apt-utils alien"), - dalec.WithMountedAptCache("test-windowscross"), - ). - Run( - llb.Dir("/tmp/repo"), - dalec.ShArgs("set -e; for i in ./RPMS/*/*.rpm; do alien --to-deb \"$i\"; done; rm -rf ./RPMS; rm -rf ./SRPMS; apt-ftparchive packages . | gzip -1 > Packages.gz"), - ). - AddMount("/tmp/repo", pkg) - - return in. - File(llb.Mkfile("/etc/apt/sources.list.d/windowscross.list", 0o644, dt)). - File(llb.Copy(repo, "/", "/tmp/repo")) - } - }, - }) + repo := in. + Run( + dalec.ShArgs("apt-get update && apt-get install -y apt-utils alien"), + dalec.WithMountedAptCache("test-windowscross"), + ). + Run( + llb.Dir("/tmp/repo"), + dalec.ShArgs("set -e; for i in ./RPMS/*/*.rpm; do alien --to-deb \"$i\"; done; rm -rf ./RPMS; rm -rf ./SRPMS; apt-ftparchive packages . | gzip -1 > Packages.gz"), + ). + AddMount("/tmp/repo", pkg) + + return in. + File(llb.Mkfile("/etc/apt/sources.list.d/windowscross.list", 0o644, dt)). + File(llb.Copy(repo, "/", "/tmp/repo")) + } + }, + } + + testWindows(ctx, t, tcfg) + t.Run("custom worker", func(t *testing.T) { + t.Parallel() + ctx := startTestSpan(baseCtx, t) + testCustomWindowscrossWorker(ctx, t, tcfg, wcfg) }) } @@ -75,7 +82,7 @@ func withWindowsAmd64(cfg *newSolveRequestConfig) { cfg.req.FrontendOpt["platform"] = "windows/amd64" } -func testWindows(ctx context.Context, t *testing.T, buildTarget string) { +func testWindows(ctx context.Context, t *testing.T, tcfg targetConfig) { t.Run("Fail when non-zero exit code during build", func(t *testing.T) { t.Parallel() spec := dalec.Spec{ @@ -97,7 +104,7 @@ func testWindows(ctx context.Context, t *testing.T, buildTarget string) { } testEnv.RunTest(ctx, t, func(ctx context.Context, gwc gwclient.Client) { - sr := newSolveRequest(withSpec(ctx, t, &spec), withBuildTarget(buildTarget), withWindowsAmd64) + sr := newSolveRequest(withSpec(ctx, t, &spec), withBuildTarget(tcfg.Container), withWindowsAmd64) sr.Evaluate = true _, err := gwc.Solve(ctx, sr) var xErr *moby_buildkit_v1_frontend.ExitError @@ -128,7 +135,7 @@ func testWindows(ctx context.Context, t *testing.T, buildTarget string) { } testEnv.RunTest(ctx, t, func(ctx context.Context, gwc gwclient.Client) { - sr := newSolveRequest(withSpec(ctx, t, &spec), withBuildTarget(buildTarget), withWindowsAmd64) + sr := newSolveRequest(withSpec(ctx, t, &spec), withBuildTarget(tcfg.Container), withWindowsAmd64) sr.Evaluate = true _, err := gwc.Solve(ctx, sr) @@ -262,7 +269,7 @@ echo "$BAR" > bar.txt } testEnv.RunTest(ctx, t, func(ctx context.Context, gwc gwclient.Client) { - sr := newSolveRequest(withSpec(ctx, t, &spec), withBuildTarget(buildTarget), withWindowsAmd64) + sr := newSolveRequest(withSpec(ctx, t, &spec), withBuildTarget(tcfg.Container), withWindowsAmd64) sr.Evaluate = true res := solveT(ctx, t, gwc, sr) @@ -300,7 +307,9 @@ echo "$BAR" > bar.txt }) }) - t.Run("test signing", windowsSigningTests) + t.Run("signing", func(t *testing.T) { + windowsSigningTests(t, tcfg) + }) t.Run("go module", func(t *testing.T) { t.Parallel() @@ -353,41 +362,13 @@ echo "$BAR" > bar.txt } testEnv.RunTest(ctx, t, func(ctx context.Context, client gwclient.Client) { - req := newSolveRequest(withBuildTarget(buildTarget), withSpec(ctx, t, spec), withWindowsAmd64) + req := newSolveRequest(withBuildTarget(tcfg.Container), withSpec(ctx, t, spec), withWindowsAmd64) solveT(ctx, t, client, req) }) }) } -func runBuild(ctx context.Context, t *testing.T, gwc gwclient.Client, spec *dalec.Spec, srOpts ...srOpt) { - st := prepareSigningState(ctx, t, gwc, spec, srOpts...) - - def, err := st.Marshal(ctx) - if err != nil { - t.Fatal(err) - } - - res := solveT(ctx, t, gwc, gwclient.SolveRequest{ - Definition: def.ToPB(), - }) - - verifySigning(ctx, t, res) -} - -func verifySigning(ctx context.Context, t *testing.T, res *gwclient.Result) { - tgt := readFile(ctx, t, "/target", res) - cfg := readFile(ctx, t, "/config.json", res) - - if string(tgt) != "windowscross" { - t.Fatal(fmt.Errorf("target incorrect; either not sent to signer or not received back from signer")) - } - - if !strings.Contains(string(cfg), "windows") { - t.Fatal(fmt.Errorf("configuration incorrect")) - } -} - -func prepareSigningState(ctx context.Context, t *testing.T, gwc gwclient.Client, spec *dalec.Spec, extraSrOpts ...srOpt) llb.State { +func prepareWindowsSigningState(ctx context.Context, t *testing.T, gwc gwclient.Client, spec *dalec.Spec, extraSrOpts ...srOpt) llb.State { zipper := getZipperState(ctx, t, gwc) srOpts := []srOpt{withSpec(ctx, t, spec), withBuildTarget("windowscross/zip"), withWindowsAmd64}