From 40aea38d122ebdce1bc105991405f0911cdfc04d Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 7 Mar 2024 15:16:14 +0400 Subject: [PATCH] feat: support per-dependency arch For internal stages. Signed-off-by: Andrey Smirnov Signed-off-by: Noel Georgi --- README.md | 4 +- cmd/bldr/cmd/llb.go | 99 ---------- go.mod | 1 - go.sum | 2 - internal/pkg/convert/graph.go | 30 +-- internal/pkg/convert/llb.go | 13 +- internal/pkg/convert/node.go | 87 ++++----- .../integration/testdata/arch-amd64/test.yaml | 10 - .../integration/testdata/arch-arm64/test.yaml | 10 - .../testdata/platform-override/final/pkg.yaml | 7 +- .../platform-override/onlyarm/pkg.yaml | 12 ++ .../testdata/platform-override/test.yaml | 14 +- .../pkg/integration/testdata/simple/test.yaml | 8 - .../pkg/integration/testdata/stages/test.yaml | 14 -- .../integration/testdata/variables/test.yaml | 14 -- internal/pkg/pkgfile/build.go | 172 +++++++++++------- internal/pkg/platform/platform.go | 34 ---- internal/pkg/types/v1alpha2/pkg.go | 1 - internal/pkg/util/testutil/buildkit.go | 69 ------- internal/pkg/util/testutil/llb.go | 44 ----- internal/pkg/util/testutil/runners.go | 16 -- 21 files changed, 186 insertions(+), 475 deletions(-) delete mode 100644 cmd/bldr/cmd/llb.go create mode 100644 internal/pkg/integration/testdata/platform-override/onlyarm/pkg.yaml delete mode 100644 internal/pkg/platform/platform.go delete mode 100644 internal/pkg/util/testutil/buildkit.go delete mode 100644 internal/pkg/util/testutil/llb.go diff --git a/README.md b/README.md index 1b62019..0a68ae0 100644 --- a/README.md +++ b/README.md @@ -227,8 +227,6 @@ Additionally, hermetic text functions from [Sprig](http://masterminds.github.io/ On the root level, following properties are available: - `name` (*str*, *required*): name of the package, also used to reference this package from other packages as dependency. -- `platform` (*str*, *optional*): platform override for the build. - If not set, defaults to the platform of the build. - `variant` (*str*, *optional*): variant of the base image of the build. Two variants are available: - `alpine`: Alpine Linux 3.16 image with `bash` package pre-installed @@ -272,7 +270,7 @@ Properties: Contents of the stage are poured into the build at the location specified with `to:` parameter. - `image` (*str*, *external dependency*): reference to the registry container image this package depends on. Contents of the image are poured into the build at the location specified with `to:` parameter. -- `platform` (*str*, *optional*): platform to pull the image for. +- `platform` (*str*, *optional*): platform to override for the `image` or `stage`. If not set, defaults to the platform of the build. - `runtime` (*bool*, *optional*): if set, marks dependency as runtime. This means that when this package is pulled in into the build, all the runtime dependencies are pulled in automatically as well. diff --git a/cmd/bldr/cmd/llb.go b/cmd/bldr/cmd/llb.go deleted file mode 100644 index a2576fb..0000000 --- a/cmd/bldr/cmd/llb.go +++ /dev/null @@ -1,99 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -package cmd - -import ( - "encoding/json" - "fmt" - "log" - "os" - - "github.com/moby/buildkit/client/llb" - solverpb "github.com/moby/buildkit/solver/pb" - "github.com/spf13/cobra" - - "github.com/siderolabs/bldr/internal/pkg/convert" - "github.com/siderolabs/bldr/internal/pkg/solver" -) - -var llbCmdFlags struct { - json bool -} - -// llbCmd represents the llb command. -var llbCmd = &cobra.Command{ - Use: "llb", - Short: "Dump buildkit LLB for the build", - Long: `This command parses build instructions from pkg.yaml files, -and outputs buildkit LLB to stdout. This can be used as 'bldr pack ... | buildctl ...'. -`, - Args: cobra.NoArgs, - Run: func(_ *cobra.Command, _ []string) { - loader := solver.FilesystemPackageLoader{ - Root: pkgRoot, - Context: options.GetVariables(), - } - - packages, err := solver.NewPackages(&loader) - if err != nil { - log.Fatal(err) - } - - graph, err := packages.Resolve(options.Target) - if err != nil { - log.Fatal(err) - } - - dt, err := convert.MarshalLLB(graph, options) - if err != nil { - log.Fatal(err) - } - - if llbCmdFlags.json { - pb := dt.ToPB() - var b []byte - if b, err = json.MarshalIndent(pb, "", " "); err != nil { - log.Fatal(err) - } - fmt.Printf("%s\n", b) - - for _, def := range pb.Def { - b, err = json.MarshalIndent(def, "", " ") - if err != nil { - log.Fatal(err) - } - - fmt.Printf("Def %s: ", b) - - op := new(solverpb.Op) - if err = op.Unmarshal(def); err != nil { - log.Fatal(err) - } - - b, err = json.MarshalIndent(op, "", " ") - if err != nil { - log.Fatal(err) - } - fmt.Printf("%s\n", b) - } - - return - } - - err = llb.WriteTo(dt, os.Stdout) - if err != nil { - log.Fatal(err) - } - }, -} - -func init() { - llbCmd.Flags().StringVarP(&options.Target, "target", "t", "", "Target image to build") - llbCmd.MarkFlagRequired("target") //nolint:errcheck - llbCmd.Flags().Var(&options.BuildPlatform, "build-platform", "Build platform") - llbCmd.Flags().Var(&options.TargetPlatform, "target-platform", "Target platform") - llbCmd.Flags().BoolVar(&llbCmdFlags.json, "json", false, "Dump as JSON for debug") - rootCmd.AddCommand(llbCmd) -} diff --git a/go.mod b/go.mod index aa5d04b..514fbdc 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.21.0 require ( github.com/Masterminds/semver v1.5.0 github.com/Masterminds/sprig/v3 v3.2.3 - github.com/alessio/shellescape v1.4.2 github.com/containerd/containerd v1.7.13 github.com/emicklei/dot v1.6.1 github.com/google/go-github/v60 v60.0.0 diff --git a/go.sum b/go.sum index 451609a..26aa3e2 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= -github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= -github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is= github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= diff --git a/internal/pkg/convert/graph.go b/internal/pkg/convert/graph.go index ce225e1..827d2c0 100644 --- a/internal/pkg/convert/graph.go +++ b/internal/pkg/convert/graph.go @@ -12,7 +12,6 @@ import ( "github.com/siderolabs/bldr/internal/pkg/constants" "github.com/siderolabs/bldr/internal/pkg/environment" - "github.com/siderolabs/bldr/internal/pkg/platform" "github.com/siderolabs/bldr/internal/pkg/solver" "github.com/siderolabs/bldr/internal/pkg/types/v1alpha2" ) @@ -22,6 +21,7 @@ import ( // GraphLLB caches common images used in the build. type GraphLLB struct { *solver.PackageGraph + solverFn SolverFunc Options *environment.Options @@ -30,24 +30,20 @@ type GraphLLB struct { LocalContext llb.State baseImageProcessor llbProcessor - cache map[cacheKey]llb.State + cache map[*solver.PackageNode]llb.State commonRunOptions []llb.RunOption } -type cacheKey struct { - *solver.PackageNode - Platform string -} - type llbProcessor func(llb.State) llb.State // NewGraphLLB creates new GraphLLB and initializes shared images. -func NewGraphLLB(graph *solver.PackageGraph, options *environment.Options) *GraphLLB { +func NewGraphLLB(graph *solver.PackageGraph, solverFn SolverFunc, options *environment.Options) *GraphLLB { result := &GraphLLB{ PackageGraph: graph, Options: options, - cache: make(map[cacheKey]llb.State), + solverFn: solverFn, + cache: make(map[*solver.PackageNode]llb.State), } if options.ProxyEnv != nil { @@ -92,12 +88,9 @@ func (graph *GraphLLB) buildBaseImages() { return addEnv(addPkg(root)) } - platform, _ := platform.ToV1Platform(graph.Root.Pkg.Platform, graph.Options.TargetPlatform.String()) //nolint:errcheck - graph.BaseImages[v1alpha2.Alpine] = graph.baseImageProcessor(llb.Image( constants.DefaultBaseImage, llb.WithCustomName(graph.Options.CommonPrefix+"base"), - llb.Platform(platform), ).Run( append(graph.commonRunOptions, llb.Shlex("apk --no-cache --update add bash"), @@ -114,12 +107,9 @@ func (graph *GraphLLB) buildBaseImages() { } func (graph *GraphLLB) buildChecksummer() { - platform, _ := platform.ToV1Platform(graph.Root.Pkg.Platform, graph.Options.TargetPlatform.String()) //nolint:errcheck - graph.Checksummer = llb.Image( constants.DefaultBaseImage, llb.WithCustomName(graph.Options.CommonPrefix+"cksum"), - llb.Platform(platform), ).Run( append(graph.commonRunOptions, llb.Shlex("apk --no-cache --update add coreutils"), @@ -143,18 +133,18 @@ func (graph *GraphLLB) buildLocalContext() { } // Build converts package graph to LLB. -func (graph *GraphLLB) Build() (llb.State, error) { - return NewNodeLLB(graph.Root, graph, graph.Root.Pkg.Platform).Build() +func (graph *GraphLLB) Build(ctx context.Context) (llb.State, error) { + return NewNodeLLB(graph.Root, graph).Build(ctx) } // Marshal returns marshaled LLB. -func (graph *GraphLLB) Marshal() (*llb.Definition, error) { - out, err := graph.Build() +func (graph *GraphLLB) Marshal(ctx context.Context) (*llb.Definition, error) { + out, err := graph.Build(ctx) if err != nil { return nil, err } out = out.SetMarshalDefaults(graph.Options.BuildPlatform.LLBPlatform) - return out.Marshal(context.TODO()) + return out.Marshal(ctx) } diff --git a/internal/pkg/convert/llb.go b/internal/pkg/convert/llb.go index ae70518..2801c9c 100644 --- a/internal/pkg/convert/llb.go +++ b/internal/pkg/convert/llb.go @@ -5,18 +5,19 @@ package convert import ( + "context" + "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/frontend/gateway/client" "github.com/siderolabs/bldr/internal/pkg/environment" "github.com/siderolabs/bldr/internal/pkg/solver" ) -// BuildLLB translates package graph into LLB DAG. -func BuildLLB(graph *solver.PackageGraph, options *environment.Options) (llb.State, error) { - return NewGraphLLB(graph, options).Build() -} +// SolverFunc can be called to solve the package into the llb state via buildkit. +type SolverFunc func(ctx context.Context, platform environment.Platform, target string) (*client.Result, error) // MarshalLLB translates package graph into LLB DAG and marshals it. -func MarshalLLB(graph *solver.PackageGraph, options *environment.Options) (*llb.Definition, error) { - return NewGraphLLB(graph, options).Marshal() +func MarshalLLB(ctx context.Context, graph *solver.PackageGraph, solver SolverFunc, options *environment.Options) (*llb.Definition, error) { + return NewGraphLLB(graph, solver, options).Marshal(ctx) } diff --git a/internal/pkg/convert/node.go b/internal/pkg/convert/node.go index d533d1d..23023fb 100644 --- a/internal/pkg/convert/node.go +++ b/internal/pkg/convert/node.go @@ -5,18 +5,19 @@ package convert import ( + "context" "fmt" "path" "path/filepath" "sort" "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/frontend/gateway/client" "github.com/opencontainers/go-digest" "github.com/siderolabs/gen/xslices" "github.com/siderolabs/bldr/internal/pkg/constants" "github.com/siderolabs/bldr/internal/pkg/environment" - "github.com/siderolabs/bldr/internal/pkg/platform" "github.com/siderolabs/bldr/internal/pkg/solver" "github.com/siderolabs/bldr/internal/pkg/types/v1alpha2" ) @@ -55,24 +56,17 @@ func defaultCopyOptions(options *environment.Options, reproducible bool) *llb.Co type NodeLLB struct { *solver.PackageNode - Graph *GraphLLB - Prefix string - Platform string + Graph *GraphLLB + Prefix string } // NewNodeLLB wraps PackageNode for LLB conversion. -func NewNodeLLB(node *solver.PackageNode, graph *GraphLLB, platformOverride string) *NodeLLB { - // set default platform if not set - if platformOverride == "" { - platformOverride = graph.Options.TargetPlatform.String() - } - +func NewNodeLLB(node *solver.PackageNode, graph *GraphLLB) *NodeLLB { return &NodeLLB{ PackageNode: node, - Graph: graph, - Prefix: graph.Options.CommonPrefix + node.Name + ":", - Platform: platformOverride, + Graph: graph, + Prefix: graph.Options.CommonPrefix + node.Name + ":", } } @@ -103,11 +97,32 @@ func (node *NodeLLB) context(root llb.State) llb.State { ) } -func (node *NodeLLB) convertDependency(dep solver.PackageDependency) (depState llb.State, srcName string, err error) { +func (node *NodeLLB) convertDependency(ctx context.Context, dep solver.PackageDependency) (depState llb.State, srcName string, err error) { if dep.IsInternal() { - depState, err = NewNodeLLB(dep.Node, node.Graph, node.Pkg.Platform).Build() - if err != nil { - return llb.Scratch(), "", err + if dep.Platform != "" && dep.Platform != node.Graph.Options.BuildPlatform.ID { + var res *client.Result + + res, err = node.Graph.solverFn(ctx, environment.Platforms[dep.Platform], dep.Node.Name) + if err != nil { + return llb.Scratch(), "", err + } + + var ref client.Reference + + ref, err = res.SingleRef() + if err != nil { + return llb.Scratch(), "", err + } + + depState, err = ref.ToState() + if err != nil { + return llb.Scratch(), "", err + } + } else { + depState, err = NewNodeLLB(dep.Node, node.Graph).Build(ctx) + if err != nil { + return llb.Scratch(), "", err + } } srcName = dep.Node.Name @@ -116,19 +131,19 @@ func (node *NodeLLB) convertDependency(dep solver.PackageDependency) (depState l srcName = dep.Image if dep.Platform != "" { - platform, err := platform.ToV1Platform(dep.Platform, "") - if err != nil { - return llb.Scratch(), "", err + platform, ok := environment.Platforms[dep.Platform] + if !ok { + return llb.Scratch(), "", fmt.Errorf("platform %q not supported", dep.Platform) } - depState = llb.Image(dep.Image, llb.Platform(platform)) + depState = llb.Image(dep.Image, platform.LLBPlatform) } } - return + return depState, srcName, nil } -func (node *NodeLLB) dependencies(root llb.State) (llb.State, error) { +func (node *NodeLLB) dependencies(ctx context.Context, root llb.State) (llb.State, error) { deps := make([]solver.PackageDependency, 0, len(node.Dependencies)) // collect all the dependencies including transitive runtime dependencies @@ -150,20 +165,13 @@ func (node *NodeLLB) dependencies(root llb.State) (llb.State, error) { stages := []llb.State{root} for _, dep := range deps { - depID := dep.ID() + node.Platform - - // set dep platform to node platform if not set - if dep.Platform == "" { - dep.Platform = node.Platform - } - - if _, alreadyProcessed := seen[depID]; alreadyProcessed { + if _, alreadyProcessed := seen[dep.ID()]; alreadyProcessed { continue } - seen[depID] = struct{}{} + seen[dep.ID()] = struct{}{} - depState, srcName, err := node.convertDependency(dep) + depState, srcName, err := node.convertDependency(ctx, dep) if err != nil { return llb.Scratch(), err } @@ -324,19 +332,14 @@ func (node *NodeLLB) finalize(root llb.State) llb.State { } // Build converts PackageNode to buildkit LLB. -func (node *NodeLLB) Build() (llb.State, error) { - cacheSt := cacheKey{ - PackageNode: node.PackageNode, - Platform: node.Platform, - } - - if state, ok := node.Graph.cache[cacheSt]; ok { +func (node *NodeLLB) Build(ctx context.Context) (llb.State, error) { + if state, ok := node.Graph.cache[node.PackageNode]; ok { return state, nil } root := node.base() - root, err := node.dependencies(root) + root, err := node.dependencies(ctx, root) if err != nil { return llb.Scratch(), err } @@ -350,7 +353,7 @@ func (node *NodeLLB) Build() (llb.State, error) { root = node.finalize(root) - node.Graph.cache[cacheSt] = root + node.Graph.cache[node.PackageNode] = root return root, nil } diff --git a/internal/pkg/integration/testdata/arch-amd64/test.yaml b/internal/pkg/integration/testdata/arch-amd64/test.yaml index 739dacd..c5bf7f5 100644 --- a/internal/pkg/integration/testdata/arch-amd64/test.yaml +++ b/internal/pkg/integration/testdata/arch-amd64/test.yaml @@ -5,13 +5,3 @@ run: platform: linux/amd64 target: final expect: success - - name: llb-amd64 - runner: llb - platform: linux/amd64 - target: final - expect: success - - name: buildkit-amd64 - runner: buildkit - platform: linux/amd64 - target: final - expect: success diff --git a/internal/pkg/integration/testdata/arch-arm64/test.yaml b/internal/pkg/integration/testdata/arch-arm64/test.yaml index 348b15a..6e49ea1 100644 --- a/internal/pkg/integration/testdata/arch-arm64/test.yaml +++ b/internal/pkg/integration/testdata/arch-arm64/test.yaml @@ -5,13 +5,3 @@ run: platform: linux/arm64 target: final expect: success - - name: llb-arm64 - runner: llb - platform: linux/arm64 - target: final - expect: success - - name: buildkit-arm64 - runner: buildkit - platform: linux/arm64 - target: final - expect: success diff --git a/internal/pkg/integration/testdata/platform-override/final/pkg.yaml b/internal/pkg/integration/testdata/platform-override/final/pkg.yaml index 6dd5bd9..5db1475 100644 --- a/internal/pkg/integration/testdata/platform-override/final/pkg.yaml +++ b/internal/pkg/integration/testdata/platform-override/final/pkg.yaml @@ -1,9 +1,8 @@ --- name: final -platform: linux/arm64 -steps: -- test: - - test `uname -m` = "aarch64" +dependencies: + - stage: onlyarm + platform: linux/arm64 finalize: - from: / to: / diff --git a/internal/pkg/integration/testdata/platform-override/onlyarm/pkg.yaml b/internal/pkg/integration/testdata/platform-override/onlyarm/pkg.yaml new file mode 100644 index 0000000..17e9898 --- /dev/null +++ b/internal/pkg/integration/testdata/platform-override/onlyarm/pkg.yaml @@ -0,0 +1,12 @@ +--- +name: onlyarm +steps: +- test: + - test "${BUILD:-x}" = "aarch64-linux-musl" + - test "${HOST:-x}" = "aarch64-linux-musl" + - test "${ARCH:-x}" = "aarch64" + - test "${TARGET:-x}" = "aarch64-talos-linux-musl" + - test `uname -m` = "aarch64" +finalize: + - from: / + to: / diff --git a/internal/pkg/integration/testdata/platform-override/test.yaml b/internal/pkg/integration/testdata/platform-override/test.yaml index 739dacd..cad5769 100644 --- a/internal/pkg/integration/testdata/platform-override/test.yaml +++ b/internal/pkg/integration/testdata/platform-override/test.yaml @@ -1,17 +1,7 @@ --- run: - - name: docker-amd64 + - name: docker runner: docker - platform: linux/amd64 - target: final - expect: success - - name: llb-amd64 - runner: llb - platform: linux/amd64 - target: final - expect: success - - name: buildkit-amd64 - runner: buildkit - platform: linux/amd64 + platform: linux/amd64,linux/arm64 target: final expect: success diff --git a/internal/pkg/integration/testdata/simple/test.yaml b/internal/pkg/integration/testdata/simple/test.yaml index 893808d..2beb607 100644 --- a/internal/pkg/integration/testdata/simple/test.yaml +++ b/internal/pkg/integration/testdata/simple/test.yaml @@ -4,14 +4,6 @@ run: runner: docker target: go expect: success - - name: buildkit - runner: buildkit - target: go - expect: success - - name: llb - runner: llb - target: go - expect: success - name: validate runner: validate expect: success diff --git a/internal/pkg/integration/testdata/stages/test.yaml b/internal/pkg/integration/testdata/stages/test.yaml index 25c6656..1038468 100644 --- a/internal/pkg/integration/testdata/stages/test.yaml +++ b/internal/pkg/integration/testdata/stages/test.yaml @@ -15,20 +15,6 @@ run: platform: linux/arm64,linux/amd64 target: final expect: success - - name: buildkit - runner: buildkit - target: final - expect: success - - name: llb-amd64 - runner: llb - platform: linux/amd64 - target: final - expect: success - - name: llb-arm64 - runner: llb - platform: linux/arm64 - target: final - expect: success - name: validate runner: validate expect: success diff --git a/internal/pkg/integration/testdata/variables/test.yaml b/internal/pkg/integration/testdata/variables/test.yaml index a928dac..e80ec25 100644 --- a/internal/pkg/integration/testdata/variables/test.yaml +++ b/internal/pkg/integration/testdata/variables/test.yaml @@ -10,20 +10,6 @@ run: platform: linux/arm64 target: final expect: success - - name: buildkit - runner: buildkit - target: final - expect: success - - name: llb-amd64 - runner: llb - platform: linux/amd64 - target: final - expect: success - - name: llb-arm64 - runner: llb - platform: linux/arm64 - target: final - expect: success - name: validate runner: validate expect: success diff --git a/internal/pkg/pkgfile/build.go b/internal/pkg/pkgfile/build.go index efc7e1f..43f0647 100644 --- a/internal/pkg/pkgfile/build.go +++ b/internal/pkg/pkgfile/build.go @@ -43,9 +43,50 @@ const ( sharedKeyHint = constants.PkgYaml ) +type platformContext struct { + packages *solver.Packages + options environment.Options +} + +func solveTarget( + platformContexts map[string]platformContext, c client.Client, cacheImports []client.CacheOptionsEntry, +) func(ctx context.Context, platform environment.Platform, target string) (*client.Result, error) { + return func(ctx context.Context, platform environment.Platform, target string) (*client.Result, error) { + if _, ok := platformContexts[platform.ID]; !ok { + return nil, fmt.Errorf("platform %s not found", platform) + } + + options, packages := platformContexts[platform.ID].options, platformContexts[platform.ID].packages + + if target == "" { + target = options.Target + } + + graph, err := packages.Resolve(target) + if err != nil { + return nil, fmt.Errorf("failed to resolve packages for platform %s and target %s: %w", platform, target, err) + } + + def, err := convert.MarshalLLB(ctx, graph, solveTarget(platformContexts, c, cacheImports), &options) + if err != nil { + return nil, fmt.Errorf("failed to marshal LLB for platform %s and target %s: %w", platform, target, err) + } + + r, err := c.Solve(ctx, client.SolveRequest{ + Definition: def.ToPB(), + CacheImports: cacheImports, + }) + if err != nil { + return nil, fmt.Errorf("failed to solve LLB for platform %s and target %s: %w", platform, target, err) + } + + return r, nil + } +} + // Build is an entrypoint for buildkit frontend. // -//nolint:gocyclo,cyclop,gocognit,maintidx +//nolint:gocyclo,cyclop,gocognit func Build(ctx context.Context, c client.Client, options *environment.Options) (*client.Result, error) { opts := c.BuildOpts().Opts @@ -102,89 +143,88 @@ func Build(ctx context.Context, c client.Client, options *environment.Options) ( } res := client.NewResult() - var eg *errgroup.Group - eg, ctx = errgroup.WithContext(ctx) + var cacheImports []client.CacheOptionsEntry - for i, platform := range platforms { - i := i - platform := platform + // new API + if cacheImportsStr := opts[keyCacheImports]; cacheImportsStr != "" { + var cacheImportsUM []controlapi.CacheOptionsEntry - eg.Go(func() error { - options := *options - options.BuildPlatform = platform - options.TargetPlatform = platform + if err := json.Unmarshal([]byte(cacheImportsStr), &cacheImportsUM); err != nil { + return nil, fmt.Errorf("failed to unmarshal %s (%q): %w", keyCacheImports, cacheImportsStr, err) + } - if exportMap { - options.CommonPrefix = fmt.Sprintf("%s ", platform.ID) - } + for _, um := range cacheImportsUM { + cacheImports = append(cacheImports, client.CacheOptionsEntry{Type: um.Type, Attrs: um.Attrs}) + } + } - pkgRef, err := fetchPkgs(ctx, c) - if err != nil { - return err + // old API + if cacheFromStr := opts[keyCacheFrom]; cacheFromStr != "" { + cacheFrom := strings.Split(cacheFromStr, ",") + + for _, s := range cacheFrom { + im := client.CacheOptionsEntry{ + Type: "registry", + Attrs: map[string]string{ + "ref": s, + }, } - buildContext := options.GetVariables().Copy() - // push build arguments as `BUILD_ARGS_` prefixed variables - buildContext.Merge(prefix(filter(opts, buildArgPrefix), "BUILD_ARG_")) + cacheImports = append(cacheImports, im) + } + } - loader := solver.BuildkitFrontendLoader{ - Context: buildContext, - Ref: pkgRef, - Ctx: ctx, - } + // prepare platform contexts + platformContexts := make(map[string]platformContext, len(platforms)) - packages, err := solver.NewPackages(&loader) - if err != nil { - return err - } + for _, platform := range platforms { + options := *options + options.BuildPlatform = platform + options.TargetPlatform = platform - graph, err := packages.Resolve(options.Target) - if err != nil { - return err - } + if exportMap { + options.CommonPrefix = fmt.Sprintf("%s ", platform.ID) + } - def, err := convert.MarshalLLB(graph, &options) //nolint:contextcheck - if err != nil { - return err - } + pkgRef, err := fetchPkgs(ctx, c) + if err != nil { + return nil, fmt.Errorf("error loading packages for %s: %w", platform, err) + } - var cacheImports []client.CacheOptionsEntry + buildContext := options.GetVariables().Copy() + // push build arguments as `BUILD_ARGS_` prefixed variables + buildContext.Merge(prefix(filter(opts, buildArgPrefix), "BUILD_ARG_")) - // new API - if cacheImportsStr := opts[keyCacheImports]; cacheImportsStr != "" { - var cacheImportsUM []controlapi.CacheOptionsEntry + loader := solver.BuildkitFrontendLoader{ + Context: buildContext, + Ref: pkgRef, + Ctx: ctx, + } - if err = json.Unmarshal([]byte(cacheImportsStr), &cacheImportsUM); err != nil { - return fmt.Errorf("failed to unmarshal %s (%q): %w", keyCacheImports, cacheImportsStr, err) - } + packages, err := solver.NewPackages(&loader) + if err != nil { + return nil, fmt.Errorf("error loading packages for %s: %w", platform, err) + } - for _, um := range cacheImportsUM { - cacheImports = append(cacheImports, client.CacheOptionsEntry{Type: um.Type, Attrs: um.Attrs}) - } - } + platformContexts[platform.ID] = platformContext{ + options: options, + packages: packages, + } + } - // old API - if cacheFromStr := opts[keyCacheFrom]; cacheFromStr != "" { - cacheFrom := strings.Split(cacheFromStr, ",") + solveTarget := solveTarget(platformContexts, c, cacheImports) - for _, s := range cacheFrom { - im := client.CacheOptionsEntry{ - Type: "registry", - Attrs: map[string]string{ - "ref": s, - }, - } + var eg *errgroup.Group + eg, ctx = errgroup.WithContext(ctx) - cacheImports = append(cacheImports, im) - } - } + for i, platform := range platforms { + i := i + platform := platform - r, err := c.Solve(ctx, client.SolveRequest{ - Definition: def.ToPB(), - CacheImports: cacheImports, - }) + eg.Go(func() error { + r, err := solveTarget(ctx, platform, "") if err != nil { - return fmt.Errorf("failed to solve LLB: %w", err) + return err } ref, err := r.SingleRef() @@ -205,7 +245,7 @@ func Build(ctx context.Context, c client.Client, options *environment.Options) ( }, Config: image.ImageConfig{ ImageConfig: specs.ImageConfig{ - Labels: packages.ImageLabels(), + Labels: platformContexts[platform.ID].packages.ImageLabels(), }, }, } diff --git a/internal/pkg/platform/platform.go b/internal/pkg/platform/platform.go deleted file mode 100644 index a45e4e2..0000000 --- a/internal/pkg/platform/platform.go +++ /dev/null @@ -1,34 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -// Package platform provides a function to convert a platform string to a v1.Platform. -package platform - -import ( - "fmt" - - v1 "github.com/opencontainers/image-spec/specs-go/v1" -) - -// ToV1Platform converts a platform string to a v1.Platform. -func ToV1Platform(platform, targetPlatform string) (v1.Platform, error) { - if platform == "" { - platform = targetPlatform - } - - switch platform { - case "linux/amd64": - return v1.Platform{ - OS: "linux", - Architecture: "amd64", - }, nil - case "linux/arm64": - return v1.Platform{ - OS: "linux", - Architecture: "arm64", - }, nil - default: - return v1.Platform{}, fmt.Errorf("unknown platform %q", platform) - } -} diff --git a/internal/pkg/types/v1alpha2/pkg.go b/internal/pkg/types/v1alpha2/pkg.go index 01fe531..5736131 100644 --- a/internal/pkg/types/v1alpha2/pkg.go +++ b/internal/pkg/types/v1alpha2/pkg.go @@ -24,7 +24,6 @@ type Pkg struct { Shell Shell `yaml:"shell,omitempty"` BaseDir string `yaml:"-"` FileName string `yaml:"-"` - Platform string `yaml:"platform,omitempty"` Install Install `yaml:"install,omitempty"` Dependencies Dependencies `yaml:"dependencies,omitempty"` Steps Steps `yaml:"steps,omitempty"` diff --git a/internal/pkg/util/testutil/buildkit.go b/internal/pkg/util/testutil/buildkit.go deleted file mode 100644 index e74acb4..0000000 --- a/internal/pkg/util/testutil/buildkit.go +++ /dev/null @@ -1,69 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -package testutil - -import ( - "os" - "os/exec" - "sync" - "testing" -) - -// BuildkitRunner runs bldr via buildctl/buildkit. -type BuildkitRunner struct { - CommandRunner - Target string - Platform string -} - -// Run implements Run interface. -func (runner BuildkitRunner) Run(t *testing.T) { - if err := IsBuildkitAvailable(); err != nil { - t.Skipf("buildkit is not available: %q", err) - } - - args := append(getBuildkitGlobalFlags(), - "build", - "--frontend", "dockerfile.v0", - "--local", "context=.", - "--local", "dockerfile=.", - "--opt", "filename=Pkgfile", - "--opt", "target="+runner.Target, - "--build-arg", "TAG=testtag", - ) - - if runner.Platform != "" { - args = append(args, "--opt", "platform="+runner.Platform) - } - - cmd := exec.Command("buildctl", args...) - - runner.run(t, cmd, "buildkit") -} - -func getBuildkitGlobalFlags() []string { - var globalOpts []string - - if buildkitHost, ok := os.LookupEnv("BUILDKIT_HOST"); ok { - globalOpts = append(globalOpts, "--addr", buildkitHost) - } - - return globalOpts -} - -var ( - buildkitCheckOnce sync.Once - //nolint:errname - buildkitCheckError error -) - -// IsBuildkitAvailable returns nil if buildkit is ready to use. -func IsBuildkitAvailable() error { - buildkitCheckOnce.Do(func() { - buildkitCheckError = exec.Command("buildctl", append(getBuildkitGlobalFlags(), "debug", "workers")...).Run() - }) - - return buildkitCheckError -} diff --git a/internal/pkg/util/testutil/llb.go b/internal/pkg/util/testutil/llb.go deleted file mode 100644 index 4e38b63..0000000 --- a/internal/pkg/util/testutil/llb.go +++ /dev/null @@ -1,44 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -package testutil - -import ( - "fmt" - "os/exec" - "strings" - "testing" - - "github.com/alessio/shellescape" -) - -// LLBRunner runs bldr via bldr llb | buildctl. -type LLBRunner struct { - CommandRunner - Target string - Platform string -} - -// Run implements Run interface. -func (runner LLBRunner) Run(t *testing.T) { - if err := IsBuildkitAvailable(); err != nil { - t.Skipf("buildkit is not available: %q", err) - } - - args := getBuildkitGlobalFlags() - for i := range args { - args[i] = shellescape.Quote(args[i]) - } - - platformArgs := "" - if runner.Platform != "" { - platformArgs = fmt.Sprintf("--build-platform=%s --target-platform=%s", shellescape.Quote(runner.Platform), shellescape.Quote(runner.Platform)) - } - - cmd := exec.Command("/bin/sh", "-c", - fmt.Sprintf("bldr llb --target=%s %s | buildctl %s build --local context=.", shellescape.Quote(runner.Target), platformArgs, strings.Join(args, " ")), - ) - - runner.run(t, cmd, "bldr llb") -} diff --git a/internal/pkg/util/testutil/runners.go b/internal/pkg/util/testutil/runners.go index 580baf3..fa05ca4 100644 --- a/internal/pkg/util/testutil/runners.go +++ b/internal/pkg/util/testutil/runners.go @@ -65,14 +65,6 @@ func getRunner(manifest RunManifest) (Run, error) { Target: manifest.Target, Platform: manifest.Platform, }, nil - case "buildkit": - return BuildkitRunner{ - CommandRunner: CommandRunner{ - Expect: manifest.Expect, - }, - Target: manifest.Target, - Platform: manifest.Platform, - }, nil case "eval": return EvalRunner{ CommandRunner: CommandRunner{ @@ -82,14 +74,6 @@ func getRunner(manifest RunManifest) (Run, error) { Target: manifest.Target, Template: manifest.Template, }, nil - case "llb": - return LLBRunner{ - CommandRunner: CommandRunner{ - Expect: manifest.Expect, - }, - Target: manifest.Target, - Platform: manifest.Platform, - }, nil case "validate": return ValidateRunner{ CommandRunner: CommandRunner{