Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Buildkit frontend #272

Merged
merged 9 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .bassignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Session.vim
go.work
go.work.sum
.direnv
.bassignore
.git/index
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pkg/runtimes/bin/exe.*
cmd/bass/bass

# created by tests, don't want it committed
pkg/runtimes/testdata/memo/bass.lock
pkg/runtimes/testdata/*.tar
hack/cni-test/plugins/
iptables.rules

Expand Down
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# syntax = basslang/frontend:dev

(use (*dir*/bass/bass.bass))

(def dist
(bass:dist *dir* "dev" "linux" "amd64"))

(-> (from (linux/alpine)
($ cp dist/bass /usr/local/bin/bass))
(with-entrypoint ["bass"]))
3 changes: 3 additions & 0 deletions Dockerfile.bass
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(run (from (docker-build *dir* {:os "linux"})
($ bass --version)
($$ --version)))
2 changes: 1 addition & 1 deletion bass/bass.bass
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
($ cp -a & $submodule-cp-args)
($ go mod download)))))

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

(defn dist [src version os arch]
Expand Down
2 changes: 1 addition & 1 deletion bass/bass.lock
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ memos: {
}
output: {
string: {
value: "0fd154b53db44c0dced9b61092db79953d69bd13"
value: "a6c8ee8f81531c08c5136feca23cc6cca0be3b24"
}
}
}
Expand Down
24 changes: 24 additions & 0 deletions bass/bump-frontend
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bass

(use (*dir*/bass.bass))

(def {:src src
(:version "dev") version
(:os "linux") os
(:arch "amd64") arch}
(next *stdin*))

(def dist
(bass:dist src version os arch))

(def thunk
(-> (from (linux/alpine)
($ cp dist/bass /usr/local/bin/bass))
(with-entrypoint ["bass" "--frontend"])
(with-label :moby.buildkit.frontend.network.none "true")
(with-label :moby.buildkit.frontend.caps
"moby.buildkit.frontend.inputs,moby.buildkit.frontend.subrequests,moby.buildkit.frontend.contexts")))

(let [ref (str "basslang/frontend:" version)
published (publish thunk ref)]
(log "published" :ref published))
259 changes: 259 additions & 0 deletions cmd/bass/frontend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
package main

import (
"context"
"encoding/json"
"fmt"
"io"
"io/fs"
"os"
"path"
"runtime"
"strings"

"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/frontend/gateway/grpcclient"
"github.com/moby/buildkit/util/apicaps"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/vito/bass/pkg/bass"
"github.com/vito/bass/pkg/basstls"
"github.com/vito/bass/pkg/cli"
"github.com/vito/bass/pkg/runtimes"
"github.com/vito/bass/pkg/runtimes/util"
)

func frontend(ctx context.Context) error {
err := grpcclient.RunFromEnvironment(ctx, frontendBuild)
if err != nil {
cli.WriteError(ctx, err)
}

return err
}

// mimic dockerfile.v1 frontend
const (
buildArgPrefix = "build-arg:"
localNameContext = "context"
localNameDockerfile = "dockerfile"
localNameBassTLS = "bass-tls"
keyFilename = "filename"
)

type InputsFilesystem struct {
ctx context.Context
gw gwclient.Client
caps apicaps.CapSet
inputs map[string]llb.State
}

var _ bass.Filesystem = &InputsFilesystem{}

func (fs *InputsFilesystem) FS(contextDir string) (fs.FS, error) {
input, found := fs.inputs[contextDir]
if !found {
return nil, fmt.Errorf("unknown input: %s", contextDir)
}

return util.OpenRefFS(fs.ctx, fs.gw, input, llb.WithCaps(fs.caps))
}

func (fs *InputsFilesystem) Write(path string, r io.Reader) error {
return nil
}

func frontendBuild(ctx context.Context, gw gwclient.Client) (*gwclient.Result, error) {
caps := gw.BuildOpts().Caps
opts := gw.BuildOpts().Opts

scriptFn := gw.BuildOpts().Opts[keyFilename]
if scriptFn == "" {
scriptFn = "Dockerfile"
}

inputs, err := gw.Inputs(ctx)
if err != nil {
return nil, err
}

_, found := inputs[localNameContext]
if !found {
// running from 'docker build', which doesn't set inputs
inputs[localNameContext] = llb.Local(localNameContext,
llb.SessionID(gw.BuildOpts().SessionID),
llb.WithCustomName("[internal] local bass workdir"),
)
}

scriptInput, found := inputs[localNameDockerfile]
if !found {
// running from 'docker build', which doesn't set inputs
scriptInput = llb.Local(localNameDockerfile,
llb.SessionID(gw.BuildOpts().SessionID),
llb.WithCustomName("[internal] local bass script"),
)

inputs[localNameDockerfile] = scriptInput
}

// Override real filesystem with one that knows how to read directly from the
// given inputs, and discard writes.
bass.FS = &InputsFilesystem{
ctx: ctx,
gw: gw,
caps: caps,
inputs: inputs,
}

var certsDir string
certsInput, found := inputs[localNameBassTLS]
if found {
certFS, err := util.OpenRefFS(ctx, gw, certsInput, llb.WithCaps(caps))
if err != nil {
return nil, err
}

certsDir = basstls.DefaultDir
if err := os.MkdirAll(certsDir, 0700); err != nil {
return nil, err
}

for _, name := range basstls.CAFiles {
source, err := certFS.Open(name)
if err != nil {
return nil, err
}

fi, err := source.Stat()
if err != nil {
return nil, err
}

target, err := os.OpenFile(path.Join(certsDir, name), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode())
if err != nil {
return nil, err
}

if _, err := io.Copy(target, source); err != nil {
return nil, err
}

if err := target.Close(); err != nil {
return nil, err
}

if err := source.Close(); err != nil {
return nil, err
}
}
}

scriptFs, err := util.OpenRefFS(ctx, gw, scriptInput, llb.WithCaps(caps))
if err != nil {
return nil, err
}

// contextFs, err := newRefFS(ctx, gw, contextInput, llb.WithCaps(caps))
// if err != nil {
// return nil, err
// }

pool := &runtimes.Pool{}

kitdruntime, err := runtimes.NewBuildkitFrontend(gw, inputs, runtimes.BuildkitConfig{
CertsDir: certsDir,
})
if err != nil {
return nil, err
}

pool.Runtimes = append(pool.Runtimes, runtimes.Assoc{
Runtime: kitdruntime,
Platform: bass.LinuxPlatform,
})

ctx = bass.WithRuntimePool(ctx, pool)

args := filter(opts, buildArgPrefix)
env := bass.NewEmptyScope()
for k, v := range args {
env.Set(bass.Symbol(k), bass.String(v))
}

runSt := bass.RunState{
Env: env,
// directly pass the context by local name, masquerading it as the host path
Dir: bass.NewHostDir(localNameContext),
Stdin: bass.Stdin,
Stdout: bass.Stdout,
}

module := bass.NewRunScope(bass.Ground, runSt)

val, err := bass.EvalFSFile(ctx, module, bass.NewFSPath(scriptFs, bass.ParseFileOrDirPath(scriptFn)))
if err != nil {
return nil, err
}

var thunk bass.Thunk
if err := val.Decode(&thunk); err != nil {
return nil, err
}

platform := ocispecs.Platform{
OS: runtime.GOOS,
Architecture: runtime.GOARCH,
}

builder := kitdruntime.NewBuilder(ctx, gw)

ib, err := builder.Build(
ctx,
thunk,
false, // don't run any entrypoint
)
if err != nil {
return nil, err
}

def, err := ib.FS.Marshal(ctx, llb.WithCaps(caps))
if err != nil {
return nil, err
}

outRes, err := gw.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}

if _, hasConfig := outRes.Metadata[exptypes.ExporterImageConfigKey]; !hasConfig {
configBytes, err := json.Marshal(ocispecs.Image{
Architecture: platform.Architecture,
OS: platform.OS,
OSVersion: platform.OSVersion,
OSFeatures: platform.OSFeatures,
Config: ib.Config,
})
if err != nil {
return nil, err
}

outRes.AddMeta(exptypes.ExporterImageConfigKey, configBytes)
}

return outRes, nil
}

func filter(opt map[string]string, key string) map[string]string {
m := map[string]string{}
for k, v := range opt {
if strings.HasPrefix(k, key) {
m[strings.TrimPrefix(k, key)] = v
}
}
return m
}
13 changes: 12 additions & 1 deletion cmd/bass/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"runtime/pprof"
"strings"

"github.com/moby/buildkit/util/appcontext"
flag "github.com/spf13/pflag"
"github.com/vito/bass/pkg/bass"
"github.com/vito/bass/pkg/cli"
Expand All @@ -34,6 +35,8 @@ var runnerAddr string
var runLSP bool
var lspLogs string

var runFrontend bool

var profPort int
var profFilePath string

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

flags.BoolVar(&runFrontend, "frontend", false, "run the bass buildkit frontend")

flags.IntVar(&profPort, "profile", 0, "port number to bind for Go HTTP profiling")
flags.StringVar(&profFilePath, "cpu-profile", "", "take a CPU profile and save it to this path")

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

func main() {
ctx := context.Background()
// reusing for convenience; originally for frontend
ctx := appcontext.Context()

ctx = bass.WithTrace(ctx, &bass.Trace{})
ctx = ioctx.StderrToContext(ctx, os.Stderr)

Expand Down Expand Up @@ -154,6 +161,10 @@ func root(ctx context.Context) error {
defer pprof.StopCPUProfile()
}

if runFrontend {
return frontend(ctx)
}

config, err := bass.LoadConfig(DefaultConfig)
if err != nil {
cli.WriteError(ctx, err)
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dagger.io/dagger v0.5.2 h1:xNMUnLWqcsb5UrvqOgJx4MJVfe7xDb50kBMXdC63Cjs=
dagger.io/dagger v0.5.2/go.mod h1:1nbGnLdIfoBV2ahbQjheI//SNGz+b5q1jqf0A+pJ+Oc=
dagger.io/dagger v0.6.0 h1:3cN0QxS/re2RKyHW3BGyk/Hz7Ux46EfvB4zbZLA/X6o=
dagger.io/dagger v0.6.0/go.mod h1:/sSGPh+1LInVuHzTkkr1pZ5N0BAEDoqJ94eM2Xoh/iE=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
Expand Down
2 changes: 1 addition & 1 deletion nix/vendorSha256.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sha256-dzdfzE4r36vx2HoWPGQyM/wnSAH2cnTVfoePFRLekD0=
sha256-tUuwQbJZwz5yaKj559CsIcDTOpRojdR6EV7jVI6Elz8=
Loading