Clean up client config. Preparation for fixing local dir issues

Signed-off-by: Solomon Hykes <sh.github.6811@hykes.org>
This commit is contained in:
Solomon Hykes 2021-01-21 16:39:29 -08:00
parent eab6028b70
commit 795b7f585c
4 changed files with 112 additions and 65 deletions

View File

@ -31,19 +31,19 @@ const (
bkBootKey = "boot" bkBootKey = "boot"
bkInputKey = "input" bkInputKey = "input"
defaultBootDir = "." // Base client config, for default values & schema validation.
baseClientConfig = `
// FIXME: rename to defaultConfig ? close({
defaultBootScript = ` bootdir: string | *"."
bootdir: string | *"." boot: [...{do:string,...}] | *[
bootscript: [
{ {
do: "local" do: "local"
dir: bootdir dir: bootdir
include: ["*.cue", "cue.mod"] include: ["*.cue", "cue.mod"]
}, }
] ]
` })
`
) )
type Client struct { type Client struct {
@ -54,26 +54,33 @@ type Client struct {
} }
type ClientConfig struct { type ClientConfig struct {
// Buildkit host address, eg. `docker://buildkitd`
Host string Host string
// Env boot script, eg. `[{do:"local",dir:"."}]`
Boot string Boot string
// Env boot dir, eg. `.`
// May be referenced by boot script.
BootDir string BootDir string
// Input overlay, eg. `www: source: #dagger: compute: [{do:"local",dir:"./src"}]`
Input string Input string
} }
func NewClient(ctx context.Context, cfg ClientConfig) (*Client, error) { func NewClient(ctx context.Context, cfg ClientConfig) (result *Client, err error) {
// buildkit client defer func() {
if cfg.Host == "" { if err != nil {
cfg.Host = os.Getenv("BUILDKIT_HOST") // Expand cue errors to get full details
err = cueErr(err)
} }
if cfg.Host == "" { }()
cfg.Host = defaultBuildkitHost // Finalize config values
} localdirs, err := (&cfg).Finalize(ctx)
if cfg.Boot == "" { if err != nil {
cfg.Boot = defaultBootScript return nil, errors.Wrap(err, "client config")
}
if cfg.BootDir == "" {
cfg.BootDir = defaultBootDir
} }
log.Ctx(ctx).Debug().
Interface("cfg", cfg).
Interface("localdirs", localdirs).
Msg("finalized client config")
c, err := bk.New(ctx, cfg.Host) c, err := bk.New(ctx, cfg.Host)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "buildkit client") return nil, errors.Wrap(err, "buildkit client")
@ -81,29 +88,80 @@ func NewClient(ctx context.Context, cfg ClientConfig) (*Client, error) {
return &Client{ return &Client{
c: c, c: c,
cfg: cfg, cfg: cfg,
localdirs: map[string]string{}, localdirs: localdirs,
}, nil }, nil
} }
func (c *Client) LocalDirs(ctx context.Context) ([]string, error) { // Compile config, fill in final values,
boot, err := c.BootScript() // and return a rollup of local directories
if err != nil { // referenced in the config.
return nil, err // Localdirs may be referenced in 2 places:
// 1. Boot script
// 2. Input overlay (FIXME: scan not yet implemented)
func (cfg *ClientConfig) Finalize(ctx context.Context) (map[string]string, error) {
localdirs := map[string]string{}
// buildkit client
if cfg.Host == "" {
cfg.Host = os.Getenv("BUILDKIT_HOST")
} }
return boot.LocalDirs(ctx) if cfg.Host == "" {
cfg.Host = defaultBuildkitHost
}
// Compile cue template for boot script & boot dir
// (using cue because script may reference dir)
v, err := cfg.Compile()
if err != nil {
return nil, errors.Wrap(err, "invalid client config")
}
// Finalize boot script
boot, err := v.Get("boot").Script()
if err != nil {
return nil, errors.Wrap(err, "invalid env boot script")
}
cfg.Boot = string(boot.Value().JSON())
// Scan boot script for references to local dirs, to grant access.
bootLocalDirs, err := boot.LocalDirs(ctx)
if err != nil {
return nil, errors.Wrap(err, "scan boot script for local dir access")
}
// Finalize boot dir
cfg.BootDir, err = v.Get("bootdir").String()
if err != nil {
return nil, errors.Wrap(err, "invalid env boot dir")
}
// Scan boot script for references to local dirs, to grant access.
for _, dir := range bootLocalDirs {
// FIXME: randomize local dir references for security
// (currently a malicious cue package may guess common local paths
// and access the corresponding host directory)
localdirs[dir] = dir
}
// FIXME: scan input overlay for references to local dirs, to grant access.
// See issue #41
return localdirs, nil
} }
func (c *Client) BootScript() (*Script, error) { // Compile client config to a cue value
// FIXME: include host and input.
func (cfg ClientConfig) Compile() (v *Value, err error) {
cc := &Compiler{} cc := &Compiler{}
src, err := cc.Compile("boot.cue", c.cfg.Boot) v, err = cc.Compile("client.cue", baseClientConfig)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "compile") return nil, errors.Wrap(err, "base client config")
} }
src, err = src.MergeTarget(c.cfg.BootDir, "bootdir") if cfg.BootDir != "" {
v, err = v.Merge(cfg.BootDir, "bootdir")
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err, "client config key 'bootdir'")
} }
return src.Get("bootscript").Script() }
if cfg.Boot != "" {
v, err = v.Merge(cfg.Boot, "boot")
if err != nil {
return nil, errors.Wrap(err, "client config key 'boot'")
}
}
return v, nil
} }
func (c *Client) Compute(ctx context.Context) (*Value, error) { func (c *Client) Compute(ctx context.Context) (*Value, error) {
@ -166,22 +224,13 @@ func (c *Client) Compute(ctx context.Context) (*Value, error) {
func (c *Client) buildfn(ctx context.Context, ch chan *bk.SolveStatus, w io.WriteCloser) error { func (c *Client) buildfn(ctx context.Context, ch chan *bk.SolveStatus, w io.WriteCloser) error {
lg := log.Ctx(ctx) lg := log.Ctx(ctx)
boot, err := c.BootScript()
if err != nil {
return errors.Wrap(err, "assemble boot script")
}
bootSource, err := boot.Value().Source()
if err != nil {
return errors.Wrap(err, "serialize boot script")
}
lg.Debug().Bytes("bootSource", bootSource).Msg("assembled boot script")
// Setup solve options // Setup solve options
opts := bk.SolveOpt{ opts := bk.SolveOpt{
FrontendAttrs: map[string]string{ FrontendAttrs: map[string]string{
bkInputKey: c.cfg.Input, bkInputKey: c.cfg.Input,
bkBootKey: string(bootSource), bkBootKey: c.cfg.Boot,
}, },
LocalDirs: map[string]string{}, LocalDirs: c.localdirs,
// FIXME: catch output & return as cue value // FIXME: catch output & return as cue value
Exports: []bk.ExportEntry{ Exports: []bk.ExportEntry{
{ {
@ -192,15 +241,12 @@ func (c *Client) buildfn(ctx context.Context, ch chan *bk.SolveStatus, w io.Writ
}, },
}, },
} }
// Connect local dirs
localdirs, err := c.LocalDirs(ctx)
if err != nil {
return errors.Wrap(err, "connect local dirs")
}
for _, dir := range localdirs {
opts.LocalDirs[dir] = dir
}
// Call buildkit solver // Call buildkit solver
lg.Debug().
Interface("localdirs", opts.LocalDirs).
Interface("attrs", opts.FrontendAttrs).
Interface("host", c.cfg.Host).
Msg("spawning buildkit job")
resp, err := c.c.Build(ctx, opts, "", Compute, ch) resp, err := c.c.Build(ctx, opts, "", Compute, ch)
if err != nil { if err != nil {
return errors.Wrap(bkCleanError(err), "buildkit solve") return errors.Wrap(bkCleanError(err), "buildkit solve")

View File

@ -73,11 +73,12 @@ func (s *Script) LocalDirs(ctx context.Context) ([]string, error) {
var dirs []string var dirs []string
err := s.Walk(ctx, func(op *Op) error { err := s.Walk(ctx, func(op *Op) error {
if err := op.Validate("#Local"); err != nil { if err := op.Validate("#Local"); err != nil {
// Ignore all operations except 'do:"local"'
return nil return nil
} }
dir, err := op.Get("dir").String() dir, err := op.Get("dir").String()
if err != nil { if err != nil {
return err return errors.Wrap(err, "invalid 'local' operation")
} }
dirs = append(dirs, dir) dirs = append(dirs, dir)
return nil return nil

View File

@ -85,11 +85,11 @@ func TestWalkBootScript(t *testing.T) {
ctx := context.TODO() ctx := context.TODO()
cc := &Compiler{} cc := &Compiler{}
cfg, err := cc.Compile("clientconfig.cue", defaultBootScript) cfg, err := cc.Compile("clientconfig.cue", baseClientConfig)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
script, err := cfg.Get("bootscript").Script() script, err := cfg.Get("boot").Script()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -31,7 +31,7 @@ func TestJSON(t *testing.T) {
func TestCompileBootScript(t *testing.T) { func TestCompileBootScript(t *testing.T) {
cc := &Compiler{} cc := &Compiler{}
cfg, err := cc.Compile("boot.cue", defaultBootScript) cfg, err := cc.Compile("boot.cue", baseClientConfig)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }