dagger compute [--input-dir TARGET=DIR] [--input-string TARGET=STRING] [--input-cue CUE] [--input-git REMOTE#REF]
Signed-off-by: Solomon Hykes <sh.github.6811@hykes.org>
This commit is contained in:
parent
af9581c28a
commit
1954c3f731
@ -93,10 +93,7 @@ which hide the complexity of `dagger compute` (but it will always be available t
|
|||||||
Here is an example command, using an example configuration:
|
Here is an example command, using an example configuration:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ dagger compute \
|
$ dagger compute ./examples/simple --input-string www.host=mysuperapp.com --input-dir www.source=.
|
||||||
./examples/simple \
|
|
||||||
--input 'www: hostname: "www.mysuperapp.com"' \
|
|
||||||
--input 'www: source: #dagger: compute: [{do:"fetch-git", remote:"https://github.com/samalba/hello-go", ref:"master"}]'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,6 +12,12 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
env *dagger.Env
|
||||||
|
input *dagger.InputValue
|
||||||
|
updater *dagger.InputValue
|
||||||
|
)
|
||||||
|
|
||||||
var computeCmd = &cobra.Command{
|
var computeCmd = &cobra.Command{
|
||||||
Use: "compute CONFIG",
|
Use: "compute CONFIG",
|
||||||
Short: "Compute a configuration",
|
Short: "Compute a configuration",
|
||||||
@ -27,17 +33,24 @@ var computeCmd = &cobra.Command{
|
|||||||
lg := logger.New()
|
lg := logger.New()
|
||||||
ctx := lg.WithContext(appcontext.Context())
|
ctx := lg.WithContext(appcontext.Context())
|
||||||
|
|
||||||
c, err := dagger.NewClient(ctx, dagger.ClientConfig{
|
if err := updater.SourceFlag().Set(args[0]); err != nil {
|
||||||
Input: viper.GetString("input"),
|
lg.Fatal().Err(err).Msg("invalid local source")
|
||||||
Updater: localUpdater(args[0]),
|
}
|
||||||
})
|
|
||||||
|
if err := env.SetUpdater(updater.Value()); err != nil {
|
||||||
|
lg.Fatal().Err(err).Msg("invalid updater script")
|
||||||
|
}
|
||||||
|
lg.Debug().Str("input", input.Value().SourceUnsafe()).Msg("Setting input")
|
||||||
|
if err := env.SetInput(input.Value()); err != nil {
|
||||||
|
lg.Fatal().Err(err).Msg("invalid input")
|
||||||
|
}
|
||||||
|
lg.Debug().Str("env state", env.State().SourceUnsafe()).Msg("creating client")
|
||||||
|
c, err := dagger.NewClient(ctx, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Fatal().Err(err).Msg("unable to create client")
|
lg.Fatal().Err(err).Msg("unable to create client")
|
||||||
}
|
}
|
||||||
// FIXME: configure which config to compute (duh)
|
|
||||||
// FIXME: configure inputs
|
|
||||||
lg.Info().Msg("running")
|
lg.Info().Msg("running")
|
||||||
output, err := c.Compute(ctx)
|
output, err := c.Compute(ctx, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Fatal().Err(err).Msg("failed to compute")
|
lg.Fatal().Err(err).Msg("failed to compute")
|
||||||
}
|
}
|
||||||
@ -46,18 +59,35 @@ var computeCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func localUpdater(dir string) string {
|
func init() {
|
||||||
return fmt.Sprintf(`[
|
// Why is this stuff here?
|
||||||
{
|
// 1. input must be global for flag parsing
|
||||||
do: "local"
|
// 2. updater must be global for flag parsing
|
||||||
dir: "%s"
|
// 3. env must have same compiler as input & updater,
|
||||||
include: ["*.cue", "cue.mod"]
|
// therefore it must be global too.
|
||||||
}
|
//
|
||||||
]`, dir)
|
// FIXME: roll up InputValue into Env?
|
||||||
|
var err error
|
||||||
|
env, err = dagger.NewEnv()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
// Setup --input-* flags
|
||||||
computeCmd.Flags().String("input", "", "Input overlay")
|
input, err = dagger.NewInputValue(env.Compiler(), "{}")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
computeCmd.Flags().Var(input.StringFlag(), "input-string", "TARGET=STRING")
|
||||||
|
computeCmd.Flags().Var(input.DirFlag(), "input-dir", "TARGET=PATH")
|
||||||
|
computeCmd.Flags().Var(input.GitFlag(), "input-git", "TARGET=REMOTE#REF")
|
||||||
|
computeCmd.Flags().Var(input.CueFlag(), "input-cue", "CUE")
|
||||||
|
|
||||||
|
// Setup (future) --from-* flags
|
||||||
|
updater, err = dagger.NewInputValue(env.Compiler(), "[...{do:string, ...}]")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := viper.BindPFlags(computeCmd.Flags()); err != nil {
|
if err := viper.BindPFlags(computeCmd.Flags()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -37,19 +37,26 @@ type Client struct {
|
|||||||
c *bk.Client
|
c *bk.Client
|
||||||
|
|
||||||
localdirs map[string]string
|
localdirs map[string]string
|
||||||
cfg ClientConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientConfig struct {
|
func NewClient(ctx context.Context, host string) (*Client, error) {
|
||||||
// Buildkit host address, eg. `docker://buildkitd`
|
if host == "" {
|
||||||
Host string
|
host = os.Getenv("BUILDKIT_HOST")
|
||||||
// Script to update the env config, eg . `[{do:"local",dir:"."}]`
|
}
|
||||||
Updater string
|
if host == "" {
|
||||||
// Input values to merge on the base config, eg. `www: source: #dagger: compute: [{do:"local",dir:"./src"}]`
|
host = defaultBuildkitHost
|
||||||
Input string
|
}
|
||||||
|
c, err := bk.New(ctx, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "buildkit client")
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
|
c: c,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(ctx context.Context, cfg ClientConfig) (result *Client, err error) {
|
// FIXME: return completed *Env, instead of *Value
|
||||||
|
func (c *Client) Compute(ctx context.Context, env *Env) (o *Value, err error) {
|
||||||
lg := log.Ctx(ctx)
|
lg := log.Ctx(ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -57,26 +64,11 @@ func NewClient(ctx context.Context, cfg ClientConfig) (result *Client, err error
|
|||||||
err = cueErr(err)
|
err = cueErr(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// Load partial env client-side, to validate & scan local dirs
|
// Scan local dirs to grant access
|
||||||
env, err := NewEnv(cfg.Updater)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "updater")
|
|
||||||
}
|
|
||||||
if err := env.SetInput(cfg.Input); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "input")
|
|
||||||
}
|
|
||||||
localdirs, err := env.LocalDirs(ctx)
|
localdirs, err := env.LocalDirs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "scan local dirs")
|
return nil, errors.Wrap(err, "scan local dirs")
|
||||||
}
|
}
|
||||||
envsrc, err := env.state.SourceString()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
lg.Debug().
|
|
||||||
Str("func", "NewClient").
|
|
||||||
Str("env", envsrc).
|
|
||||||
Msg("loaded partial env client-side")
|
|
||||||
for label, dir := range localdirs {
|
for label, dir := range localdirs {
|
||||||
abs, err := filepath.Abs(dir)
|
abs, err := filepath.Abs(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -84,32 +76,14 @@ func NewClient(ctx context.Context, cfg ClientConfig) (result *Client, err error
|
|||||||
}
|
}
|
||||||
localdirs[label] = abs
|
localdirs[label] = abs
|
||||||
}
|
}
|
||||||
// Configure buildkit client
|
c.localdirs = localdirs
|
||||||
if cfg.Host == "" {
|
|
||||||
cfg.Host = os.Getenv("BUILDKIT_HOST")
|
|
||||||
}
|
|
||||||
if cfg.Host == "" {
|
|
||||||
cfg.Host = defaultBuildkitHost
|
|
||||||
}
|
|
||||||
c, err := bk.New(ctx, cfg.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "buildkit client")
|
|
||||||
}
|
|
||||||
return &Client{
|
|
||||||
c: c,
|
|
||||||
cfg: cfg,
|
|
||||||
localdirs: localdirs,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Compute(ctx context.Context) (*Value, error) {
|
// FIXME: merge this into env output.
|
||||||
lg := log.Ctx(ctx)
|
out, err := env.Compiler().EmptyStruct()
|
||||||
|
|
||||||
cc := &Compiler{}
|
|
||||||
out, err := cc.EmptyStruct()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn Build() goroutine
|
// Spawn Build() goroutine
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
events := make(chan *bk.SolveStatus)
|
events := make(chan *bk.SolveStatus)
|
||||||
@ -118,7 +92,7 @@ func (c *Client) Compute(ctx context.Context) (*Value, error) {
|
|||||||
// Spawn build function
|
// Spawn build function
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
defer outw.Close()
|
defer outw.Close()
|
||||||
return c.buildfn(ctx, events, outw)
|
return c.buildfn(ctx, env, events, outw)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Spawn print function(s)
|
// Spawn print function(s)
|
||||||
@ -154,19 +128,28 @@ func (c *Client) Compute(ctx context.Context) (*Value, error) {
|
|||||||
// Retrieve output
|
// Retrieve output
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
defer outr.Close()
|
defer outr.Close()
|
||||||
return c.outputfn(ctx, outr, out, cc)
|
return c.outputfn(ctx, outr, out, env.cc)
|
||||||
})
|
})
|
||||||
return out, eg.Wait()
|
return out, eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) buildfn(ctx context.Context, ch chan *bk.SolveStatus, w io.WriteCloser) error {
|
func (c *Client) buildfn(ctx context.Context, env *Env, ch chan *bk.SolveStatus, w io.WriteCloser) error {
|
||||||
lg := log.Ctx(ctx)
|
lg := log.Ctx(ctx)
|
||||||
|
|
||||||
|
// Serialize input and updater
|
||||||
|
input, err := env.Input().SourceString()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "serialize env input")
|
||||||
|
}
|
||||||
|
updater, err := env.Updater().Value().SourceString()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "serialize updater 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: input,
|
||||||
bkUpdaterKey: c.cfg.Updater,
|
bkUpdaterKey: updater,
|
||||||
},
|
},
|
||||||
LocalDirs: c.localdirs,
|
LocalDirs: c.localdirs,
|
||||||
// FIXME: catch output & return as cue value
|
// FIXME: catch output & return as cue value
|
||||||
@ -183,7 +166,6 @@ func (c *Client) buildfn(ctx context.Context, ch chan *bk.SolveStatus, w io.Writ
|
|||||||
lg.Debug().
|
lg.Debug().
|
||||||
Interface("localdirs", opts.LocalDirs).
|
Interface("localdirs", opts.LocalDirs).
|
||||||
Interface("attrs", opts.FrontendAttrs).
|
Interface("attrs", opts.FrontendAttrs).
|
||||||
Interface("host", c.cfg.Host).
|
|
||||||
Msg("spawning buildkit job")
|
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 {
|
||||||
|
@ -27,10 +27,13 @@ func Compute(ctx context.Context, c bkgw.Client) (r *bkgw.Result, err error) {
|
|||||||
if o, exists := c.BuildOpts().Opts[bkUpdaterKey]; exists {
|
if o, exists := c.BuildOpts().Opts[bkUpdaterKey]; exists {
|
||||||
updater = o
|
updater = o
|
||||||
}
|
}
|
||||||
env, err := NewEnv(updater)
|
env, err := NewEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := env.SetUpdater(updater); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := env.Update(ctx, s); err != nil {
|
if err := env.Update(ctx, s); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
118
dagger/env.go
118
dagger/env.go
@ -35,39 +35,82 @@ type Env struct {
|
|||||||
cc *Compiler
|
cc *Compiler
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEnv(updater interface{}) (*Env, error) {
|
func (env *Env) Updater() *Script {
|
||||||
var (
|
return env.updater
|
||||||
env = &Env{}
|
|
||||||
cc = &Compiler{}
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
// 1. Updater
|
|
||||||
if updater == nil {
|
|
||||||
updater = "[]"
|
|
||||||
}
|
}
|
||||||
env.updater, err = cc.CompileScript("updater", updater)
|
|
||||||
|
// Set the updater script for this environment.
|
||||||
|
// u may be:
|
||||||
|
// - A compiled script: *Script
|
||||||
|
// - A compiled value: *Value
|
||||||
|
// - A cue source: string, []byte, io.Reader
|
||||||
|
func (env *Env) SetUpdater(u interface{}) error {
|
||||||
|
if v, ok := u.(*Value); ok {
|
||||||
|
updater, err := NewScript(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return errors.Wrap(err, "invalid updater script")
|
||||||
}
|
}
|
||||||
// 2. initialize empty values
|
env.updater = updater
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if updater, ok := u.(*Script); ok {
|
||||||
|
env.updater = updater
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if u == nil {
|
||||||
|
u = "[]"
|
||||||
|
}
|
||||||
|
updater, err := env.cc.CompileScript("updater", u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env.updater = updater
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEnv() (*Env, error) {
|
||||||
|
cc := &Compiler{}
|
||||||
empty, err := cc.EmptyStruct()
|
empty, err := cc.EmptyStruct()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
env.input = empty
|
env := &Env{
|
||||||
env.base = empty
|
cc: cc,
|
||||||
env.state = empty
|
base: empty,
|
||||||
env.output = empty
|
input: empty,
|
||||||
// 3. compiler
|
output: empty,
|
||||||
env.cc = cc
|
state: empty,
|
||||||
|
}
|
||||||
|
if err := env.SetUpdater(nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return env, nil
|
return env, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (env *Env) SetInput(src interface{}) error {
|
func (env *Env) Compiler() *Compiler {
|
||||||
if src == nil {
|
return env.cc
|
||||||
src = "{}"
|
|
||||||
}
|
}
|
||||||
input, err := env.cc.Compile("input", src)
|
|
||||||
|
func (env *Env) State() *Value {
|
||||||
|
return env.state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env *Env) Input() *Value {
|
||||||
|
return env.input
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env *Env) SetInput(i interface{}) error {
|
||||||
|
if input, ok := i.(*Value); ok {
|
||||||
|
return env.set(
|
||||||
|
env.base,
|
||||||
|
input,
|
||||||
|
env.output,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if i == nil {
|
||||||
|
i = "{}"
|
||||||
|
}
|
||||||
|
input, err := env.cc.Compile("input", i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -98,6 +141,14 @@ func (env *Env) Update(ctx context.Context, s Solver) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (env *Env) Base() *Value {
|
||||||
|
return env.base
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env *Env) Output() *Value {
|
||||||
|
return env.output
|
||||||
|
}
|
||||||
|
|
||||||
// Scan all scripts in the environment for references to local directories (do:"local"),
|
// Scan all scripts in the environment for references to local directories (do:"local"),
|
||||||
// and return all referenced directory names.
|
// and return all referenced directory names.
|
||||||
// This is used by clients to grant access to local directories when they are referenced
|
// This is used by clients to grant access to local directories when they are referenced
|
||||||
@ -105,8 +156,15 @@ func (env *Env) Update(ctx context.Context, s Solver) error {
|
|||||||
func (env *Env) LocalDirs(ctx context.Context) (map[string]string, error) {
|
func (env *Env) LocalDirs(ctx context.Context) (map[string]string, error) {
|
||||||
lg := log.Ctx(ctx)
|
lg := log.Ctx(ctx)
|
||||||
dirs := map[string]string{}
|
dirs := map[string]string{}
|
||||||
|
lg.Debug().
|
||||||
|
Str("func", "Env.LocalDirs").
|
||||||
|
Str("state", env.state.SourceUnsafe()).
|
||||||
|
Str("updater", env.updater.Value().SourceUnsafe()).
|
||||||
|
Msg("starting")
|
||||||
|
defer func() {
|
||||||
|
lg.Debug().Str("func", "Env.LocalDirs").Interface("result", dirs).Msg("done")
|
||||||
|
}()
|
||||||
// 1. Walk env state, scan compute script for each component.
|
// 1. Walk env state, scan compute script for each component.
|
||||||
lg.Debug().Msg("walking env client-side for local dirs")
|
|
||||||
_, err := env.Walk(ctx, func(ctx context.Context, c *Component, out *Fillable) error {
|
_, err := env.Walk(ctx, func(ctx context.Context, c *Component, out *Fillable) error {
|
||||||
lg.Debug().
|
lg.Debug().
|
||||||
Str("func", "Env.LocalDirs").
|
Str("func", "Env.LocalDirs").
|
||||||
@ -163,22 +221,24 @@ func (env *Env) Compute(ctx context.Context, s Solver) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this is just a 3-way merge. Add var args to Value.Merge.
|
// FIXME: this is just a 3-way merge. Add var args to Value.Merge.
|
||||||
func (env *Env) set(base, input, output *Value) error {
|
func (env *Env) set(base, input, output *Value) (err error) {
|
||||||
// FIXME: make this cleaner in *Value by keeping intermediary instances
|
// FIXME: make this cleaner in *Value by keeping intermediary instances
|
||||||
// FIXME: state.CueInst() must return an instance with the same
|
// FIXME: state.CueInst() must return an instance with the same
|
||||||
// contents as state.v, for the purposes of cueflow.
|
// contents as state.v, for the purposes of cueflow.
|
||||||
// That is not currently how *Value works, so we prepare the cue
|
// That is not currently how *Value works, so we prepare the cue
|
||||||
// instance manually.
|
// instance manually.
|
||||||
// --> refactor the Value API to do this for us.
|
// --> refactor the Value API to do this for us.
|
||||||
baseInst := base.CueInst()
|
stateInst := env.state.CueInst()
|
||||||
inputInst := input.CueInst()
|
|
||||||
outputInst := output.CueInst()
|
|
||||||
|
|
||||||
stateInst, err := baseInst.Fill(inputInst.Value())
|
stateInst, err = stateInst.Fill(base.val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "merge base & input")
|
return errors.Wrap(err, "merge base & input")
|
||||||
}
|
}
|
||||||
stateInst, err = stateInst.Fill(outputInst.Value())
|
stateInst, err = stateInst.Fill(input.val)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "merge base & input")
|
||||||
|
}
|
||||||
|
stateInst, err = stateInst.Fill(output.val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "merge output with base & input")
|
return errors.Wrap(err, "merge output with base & input")
|
||||||
}
|
}
|
||||||
|
71
dagger/env_test.go
Normal file
71
dagger/env_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package dagger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimpleEnvSet(t *testing.T) {
|
||||||
|
env, err := NewEnv()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := env.SetInput(`hello: "world"`); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hello, err := env.State().Get("hello").String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hello != "world" {
|
||||||
|
t.Fatal(hello)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleEnvSetFromInputValue(t *testing.T) {
|
||||||
|
env, err := NewEnv()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := env.Compiler().Compile("", `hello: "world"`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := env.SetInput(v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hello, err := env.State().Get("hello").String()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hello != "world" {
|
||||||
|
t.Fatal(hello)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvInputComponent(t *testing.T) {
|
||||||
|
env, err := NewEnv()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := env.Compiler().Compile("", `foo: #dagger: compute: [{do:"local",dir:"."}]`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := env.SetInput(v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
localdirs, err := env.LocalDirs(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(localdirs) != 1 {
|
||||||
|
t.Fatal(localdirs)
|
||||||
|
}
|
||||||
|
if dir, ok := localdirs["."]; !ok || dir != "." {
|
||||||
|
t.Fatal(localdirs)
|
||||||
|
}
|
||||||
|
}
|
239
dagger/input.go
Normal file
239
dagger/input.go
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
package dagger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"cuelang.org/go/cue"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A mutable cue value with an API suitable for user inputs,
|
||||||
|
// such as command-line flag parsing.
|
||||||
|
type InputValue struct {
|
||||||
|
root *Value
|
||||||
|
cc *Compiler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iv *InputValue) Value() *Value {
|
||||||
|
return iv.root
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iv *InputValue) String() string {
|
||||||
|
s, _ := iv.root.SourceString()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInputValue(cc *Compiler, base interface{}) (*InputValue, error) {
|
||||||
|
root, err := cc.Compile("base", base)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &InputValue{
|
||||||
|
cc: cc,
|
||||||
|
root: root,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iv *InputValue) Set(s string, enc func(string, *Compiler) (interface{}, error)) error {
|
||||||
|
// Split from eg. 'foo.bar={bla:"bla"}`
|
||||||
|
k, vRaw := splitkv(s)
|
||||||
|
v, err := enc(vRaw, iv.cc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
root, err := iv.root.MergePath(v, k)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
iv.root = root
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapter to receive string values from pflag
|
||||||
|
func (iv *InputValue) StringFlag() pflag.Value {
|
||||||
|
return stringFlag{
|
||||||
|
iv: iv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringFlag struct {
|
||||||
|
iv *InputValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf stringFlag) Set(s string) error {
|
||||||
|
return sf.iv.Set(s, func(s string, _ *Compiler) (interface{}, error) {
|
||||||
|
return s, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf stringFlag) String() string {
|
||||||
|
return sf.iv.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf stringFlag) Type() string {
|
||||||
|
return "STRING"
|
||||||
|
}
|
||||||
|
|
||||||
|
// DIR FLAG
|
||||||
|
// Receive a local directory path and translate it into a component
|
||||||
|
func (iv *InputValue) DirFlag(include ...string) pflag.Value {
|
||||||
|
if include == nil {
|
||||||
|
include = []string{}
|
||||||
|
}
|
||||||
|
return dirFlag{
|
||||||
|
iv: iv,
|
||||||
|
include: include,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type dirFlag struct {
|
||||||
|
iv *InputValue
|
||||||
|
include []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f dirFlag) Set(s string) error {
|
||||||
|
return f.iv.Set(s, func(s string, cc *Compiler) (interface{}, error) {
|
||||||
|
// FIXME: this is a hack because cue API can't merge into a list
|
||||||
|
include, err := json.Marshal(f.include)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cc.Compile("", fmt.Sprintf(
|
||||||
|
`#dagger: compute: [{do:"local",dir:"%s", include:%s}]`,
|
||||||
|
s,
|
||||||
|
include,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f dirFlag) String() string {
|
||||||
|
return f.iv.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f dirFlag) Type() string {
|
||||||
|
return "PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GIT FLAG
|
||||||
|
// Receive a git repository reference and translate it into a component
|
||||||
|
func (iv *InputValue) GitFlag() pflag.Value {
|
||||||
|
return gitFlag{
|
||||||
|
iv: iv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type gitFlag struct {
|
||||||
|
iv *InputValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f gitFlag) Set(s string) error {
|
||||||
|
return f.iv.Set(s, func(s string, cc *Compiler) (interface{}, error) {
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid git url")
|
||||||
|
}
|
||||||
|
ref := u.Fragment // eg. #main
|
||||||
|
u.Fragment = ""
|
||||||
|
remote := u.String()
|
||||||
|
|
||||||
|
return cc.Compile("", fmt.Sprintf(
|
||||||
|
`#dagger: compute: [{do:"fetch-git", remote:"%s", ref:"%s"}]`,
|
||||||
|
remote,
|
||||||
|
ref,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f gitFlag) String() string {
|
||||||
|
return f.iv.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f gitFlag) Type() string {
|
||||||
|
return "REMOTE,REF"
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOURCE FLAG
|
||||||
|
// Adapter to receive a simple source description and translate it to a loader script.
|
||||||
|
// For example 'git+https://github.com/cuelang/cue#master` -> [{do:"git",remote:"https://github.com/cuelang/cue",ref:"master"}]
|
||||||
|
|
||||||
|
func (iv *InputValue) SourceFlag() pflag.Value {
|
||||||
|
return sourceFlag{
|
||||||
|
iv: iv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sourceFlag struct {
|
||||||
|
iv *InputValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f sourceFlag) Set(s string) error {
|
||||||
|
return f.iv.Set(s, func(s string, cc *Compiler) (interface{}, error) {
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch u.Scheme {
|
||||||
|
case "", "file":
|
||||||
|
return cc.Compile(
|
||||||
|
"source",
|
||||||
|
// FIXME: include only cue files as a shortcut. Make this configurable somehow.
|
||||||
|
fmt.Sprintf(`[{do:"local",dir:"%s",include:["*.cue","cue.mod"]}]`, u.Host+u.Path),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported source scheme: %q", u.Scheme)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f sourceFlag) String() string {
|
||||||
|
return f.iv.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f sourceFlag) Type() string {
|
||||||
|
return "PATH | file://PATH | git+ssh://HOST/PATH | git+https://HOST/PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAW CUE FLAG
|
||||||
|
// Adapter to receive raw cue values from pflag
|
||||||
|
func (iv *InputValue) CueFlag() pflag.Value {
|
||||||
|
return cueFlag{
|
||||||
|
iv: iv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cueFlag struct {
|
||||||
|
iv *InputValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f cueFlag) Set(s string) error {
|
||||||
|
return f.iv.Set(s, func(s string, cc *Compiler) (interface{}, error) {
|
||||||
|
return cc.Compile("cue input", s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f cueFlag) String() string {
|
||||||
|
return f.iv.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f cueFlag) Type() string {
|
||||||
|
return "CUE"
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTILITIES
|
||||||
|
|
||||||
|
func splitkv(kv string) (cue.Path, string) {
|
||||||
|
parts := strings.SplitN(kv, "=", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
if parts[0] == "." || parts[0] == "" {
|
||||||
|
return cue.MakePath(), parts[1]
|
||||||
|
}
|
||||||
|
return cue.ParsePath(parts[0]), parts[1]
|
||||||
|
}
|
||||||
|
if len(parts) == 1 {
|
||||||
|
return cue.MakePath(), parts[0]
|
||||||
|
}
|
||||||
|
return cue.MakePath(), ""
|
||||||
|
}
|
35
dagger/input_test.go
Normal file
35
dagger/input_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package dagger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnvInputFlag(t *testing.T) {
|
||||||
|
env, err := NewEnv()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
input, err := NewInputValue(env.Compiler(), `{}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := input.DirFlag().Set("www.source=."); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := env.SetInput(input.Value()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
localdirs, err := env.LocalDirs(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(localdirs) != 1 {
|
||||||
|
t.Fatal(localdirs)
|
||||||
|
}
|
||||||
|
if dir, ok := localdirs["."]; !ok || dir != "." {
|
||||||
|
t.Fatal(localdirs)
|
||||||
|
}
|
||||||
|
}
|
@ -101,6 +101,11 @@ func (s *Script) Walk(ctx context.Context, fn func(op *Op) error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Script) LocalDirs(ctx context.Context) (map[string]string, error) {
|
func (s *Script) LocalDirs(ctx context.Context) (map[string]string, error) {
|
||||||
|
lg := log.Ctx(ctx)
|
||||||
|
lg.Debug().
|
||||||
|
Str("func", "Script.LocalDirs").
|
||||||
|
Str("location", s.Value().Path().String()).
|
||||||
|
Msg("starting")
|
||||||
dirs := map[string]string{}
|
dirs := map[string]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 {
|
||||||
@ -114,5 +119,11 @@ func (s *Script) LocalDirs(ctx context.Context) (map[string]string, error) {
|
|||||||
dirs[dir] = dir
|
dirs[dir] = dir
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
lg.Debug().
|
||||||
|
Str("func", "Script.LocalDirs").
|
||||||
|
Str("location", s.Value().Path().String()).
|
||||||
|
Interface("err", err).
|
||||||
|
Interface("result", dirs).
|
||||||
|
Msg("done")
|
||||||
return dirs, err
|
return dirs, err
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,7 @@ func wrapValue(v cue.Value, inst *cue.Instance, cc *Compiler) *Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill is a concurrency safe wrapper around cue.Value.Fill()
|
// Fill the value in-place, unlike Merge which returns a copy.
|
||||||
// This is the only method which changes the value in-place.
|
|
||||||
func (v *Value) Fill(x interface{}) error {
|
func (v *Value) Fill(x interface{}) error {
|
||||||
v.cc.Lock()
|
v.cc.Lock()
|
||||||
defer v.cc.Unlock()
|
defer v.cc.Unlock()
|
||||||
@ -96,6 +95,11 @@ func (v *Value) String() (string, error) {
|
|||||||
return v.val.String()
|
return v.val.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Value) SourceUnsafe() string {
|
||||||
|
s, _ := v.SourceString()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// Proxy function to the underlying cue.Value
|
// Proxy function to the underlying cue.Value
|
||||||
func (v *Value) Path() cue.Path {
|
func (v *Value) Path() cue.Path {
|
||||||
return v.val.Path()
|
return v.val.Path()
|
||||||
|
@ -94,7 +94,7 @@ test::exec(){
|
|||||||
test::one "Exec: env valid" --exit=0 --stdout={} \
|
test::one "Exec: env valid" --exit=0 --stdout={} \
|
||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/exec/env/valid
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/exec/env/valid
|
||||||
test::one "Exec: env with overlay" --exit=0 \
|
test::one "Exec: env with overlay" --exit=0 \
|
||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input 'bar: "overlay environment"' "$d"/exec/env/overlay
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input-cue 'bar: "overlay environment"' "$d"/exec/env/overlay
|
||||||
|
|
||||||
test::one "Exec: non existent dir" --exit=0 --stdout={} \
|
test::one "Exec: non existent dir" --exit=0 --stdout={} \
|
||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/exec/dir/doesnotexist
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/exec/dir/doesnotexist
|
||||||
@ -191,13 +191,13 @@ test::input() {
|
|||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/input/simple
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/input/simple
|
||||||
|
|
||||||
test::one "Input: simple input" --exit=0 --stdout='{"in":"foobar","test":"received: foobar"}' \
|
test::one "Input: simple input" --exit=0 --stdout='{"in":"foobar","test":"received: foobar"}' \
|
||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input 'in: "foobar"' "$d"/input/simple
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input-cue 'in: "foobar"' "$d"/input/simple
|
||||||
|
|
||||||
test::one "Input: default values" --exit=0 --stdout='{"in":"default input","test":"received: default input"}' \
|
test::one "Input: default values" --exit=0 --stdout='{"in":"default input","test":"received: default input"}' \
|
||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/input/default
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/input/default
|
||||||
|
|
||||||
test::one "Input: override default value" --exit=0 --stdout='{"in":"foobar","test":"received: foobar"}' \
|
test::one "Input: override default value" --exit=0 --stdout='{"in":"foobar","test":"received: foobar"}' \
|
||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input 'in: "foobar"' "$d"/input/default
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute --input-cue 'in: "foobar"' "$d"/input/default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
1
go.mod
1
go.mod
@ -11,6 +11,7 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/rs/zerolog v1.20.0
|
github.com/rs/zerolog v1.20.0
|
||||||
github.com/spf13/cobra v1.0.0
|
github.com/spf13/cobra v1.0.0
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/spf13/viper v1.7.0
|
github.com/spf13/viper v1.7.0
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85
|
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||||
|
Reference in New Issue
Block a user