Skip to content

Commit a56f0d4

Browse files
committed
Bass Buildkit frontend
Bass can now be used anywhere that accepts a Dockerfile. (Assuming they're using buildx/Buildkit.)
1 parent ddf0b5b commit a56f0d4

File tree

17 files changed

+1182
-347
lines changed

17 files changed

+1182
-347
lines changed

.bassignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
Session.vim
22
go.work
33
go.work.sum
4+
.direnv
5+
.bassignore
6+
.git/index

Dockerfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# syntax = basslang/frontend:dev
2+
3+
(use (*dir*/bass/bass.bass))
4+
5+
(def dist
6+
(bass:dist *context* "dev" "linux" "amd64"))
7+
8+
(-> (from (linux/alpine)
9+
($ cp dist/bass /usr/local/bin/bass))
10+
(with-entrypoint ["bass"]))

Dockerfile.bass

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
(run (from (docker-build *dir* {:os "linux"})
2+
($ --version)))

bass/bass.bass

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@
5959
($ cp -a & $submodule-cp-args)
6060
($ go mod download)))))
6161

62-
(provide [build smoke-test tests docs coverage]
62+
(provide [build dist smoke-test tests docs coverage]
6363
(use (*dir*/buildkit.bass))
6464

6565
(defn dist [src version os arch]
66-
(-> ($ make
66+
(-> ($ make -j
6767
(str "VERSION=" version)
6868
(str "GOOS=" os)
6969
(str "GOARCH=" arch)

bass/bump-frontend

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env bass
2+
3+
(use (*dir*/bass.bass))
4+
5+
(def {:src src
6+
(:version "dev") version
7+
(:os "linux") os
8+
(:arch "amd64") arch}
9+
(next *stdin*))
10+
11+
(def dist
12+
(bass:dist src version os arch))
13+
14+
(def thunk
15+
(-> (from (linux/alpine)
16+
($ cp dist/bass /usr/local/bin/bass))
17+
(with-entrypoint ["bass" "--frontend"])
18+
(with-label :moby.buildkit.frontend.network.none "true")
19+
(with-label :moby.buildkit.frontend.caps
20+
"moby.buildkit.frontend.inputs,moby.buildkit.frontend.subrequests,moby.buildkit.frontend.contexts")))
21+
22+
(let [ref (str "basslang/frontend:" version)
23+
published (publish thunk ref)]
24+
(log "published" :ref published))

cmd/bass/frontend.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"io"
7+
"os"
8+
"path"
9+
"runtime"
10+
11+
"github.com/moby/buildkit/client/llb"
12+
"github.com/moby/buildkit/exporter/containerimage/exptypes"
13+
gwclient "github.com/moby/buildkit/frontend/gateway/client"
14+
"github.com/moby/buildkit/frontend/gateway/grpcclient"
15+
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
16+
"github.com/vito/bass/pkg/bass"
17+
"github.com/vito/bass/pkg/basstls"
18+
"github.com/vito/bass/pkg/cli"
19+
"github.com/vito/bass/pkg/runtimes"
20+
"github.com/vito/bass/pkg/runtimes/util"
21+
)
22+
23+
func frontend(ctx context.Context) error {
24+
err := grpcclient.RunFromEnvironment(ctx, frontendBuild)
25+
if err != nil {
26+
cli.WriteError(ctx, err)
27+
}
28+
29+
return err
30+
}
31+
32+
// mimic dockerfile.v1 frontend
33+
const (
34+
localNameContext = "context"
35+
localNameDockerfile = "dockerfile"
36+
localNameBassTLS = "bass-tls"
37+
keyFilename = "filename"
38+
)
39+
40+
func frontendBuild(ctx context.Context, gw gwclient.Client) (*gwclient.Result, error) {
41+
caps := gw.BuildOpts().Caps
42+
43+
scriptFn := gw.BuildOpts().Opts[keyFilename]
44+
if scriptFn == "" {
45+
scriptFn = "Dockerfile"
46+
}
47+
48+
inputs, err := gw.Inputs(ctx)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
// contextInput, found := inputs[localNameContext]
54+
// if !found {
55+
// contextInput = llb.Local(localNameContext,
56+
// llb.SessionID(gw.BuildOpts().SessionID),
57+
// llb.WithCustomName("[internal] local bass workdir"),
58+
// )
59+
// }
60+
61+
scriptInput, found := inputs[localNameDockerfile]
62+
if !found {
63+
// running from 'docker build', which doesn't set inputs
64+
scriptInput = llb.Local(localNameDockerfile,
65+
llb.SessionID(gw.BuildOpts().SessionID),
66+
llb.WithCustomName("[internal] local bass script"),
67+
)
68+
}
69+
70+
var certsDir string
71+
certsInput, found := inputs[localNameBassTLS]
72+
if found {
73+
certFS, err := util.OpenRefFS(ctx, gw, certsInput, llb.WithCaps(caps))
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
certsDir = basstls.DefaultDir
79+
if err := os.MkdirAll(certsDir, 0700); err != nil {
80+
return nil, err
81+
}
82+
83+
for _, name := range basstls.CAFiles {
84+
source, err := certFS.Open(name)
85+
if err != nil {
86+
return nil, err
87+
}
88+
89+
fi, err := source.Stat()
90+
if err != nil {
91+
return nil, err
92+
}
93+
94+
target, err := os.OpenFile(path.Join(certsDir, name), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode())
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
if _, err := io.Copy(target, source); err != nil {
100+
return nil, err
101+
}
102+
103+
if err := target.Close(); err != nil {
104+
return nil, err
105+
}
106+
107+
if err := source.Close(); err != nil {
108+
return nil, err
109+
}
110+
}
111+
}
112+
113+
scriptFs, err := util.OpenRefFS(ctx, gw, scriptInput, llb.WithCaps(caps))
114+
if err != nil {
115+
return nil, err
116+
}
117+
118+
// contextFs, err := newRefFS(ctx, gw, contextInput, llb.WithCaps(caps))
119+
// if err != nil {
120+
// return nil, err
121+
// }
122+
123+
pool := &runtimes.Pool{}
124+
125+
kitdruntime, err := runtimes.NewBuildkitFrontend(gw, inputs, runtimes.BuildkitConfig{
126+
CertsDir: certsDir,
127+
})
128+
if err != nil {
129+
return nil, err
130+
}
131+
132+
pool.Runtimes = append(pool.Runtimes, runtimes.Assoc{
133+
Runtime: kitdruntime,
134+
Platform: bass.LinuxPlatform,
135+
})
136+
137+
ctx = bass.WithRuntimePool(ctx, pool)
138+
139+
runSt := bass.RunState{
140+
Env: bass.NewEmptyScope(), // TODO: build args
141+
Dir: bass.NewFSPath(scriptFs, bass.ParseFileOrDirPath(path.Dir(scriptFn))),
142+
Stdin: bass.Stdin,
143+
Stdout: bass.Stdout,
144+
}
145+
146+
module := bass.NewRunScope(bass.Ground, runSt)
147+
// directly pass the context by local name, masquerading it as the host path
148+
module.Set("*context*", bass.NewHostDir(localNameContext))
149+
150+
val, err := bass.EvalFSFile(ctx, module, bass.NewFSPath(scriptFs, bass.ParseFileOrDirPath(scriptFn)))
151+
if err != nil {
152+
return nil, err
153+
}
154+
155+
var thunk bass.Thunk
156+
if err := val.Decode(&thunk); err != nil {
157+
return nil, err
158+
}
159+
160+
platform := ocispecs.Platform{
161+
OS: runtime.GOOS,
162+
Architecture: runtime.GOARCH,
163+
}
164+
165+
builder := kitdruntime.NewBuilder(ctx, gw)
166+
167+
ib, err := builder.Build(
168+
ctx,
169+
thunk,
170+
func(st llb.ExecState, sourcePath string) llb.State {
171+
return st.Root()
172+
},
173+
)
174+
if err != nil {
175+
return nil, err
176+
}
177+
178+
def, err := ib.FS.Marshal(ctx, llb.WithCaps(caps))
179+
if err != nil {
180+
return nil, err
181+
}
182+
183+
outRes, err := gw.Solve(ctx, gwclient.SolveRequest{
184+
Definition: def.ToPB(),
185+
})
186+
if err != nil {
187+
return nil, err
188+
}
189+
190+
if _, hasConfig := outRes.Metadata[exptypes.ExporterImageConfigKey]; !hasConfig {
191+
configBytes, err := json.Marshal(ocispecs.Image{
192+
Architecture: platform.Architecture,
193+
OS: platform.OS,
194+
OSVersion: platform.OSVersion,
195+
OSFeatures: platform.OSFeatures,
196+
Config: ib.Config,
197+
})
198+
if err != nil {
199+
return nil, err
200+
}
201+
202+
outRes.AddMeta(exptypes.ExporterImageConfigKey, configBytes)
203+
}
204+
205+
return outRes, nil
206+
}

cmd/bass/main.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"runtime/pprof"
1111
"strings"
1212

13+
"github.com/moby/buildkit/util/appcontext"
1314
flag "github.com/spf13/pflag"
1415
"github.com/vito/bass/pkg/bass"
1516
"github.com/vito/bass/pkg/cli"
@@ -34,6 +35,8 @@ var runnerAddr string
3435
var runLSP bool
3536
var lspLogs string
3637

38+
var runFrontend bool
39+
3740
var profPort int
3841
var profFilePath string
3942

@@ -58,6 +61,8 @@ func init() {
5861
flags.BoolVar(&runLSP, "lsp", false, "run the bass language server")
5962
flags.StringVar(&lspLogs, "lsp-log-file", "", "write language server logs to this file")
6063

64+
flags.BoolVar(&runFrontend, "frontend", false, "run the bass buildkit frontend")
65+
6166
flags.IntVar(&profPort, "profile", 0, "port number to bind for Go HTTP profiling")
6267
flags.StringVar(&profFilePath, "cpu-profile", "", "take a CPU profile and save it to this path")
6368

@@ -76,7 +81,9 @@ func logLevel() zapcore.LevelEnabler {
7681
}
7782

7883
func main() {
79-
ctx := context.Background()
84+
// reusing for convenience; originally for frontend
85+
ctx := appcontext.Context()
86+
8087
ctx = bass.WithTrace(ctx, &bass.Trace{})
8188
ctx = ioctx.StderrToContext(ctx, os.Stderr)
8289

@@ -143,6 +150,10 @@ func root(ctx context.Context) error {
143150
defer pprof.StopCPUProfile()
144151
}
145152

153+
if runFrontend {
154+
return frontend(ctx)
155+
}
156+
146157
config, err := bass.LoadConfig(DefaultConfig)
147158
if err != nil {
148159
cli.WriteError(ctx, err)

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ require (
5858

5959
require (
6060
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
61+
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
6162
github.com/Khan/genqlient v0.5.0 // indirect
6263
github.com/Microsoft/go-winio v0.6.0 // indirect
6364
github.com/charmbracelet/bubbles v0.13.0 // indirect
@@ -66,11 +67,14 @@ require (
6667
github.com/containerd/console v1.0.3 // indirect
6768
github.com/containerd/continuity v0.3.0 // indirect
6869
github.com/containerd/go-runc v1.0.1-0.20230316182144-f5d58d02d6c8 // indirect
70+
github.com/containerd/ttrpc v1.2.1 // indirect
6971
github.com/containerd/typeurl/v2 v2.1.0 // indirect
7072
github.com/creack/pty v1.1.11 // indirect
73+
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
7174
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
7275
github.com/dlclark/regexp2 v1.4.0 // indirect
7376
github.com/docker/docker-credential-helpers v0.7.0 // indirect
77+
github.com/docker/go-connections v0.4.0 // indirect
7478
github.com/docker/go-units v0.5.0 // indirect
7579
github.com/fogleman/ease v0.0.0-20170301025033-8da417bf1776 // indirect
7680
github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect
@@ -94,6 +98,7 @@ require (
9498
github.com/mattn/go-tty v0.0.3 // indirect
9599
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
96100
github.com/mna/pigeon v1.0.1-0.20200224192238-18953b277063 // indirect
101+
github.com/moby/locker v1.0.1 // indirect
97102
github.com/moby/patternmatcher v0.5.0 // indirect
98103
github.com/moby/sys/signal v0.7.0 // indirect
99104
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect

0 commit comments

Comments
 (0)