Skip to content

Commit b4a8159

Browse files
authored
build: add melange support. (#1391)
1 parent 8cf28c4 commit b4a8159

File tree

8 files changed

+428
-139
lines changed

8 files changed

+428
-139
lines changed

internal/build/binary/binary.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,10 @@ func buildSpec(ctx context.Context, pl pkggraph.PackageLoader, env cfg.Context,
329329
}
330330
}
331331

332+
if src.MelangeBuild != nil {
333+
return melangeBuild{loc.Rel(), src.MelangeBuild.Files, src.MelangeBuild.Platforms}, nil
334+
}
335+
332336
return nil, fnerrors.NewWithLocation(loc, "don't know how to build binary image: `from` statement does not yield a build unit")
333337
}
334338

internal/build/binary/melange.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2022 Namespace Labs Inc; All rights reserved.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
5+
package binary
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"io/fs"
11+
"os"
12+
"os/exec"
13+
"path/filepath"
14+
"strings"
15+
16+
"github.com/google/go-containerregistry/pkg/v1/empty"
17+
"github.com/google/go-containerregistry/pkg/v1/mutate"
18+
"namespacelabs.dev/foundation/internal/artifacts/oci"
19+
"namespacelabs.dev/foundation/internal/build"
20+
"namespacelabs.dev/foundation/internal/compute"
21+
"namespacelabs.dev/foundation/internal/console"
22+
"namespacelabs.dev/foundation/internal/fnfs/memfs"
23+
"namespacelabs.dev/foundation/internal/sdk/host"
24+
"namespacelabs.dev/foundation/internal/sdk/melange"
25+
"namespacelabs.dev/foundation/std/pkggraph"
26+
"namespacelabs.dev/foundation/std/tasks"
27+
)
28+
29+
type melangeBuild struct {
30+
rel string
31+
32+
files []string
33+
platforms []string
34+
}
35+
36+
func (m melangeBuild) BuildImage(ctx context.Context, env pkggraph.SealedContext, conf build.Configuration) (compute.Computable[oci.Image], error) {
37+
action := tasks.Action("melange.build").Scope(conf.SourcePackage())
38+
39+
mbin, err := melange.SDK(ctx, host.HostPlatform())
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
localfs := memfs.DeferSnapshot(conf.Workspace().ReadOnlyFS(m.rel), memfs.SnapshotOpts{
45+
IncludeFiles: m.files,
46+
})
47+
48+
return compute.Map(action, compute.Inputs().Computable("mbin", mbin).JSON("platform", m.platforms).Computable("localfs", localfs), compute.Output{},
49+
func(ctx context.Context, deps compute.Resolved) (oci.Image, error) {
50+
mb := compute.MustGetDepValue(deps, mbin, "mbin")
51+
contents := compute.MustGetDepValue(deps, localfs, "localfs")
52+
53+
dir, err := os.MkdirTemp("", "melange")
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
fmt.Fprintf(console.Debug(ctx), "melange: created %s\n", dir)
59+
60+
defer os.RemoveAll(dir)
61+
62+
for _, path := range m.files {
63+
c, err := fs.ReadFile(contents, path)
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
if err := os.WriteFile(filepath.Join(dir, path), c, 0660); err != nil {
69+
return nil, err
70+
}
71+
}
72+
73+
out := console.Output(ctx, "melange")
74+
75+
cmd := exec.CommandContext(ctx, string(mb),
76+
append([]string{
77+
"build",
78+
"-k", "https://packages.wolfi.dev/os/wolfi-signing.rsa.pub",
79+
"-r", "https://packages.wolfi.dev/os",
80+
"--arch", strings.Join(m.platforms, ","),
81+
}, m.files...)...,
82+
)
83+
cmd.Stdout = out
84+
cmd.Stderr = out
85+
cmd.Dir = dir
86+
87+
if err := cmd.Run(); err != nil {
88+
return nil, err
89+
}
90+
91+
layer, err := oci.LayerFromFS(ctx, os.DirFS(filepath.Join(dir, "packages")))
92+
if err != nil {
93+
return nil, err
94+
}
95+
96+
return mutate.AppendLayers(empty.Image, layer)
97+
}), nil
98+
}
99+
100+
func (m melangeBuild) PlatformIndependent() bool { return true }
101+
102+
func (m melangeBuild) Description() string { return fmt.Sprintf("melangeBuild()") }

internal/cli/cmd/sdk/sdk.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ import (
2828
"namespacelabs.dev/foundation/internal/sdk/host"
2929
"namespacelabs.dev/foundation/internal/sdk/k3d"
3030
"namespacelabs.dev/foundation/internal/sdk/kubectl"
31+
"namespacelabs.dev/foundation/internal/sdk/melange"
3132
"namespacelabs.dev/foundation/std/module"
3233
"namespacelabs.dev/foundation/std/tasks"
3334
)
3435

