commit
6e31193d64
@ -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:
|
||||
|
||||
```
|
||||
$ dagger compute \
|
||||
./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"}]'
|
||||
$ dagger compute ./examples/simple --input-string www.host=mysuperapp.com --input-dir www.source=.
|
||||
```
|
||||
|
||||
|
||||
|
@ -12,6 +12,12 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
env *dagger.Env
|
||||
input *dagger.InputValue
|
||||
updater *dagger.InputValue
|
||||
)
|
||||
|
||||
var computeCmd = &cobra.Command{
|
||||
Use: "compute CONFIG",
|
||||
Short: "Compute a configuration",
|
||||
@ -27,17 +33,24 @@ var computeCmd = &cobra.Command{
|
||||
lg := logger.New()
|
||||
ctx := lg.WithContext(appcontext.Context())
|
||||
|
||||
c, err := dagger.NewClient(ctx, dagger.ClientConfig{
|
||||
Input: viper.GetString("input"),
|
||||
Updater: localUpdater(args[0]),
|
||||
})
|
||||
if err := updater.SourceFlag().Set(args[0]); err != nil {
|
||||
lg.Fatal().Err(err).Msg("invalid local source")
|
||||
}
|
||||
|
||||
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 {
|
||||
lg.Fatal().Err(err).Msg("unable to create client")
|
||||
}
|
||||
// FIXME: configure which config to compute (duh)
|
||||
// FIXME: configure inputs
|
||||
lg.Info().Msg("running")
|
||||
output, err := c.Compute(ctx)
|
||||
output, err := c.Compute(ctx, env)
|
||||
if err != nil {
|
||||
lg.Fatal().Err(err).Msg("failed to compute")
|
||||
}
|
||||
@ -46,18 +59,35 @@ var computeCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func localUpdater(dir string) string {
|
||||
return fmt.Sprintf(`[
|
||||
{
|
||||
do: "local"
|
||||
dir: "%s"
|
||||
include: ["*.cue", "cue.mod"]
|
||||
}
|
||||
]`, dir)
|
||||
}
|
||||
|
||||
func init() {
|
||||
computeCmd.Flags().String("input", "", "Input overlay")
|
||||
// Why is this stuff here?
|
||||
// 1. input must be global for flag parsing
|
||||
// 2. updater must be global for flag parsing
|
||||
// 3. env must have same compiler as input & updater,
|
||||
// therefore it must be global too.
|
||||
//
|
||||
// FIXME: roll up InputValue into Env?
|
||||
var err error
|
||||
env, err = dagger.NewEnv()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Setup --input-* flags
|
||||
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 {
|
||||
panic(err)
|
||||
|
@ -37,19 +37,26 @@ type Client struct {
|
||||
c *bk.Client
|
||||
|
||||
localdirs map[string]string
|
||||
cfg ClientConfig
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
// Buildkit host address, eg. `docker://buildkitd`
|
||||
Host string
|
||||
// Script to update the env config, eg . `[{do:"local",dir:"."}]`
|
||||
Updater string
|
||||
// Input values to merge on the base config, eg. `www: source: #dagger: compute: [{do:"local",dir:"./src"}]`
|
||||
Input string
|
||||
func NewClient(ctx context.Context, host string) (*Client, error) {
|
||||
if host == "" {
|
||||
host = os.Getenv("BUILDKIT_HOST")
|
||||
}
|
||||
if host == "" {
|
||||
host = defaultBuildkitHost
|
||||
}
|
||||
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)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@ -57,26 +64,11 @@ func NewClient(ctx context.Context, cfg ClientConfig) (result *Client, err error
|
||||
err = cueErr(err)
|
||||
}
|
||||
}()
|
||||
// Load partial env client-side, to validate & scan local dirs
|
||||
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")
|
||||
}
|
||||
// Scan local dirs to grant access
|
||||
localdirs, err := env.LocalDirs(ctx)
|
||||
if err != nil {
|
||||
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 {
|
||||
abs, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
@ -84,32 +76,14 @@ func NewClient(ctx context.Context, cfg ClientConfig) (result *Client, err error
|
||||
}
|
||||
localdirs[label] = abs
|
||||
}
|
||||
// Configure buildkit client
|
||||
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
|
||||
}
|
||||
c.localdirs = localdirs
|
||||
|
||||
func (c *Client) Compute(ctx context.Context) (*Value, error) {
|
||||
lg := log.Ctx(ctx)
|
||||
|
||||
cc := &Compiler{}
|
||||
out, err := cc.EmptyStruct()
|
||||
// FIXME: merge this into env output.
|
||||
out, err := env.Compiler().EmptyStruct()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Spawn Build() goroutine
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
events := make(chan *bk.SolveStatus)
|
||||
@ -118,7 +92,7 @@ func (c *Client) Compute(ctx context.Context) (*Value, error) {
|
||||
// Spawn build function
|
||||
eg.Go(func() error {
|
||||
defer outw.Close()
|
||||
return c.buildfn(ctx, events, outw)
|
||||
return c.buildfn(ctx, env, events, outw)
|
||||
})
|
||||
|
||||
// Spawn print function(s)
|
||||
@ -154,19 +128,28 @@ func (c *Client) Compute(ctx context.Context) (*Value, error) {
|
||||
// Retrieve output
|
||||
eg.Go(func() error {
|
||||
defer outr.Close()
|
||||
return c.outputfn(ctx, outr, out, cc)
|
||||
return c.outputfn(ctx, outr, out, env.cc)
|
||||
})
|
||||
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)
|
||||
|
||||
// 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
|
||||
opts := bk.SolveOpt{
|
||||
FrontendAttrs: map[string]string{
|
||||
bkInputKey: c.cfg.Input,
|
||||
bkUpdaterKey: c.cfg.Updater,
|
||||
bkInputKey: input,
|
||||
bkUpdaterKey: updater,
|
||||
},
|
||||
LocalDirs: c.localdirs,
|
||||
// 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().
|
||||
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)
|
||||
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 {
|
||||
updater = o
|
||||
}
|
||||
env, err := NewEnv(updater)
|
||||
env, err := NewEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := env.SetUpdater(updater); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := env.Update(ctx, s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
118
dagger/env.go
118
dagger/env.go
@ -35,39 +35,82 @@ type Env struct {
|
||||
cc *Compiler
|
||||
}
|
||||
|
||||
func NewEnv(updater interface{}) (*Env, error) {
|
||||
var (
|
||||
env = &Env{}
|
||||
cc = &Compiler{}
|
||||
err error
|
||||
)
|
||||
// 1. Updater
|
||||
if updater == nil {
|
||||
updater = "[]"
|
||||
func (env *Env) Updater() *Script {
|
||||
return env.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 {
|
||||
return errors.Wrap(err, "invalid updater script")
|
||||
}
|
||||
env.updater = updater
|
||||
return nil
|
||||
}
|
||||
env.updater, err = cc.CompileScript("updater", updater)
|
||||
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 nil, err
|
||||
return err
|
||||
}
|
||||
// 2. initialize empty values
|
||||
env.updater = updater
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewEnv() (*Env, error) {
|
||||
cc := &Compiler{}
|
||||
empty, err := cc.EmptyStruct()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env.input = empty
|
||||
env.base = empty
|
||||
env.state = empty
|
||||
env.output = empty
|
||||
// 3. compiler
|
||||
env.cc = cc
|
||||
env := &Env{
|
||||
cc: cc,
|
||||
base: empty,
|
||||
input: empty,
|
||||
output: empty,
|
||||
state: empty,
|
||||
}
|
||||
if err := env.SetUpdater(nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func (env *Env) SetInput(src interface{}) error {
|
||||
if src == nil {
|
||||
src = "{}"
|
||||
func (env *Env) Compiler() *Compiler {
|
||||
return env.cc
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
input, err := env.cc.Compile("input", src)
|
||||
if i == nil {
|
||||
i = "{}"
|
||||
}
|
||||
input, err := env.cc.Compile("input", i)
|
||||
if err != nil {
|
||||
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"),
|
||||
// and return all referenced directory names.
|
||||
// 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) {
|
||||
lg := log.Ctx(ctx)
|
||||
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.
|
||||
lg.Debug().Msg("walking env client-side for local dirs")
|
||||
_, err := env.Walk(ctx, func(ctx context.Context, c *Component, out *Fillable) error {
|
||||
lg.Debug().
|
||||
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.
|
||||
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: state.CueInst() must return an instance with the same
|
||||
// contents as state.v, for the purposes of cueflow.
|
||||
// That is not currently how *Value works, so we prepare the cue
|
||||
// instance manually.
|
||||
// --> refactor the Value API to do this for us.
|
||||
baseInst := base.CueInst()
|
||||
inputInst := input.CueInst()
|
||||
outputInst := output.CueInst()
|
||||
stateInst := env.state.CueInst()
|
||||
|
||||
stateInst, err := baseInst.Fill(inputInst.Value())
|
||||
stateInst, err = stateInst.Fill(base.val)
|
||||
if err != nil {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
@ -102,6 +102,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) {
|
||||
lg := log.Ctx(ctx)
|
||||
lg.Debug().
|
||||
Str("func", "Script.LocalDirs").
|
||||
Str("location", s.Value().Path().String()).
|
||||
Msg("starting")
|
||||
dirs := map[string]string{}
|
||||
err := s.Walk(ctx, func(op *Op) error {
|
||||
if err := op.Validate("#Local"); err != nil {
|
||||
@ -115,5 +120,11 @@ func (s *Script) LocalDirs(ctx context.Context) (map[string]string, error) {
|
||||
dirs[dir] = dir
|
||||
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
|
||||
}
|
||||
|
@ -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()
|
||||
// This is the only method which changes the value in-place.
|
||||
// Fill the value in-place, unlike Merge which returns a copy.
|
||||
func (v *Value) Fill(x interface{}) error {
|
||||
v.cc.Lock()
|
||||
defer v.cc.Unlock()
|
||||
@ -96,6 +95,11 @@ func (v *Value) String() (string, error) {
|
||||
return v.val.String()
|
||||
}
|
||||
|
||||
func (v *Value) SourceUnsafe() string {
|
||||
s, _ := v.SourceString()
|
||||
return s
|
||||
}
|
||||
|
||||
// Proxy function to the underlying cue.Value
|
||||
func (v *Value) Path() cue.Path {
|
||||
return v.val.Path()
|
||||
|
@ -105,7 +105,7 @@ test::exec(){
|
||||
test::one "Exec: env valid" --exit=0 --stdout={} \
|
||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/exec/env/valid
|
||||
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={} \
|
||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/exec/dir/doesnotexist
|
||||
@ -202,13 +202,13 @@ test::input() {
|
||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/input/simple
|
||||
|
||||
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"}' \
|
||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/input/default
|
||||
|
||||
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/rs/zerolog v1.20.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.7.0
|
||||
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
|
Reference in New Issue
Block a user