3536
func NewSdkCmd(hidden bool) *cobra.Command {
36-
sdks := []string{"go", "k3d", "kubectl", "grpcurl", "deno", "buildctl"}
37+
sdks := []string{"go", "k3d", "kubectl", "grpcurl", "deno", "buildctl", "melange"}
3738

3839
goSdkVersion := "1.22"
3940

@@ -85,6 +86,7 @@ func sdkList(sdks []string, goVersion string) []sdk {
8586
simpleFileSDK("kubectl", kubectl.SDK),
8687
simpleFileSDK("grpcurl", grpcurl.SDK),
8788
simpleFileSDK("buildctl", buildctl.SDK),
89+
simpleFileSDK("melange", melange.SDK),
8890
}
8991

9092
var ret []sdk

internal/frontend/cuefrontend/binary.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type cueImageBuildPlan struct {
4848
FilesFrom *cueImageBuildPlan_FilesFrom `json:"files_from,omitempty"`
4949
MakeFilesystemImage *cueImageBuildPlan_MakeFilesystemImage `json:"make_fs_image,omitempty"`
5050
ImageID string `json:"image_id,omitempty"`
51+
MelangeBuild *schema.ImageBuildPlan_MelangeBuild `json:"melange_build,omitempty"`
5152
}
5253

5354
type cueImageBuildPlan_LLBPlan struct {
@@ -281,6 +282,11 @@ func (bp cueImageBuildPlan) ToSchema(ctx context.Context, pl parsing.EarlyPackag
281282
set = append(set, "image_id")
282283
}
283284

285+
if bp.MelangeBuild != nil {
286+
plan.MelangeBuild = bp.MelangeBuild
287+
set = append(set, "melange_build")
288+
}
289+
284290
if len(set) == 0 {
285291
return nil, fnerrors.NewWithLocation(loc, "plan is missing at least one instruction")
286292
} else if len(set) > 1 {

internal/sdk/melange/melange.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2022 Namespace Labs Inc; All rights reserved.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
5+
package melange
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"path/filepath"
11+
12+
specs "github.com/opencontainers/image-spec/specs-go/v1"
13+
"namespacelabs.dev/foundation/internal/artifacts"
14+
"namespacelabs.dev/foundation/internal/artifacts/download"
15+
"namespacelabs.dev/foundation/internal/artifacts/unpack"
16+
"namespacelabs.dev/foundation/internal/compute"
17+
"namespacelabs.dev/foundation/internal/fnerrors"
18+
"namespacelabs.dev/foundation/internal/fnfs/tarfs"
19+
"namespacelabs.dev/foundation/schema"
20+
"namespacelabs.dev/foundation/std/tasks"
21+
)
22+
23+
const version = "0.19.4"
24+
25+
var Pins = map[string]artifacts.Reference{
26+
"linux/amd64": {
27+
URL: fmt.Sprintf("https://github.com/chainguard-dev/melange/releases/download/v%s/melange_%s_linux_amd64.tar.gz", version, version),
28+
Digest: schema.Digest{
29+
Algorithm: "sha256",
30+
Hex: "940df40fd759b50c9426150496a83a6eff48ef864bd790eeb8b27d3e9bbcb5ff",
31+
},
32+
},
33+
"linux/arm64": {
34+
URL: fmt.Sprintf("https://github.com/chainguard-dev/melange/releases/download/v%s/melange_%s_linux_arm64.tar.gz", version, version),
35+
Digest: schema.Digest{
36+
Algorithm: "sha256",
37+
Hex: "f1da4af66164ba9ba9aa83a90fe6080b1800838f2cadc2dc49bc49d6bc266884",
38+
},
39+
},
40+
"darwin/arm64": {
41+
URL: fmt.Sprintf("https://github.com/chainguard-dev/melange/releases/download/v%s/melange_%s_darwin_arm64.tar.gz", version, version),
42+
Digest: schema.Digest{
43+
Algorithm: "sha256",
44+
Hex: "9a511cb67618f6782dfb29a44b5ed47a44b184c3ea783e13b418b22f56c696b4",
45+
},
46+
},
47+
"darwin/amd64": {
48+
URL: fmt.Sprintf("https://github.com/chainguard-dev/melange/releases/download/v%s/melange_%s_darwin_amd64.tar.gz", version, version),
49+
Digest: schema.Digest{
50+
Algorithm: "sha256",
51+
Hex: "f3306ba66f9f4d83947ecf0b05ae52d2d75a8cfca9e470ad8a4b83430a889c2b",
52+
},
53+
},
54+
}
55+
56+
type Melange string
57+
58+
func EnsureSDK(ctx context.Context, p specs.Platform) (Melange, error) {
59+
sdk, err := SDK(ctx, p)
60+
if err != nil {
61+
return "", err
62+
}
63+
64+
return compute.GetValue(ctx, sdk)
65+
}
66+
67+
func SDK(ctx context.Context, p specs.Platform) (compute.Computable[Melange], error) {
68+
key := fmt.Sprintf("%s/%s", p.OS, p.Architecture)
69+
ref, ok := Pins[key]
70+
if !ok {
71+
return nil, fnerrors.New("platform not supported: %s", key)
72+
}
73+
74+
w := unpack.Unpack("melange", tarfs.TarGunzip(download.URL(ref)))
75+
76+
return compute.Map(
77+
tasks.Action("melange.ensure").Arg("version", version).HumanReadablef("Ensuring melange %s is installed", version),
78+
compute.Inputs().Computable("melange", w),
79+
compute.Output{},
80+
func(ctx context.Context, r compute.Resolved) (Melange, error) {
81+
return Melange(filepath.Join(compute.MustGetDepValue(r, w, "melange").Files, fmt.Sprintf("melange_%s_%s_%s", version, p.OS, p.Architecture), "melange")), nil
82+
}), nil
83+
}

internal/versions/versions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"api_version": 141,
2+
"api_version": 142,
33
"minimum_api_version": 40,
44
"cache_version": 1
55
}

0 commit comments

Comments
 (0)