Merge pull request #1648 from talentedmrjones/dagger-do-help

Implements dagger do
This commit is contained in:
Richard Jones 2022-03-01 14:17:26 -07:00 committed by GitHub
commit e055244557
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 486 additions and 335 deletions

140
cmd/dagger/cmd/do.go Normal file
View File

@ -0,0 +1,140 @@
package cmd
import (
"context"
"fmt"
"os"
"strings"
"text/tabwriter"
"cuelang.org/go/cue"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.dagger.io/dagger/cmd/dagger/cmd/common"
"go.dagger.io/dagger/cmd/dagger/logger"
"go.dagger.io/dagger/plan"
"go.dagger.io/dagger/solver"
"golang.org/x/term"
)
var doCmd = &cobra.Command{
Use: "do [OPTIONS] ACTION [SUBACTION...]",
Short: "Execute a dagger action.",
// Args: cobra.MinimumNArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
// Fix Viper bug for duplicate flags:
// https://github.com/spf13/viper/issues/233
if err := viper.BindPFlags(cmd.Flags()); err != nil {
panic(err)
}
},
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
doHelp(cmd, nil)
return
}
var (
lg = logger.New()
tty *logger.TTYOutput
err error
)
if f := viper.GetString("log-format"); f == "tty" || f == "auto" && term.IsTerminal(int(os.Stdout.Fd())) {
tty, err = logger.NewTTYOutput(os.Stderr)
if err != nil {
lg.Fatal().Err(err).Msg("failed to initialize TTY logger")
}
tty.Start()
defer tty.Stop()
lg = lg.Output(tty)
}
ctx := lg.WithContext(cmd.Context())
cl := common.NewClient(ctx)
p, err := loadPlan(getTargetPath(args).String())
if err != nil {
lg.Fatal().Err(err).Msg("failed to load plan")
}
err = cl.Do(ctx, p.Context(), func(ctx context.Context, s solver.Solver) error {
_, err := p.Up(ctx, s)
if err != nil {
return err
}
return nil
})
// FIXME: rework telemetry
if err != nil {
lg.Fatal().Err(err).Msg("failed to up environment")
}
},
}
func loadPlan(target string) (*plan.Plan, error) {
project := viper.GetString("project")
return plan.Load(context.Background(), plan.Config{
Args: []string{project},
With: viper.GetStringSlice("with"),
Target: target,
Vendor: !viper.GetBool("no-vendor"),
})
}
func getTargetPath(args []string) cue.Path {
actionLookupArgs := []string{plan.ActionsPath}
actionLookupArgs = append(actionLookupArgs, args...)
actionLookupSelectors := []cue.Selector{}
for _, a := range actionLookupArgs {
actionLookupSelectors = append(actionLookupSelectors, cue.Str(a))
}
return cue.MakePath(actionLookupSelectors...)
}
func doHelp(cmd *cobra.Command, _ []string) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.StripEscape)
defer w.Flush()
p, err := loadPlan("")
if err != nil {
fmt.Printf("%s", err)
fmt.Fprintln(w, "failed to load plan")
return
}
project := viper.GetString("project")
actionLookupPath := getTargetPath(cmd.Flags().Args())
actions := p.Action().FindByPath(actionLookupPath).Children
fmt.Printf(`Execute a dagger action.
%s
Plan loaded from %s:
%s
`, cmd.UsageString(), project, "\n"+actionLookupPath.String()+":")
// fmt.Fprintln(w, "Actions\tDescription\tPackage")
// fmt.Fprintln(w, "\t\t")
for _, a := range actions {
if !a.Hidden {
lineParts := []string{"", a.Name, strings.TrimSpace(a.Comment)}
fmt.Fprintln(w, strings.Join(lineParts, "\t"))
}
}
}
func init() {
doCmd.Flags().StringArrayP("with", "w", []string{}, "")
doCmd.Flags().Bool("no-vendor", false, "Force up, disable inputs check")
doCmd.SetHelpFunc(doHelp)
if err := viper.BindPFlags(doCmd.Flags()); err != nil {
panic(err)
}
}

View File

@ -30,7 +30,7 @@ func init() {
rootCmd.PersistentFlags().StringArray("cache-from", []string{}, rootCmd.PersistentFlags().StringArray("cache-from", []string{},
"External cache sources (eg. user/app:cache, type=local,src=path/to/dir)") "External cache sources (eg. user/app:cache, type=local,src=path/to/dir)")
rootCmd.PersistentFlags().String("project", "", "Specify a project directory (defaults to current)") rootCmd.PersistentFlags().StringP("project", "p", "./", "Specify a project directory (defaults to current)")
rootCmd.PersistentPreRun = func(cmd *cobra.Command, _ []string) { rootCmd.PersistentPreRun = func(cmd *cobra.Command, _ []string) {
go checkVersion() go checkVersion()
@ -45,6 +45,7 @@ func init() {
versionCmd, versionCmd,
docCmd, docCmd,
mod.Cmd, mod.Cmd,
doCmd,
) )
if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil {

34
plan/action.go Normal file
View File

@ -0,0 +1,34 @@
package plan
import (
"cuelang.org/go/cue"
)
type Action struct {
Name string
Hidden bool
Path cue.Path
Comment string
Children []*Action
// pkg string
}
func (a *Action) AddChild(c *Action) {
a.Children = append(a.Children, c)
}
func (a *Action) FindByPath(path cue.Path) *Action {
queue := []*Action{a}
for len(queue) > 0 {
nextUp := queue[0]
queue = queue[1:]
if nextUp.Path.String() == path.String() {
return nextUp
}
if len(nextUp.Children) > 0 {
queue = append(queue, nextUp.Children...)
}
}
return nil
}

View File

@ -17,11 +17,16 @@ import (
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
) )
const (
ActionsPath = "actions"
)
type Plan struct { type Plan struct {
config Config config Config
context *plancontext.Context context *plancontext.Context
source *compiler.Value source *compiler.Value
action *Action
} }
type Config struct { type Config struct {
@ -66,6 +71,8 @@ func Load(ctx context.Context, cfg Config) (*Plan, error) {
source: v, source: v,
} }
p.fillAction()
if err := p.configPlatform(); err != nil { if err := p.configPlatform(); err != nil {
return nil, err return nil, err
} }
@ -85,6 +92,10 @@ func (p *Plan) Source() *compiler.Value {
return p.source return p.source
} }
func (p *Plan) Action() *Action {
return p.action
}
// configPlatform load the platform specified in the context // configPlatform load the platform specified in the context
// Buildkit will then run every operation using that platform // Buildkit will then run every operation using that platform
// If platform is not define, context keep default platform // If platform is not define, context keep default platform
@ -186,6 +197,78 @@ func (p *Plan) Up(ctx context.Context, s solver.Solver) (*compiler.Value, error)
} }
} }
func (p *Plan) fillAction() {
cfg := &cueflow.Config{
FindHiddenTasks: true,
Root: cue.ParsePath(ActionsPath),
}
flow := cueflow.New(
cfg,
p.source.Cue(),
noOpRunner,
)
actions := p.source.Lookup(ActionsPath)
actionsComment := ""
for _, cg := range actions.Doc() {
actionsComment += cg.Text()
}
p.action = &Action{
ActionsPath,
false,
actions.Path(),
actionsComment,
[]*Action{},
}
tasks := flow.Tasks()
for _, t := range tasks {
var q []cue.Selector
prevAction := p.action
for _, s := range t.Path().Selectors() {
q = append(q, s)
path := cue.MakePath(q...)
a := prevAction.FindByPath(path)
if a == nil {
v := p.Source().LookupPath(path)
childComment := ""
for _, cg := range v.Doc() {
childComment += cg.Text()
}
a = &Action{
s.String(),
s.PkgPath() != "",
path,
childComment,
[]*Action{},
}
prevAction.AddChild(a)
}
prevAction = a
}
}
}
func noOpRunner(flowVal cue.Value) (cueflow.Runner, error) {
v := compiler.Wrap(flowVal)
_, err := task.Lookup(v)
if err != nil {
// Not a task
if err == task.ErrNotTask {
return nil, nil
}
return nil, err
}
// Wrapper around `task.Run` that handles logging, tracing, etc.
return cueflow.RunnerFunc(func(t *cueflow.Task) error {
return nil
}), nil
}
func newRunner(pctx *plancontext.Context, s solver.Solver, computed *compiler.Value) cueflow.TaskFunc { func newRunner(pctx *plancontext.Context, s solver.Solver, computed *compiler.Value) cueflow.TaskFunc {
return func(flowVal cue.Value) (cueflow.Runner, error) { return func(flowVal cue.Value) (cueflow.Runner, error) {
v := compiler.Wrap(flowVal) v := compiler.Wrap(flowVal)

View File

@ -7,154 +7,77 @@ setup() {
@test "plan/hello" { @test "plan/hello" {
# Europa loader handles the cwd differently, therefore we need to CD into the tree at or below the parent of cue.mod # Europa loader handles the cwd differently, therefore we need to CD into the tree at or below the parent of cue.mod
cd "$TESTDIR" cd "$TESTDIR"
"$DAGGER" up ./plan/hello-europa "$DAGGER" "do" -p ./plan/hello-europa test
} }
@test "plan/proxy invalid schema" { @test "plan/proxy invalid schema" {
cd "$TESTDIR" cd "$TESTDIR"
run "$DAGGER" up ./plan/proxy/invalid_schema.cue run "$DAGGER" "do" -p ./plan/proxy/invalid_schema.cue verify
assert_failure assert_failure
} }
@test "plan/proxy invalid value" { @test "plan/proxy invalid value" {
cd "$TESTDIR" cd "$TESTDIR"
run "$DAGGER" up ./plan/proxy/invalid_value.cue run "$DAGGER" "do" -p ./plan/proxy/invalid_value.cue verify
assert_failure assert_failure
} }
@test "plan/proxy incomplete unix" { @test "plan/proxy incomplete unix" {
cd "$TESTDIR" cd "$TESTDIR"
run "$DAGGER" up ./plan/proxy/incomplete_unix.cue run "$DAGGER" "do" -p ./plan/proxy/incomplete_unix.cue verify
assert_failure assert_failure
} }
@test "plan/proxy incomplete service" { @test "plan/proxy incomplete service" {
cd "$TESTDIR" cd "$TESTDIR"
run "$DAGGER" up ./plan/proxy/incomplete_service.cue run "$DAGGER" "do" -p ./plan/proxy/incomplete_service.cue verify
assert_output --partial 'mount "docker" is not concrete' assert_output --partial 'mount "docker" is not concrete'
} }
@test "plan/proxy unix" { @test "plan/proxy unix" {
cd "$TESTDIR" cd "$TESTDIR"
"$DAGGER" up ./plan/proxy/unix.cue "$DAGGER" "do" -p ./plan/proxy/unix.cue verify
} }
@test "plan/inputs/directories exists" { @test "plan/inputs/directories" {
cd "$TESTDIR" cd "$TESTDIR"
"$DAGGER" up ./plan/inputs/directories/exists.cue "$DAGGER" "do" -p ./plan/inputs/directories/valid exists
}
@test "plan/inputs/directories relative directories" { run "$DAGGER" "do" -p ./plan/inputs/directories/invalid notExists
cd "$TESTDIR"
cd "$TESTDIR"/plan/inputs
"$DAGGER" up ./directories/exists.cue
}
@test "plan/inputs/directories not exists" {
cd "$TESTDIR"
run "$DAGGER" up ./plan/inputs/directories/not_exists.cue
assert_failure assert_failure
assert_output --partial 'fasdfsdfs" does not exist' assert_output --partial 'fasdfsdfs" does not exist'
}
@test "plan/inputs/directories conflicting values" { run "$DAGGER" "do" -p ./plan/inputs/directories/valid conflictingValues
cd "$TESTDIR"
run "$DAGGER" up ./plan/inputs/directories/conflicting_values.cue
assert_failure assert_failure
assert_output --partial 'conflicting values "local directory" and "local dfsadf"' assert_output --partial 'conflicting values "local directory" and "local dfsadf"'
} }
@test "plan/inputs/secrets" { @test "plan/inputs/secrets" {
cd "$TESTDIR" cd "$TESTDIR"
"$DAGGER" up ./plan/inputs/secrets/exec.cue "$DAGGER" "do" -p ./plan/inputs/secrets test valid
"$DAGGER" up ./plan/inputs/secrets/exec_relative.cue "$DAGGER" "do" -p ./plan/inputs/secrets test relative
run "$DAGGER" up ./plan/inputs/secrets/invalid_command.cue run "$DAGGER" "do" -p ./plan/inputs/secrets test badCommand
assert_failure assert_failure
assert_output --partial 'failed: exec: "rtyet": executable file not found' assert_output --partial 'failed: exec: "rtyet": executable file not found'
run "$DAGGER" up ./plan/inputs/secrets/invalid_command_options.cue run "$DAGGER" "do" -p ./plan/inputs/secrets test badArgs
assert_failure assert_failure
assert_output --partial 'option' assert_output --partial 'option'
} }
@test "plan/with" { @test "plan/with" {
cd "$TESTDIR" cd "$TESTDIR"
"$DAGGER" up --with 'inputs: params: foo:"bar"' ./plan/with/params.cue "$DAGGER" "do" --with 'inputs: params: foo:"bar"' -p ./plan/with test params
"$DAGGER" up --with 'actions: verify: env: FOO: "bar"' ./plan/with/actions.cue "$DAGGER" "do" --with 'actions: test: direct: env: FOO: "bar"' -p ./plan/with test direct
run "$DAGGER" up --with 'inputs: params: foo:1' ./plan/with/params.cue run "$DAGGER" "do" --with 'inputs: params: foo:1' -p ./plan/with test params
assert_failure assert_failure
assert_output --partial "conflicting values string and 1" assert_output --partial "conflicting values string and 1"
run "$DAGGER" up ./plan/with/params.cue run "$DAGGER" "do" -p ./plan/with test params
assert_failure assert_failure
assert_output --partial "actions.verify.env.FOO: non-concrete value string" assert_output --partial "actions.test.params.env.FOO: non-concrete value string"
}
@test "plan/outputs/directories" {
cd "$TESTDIR"/plan/outputs/directories
"$DAGGER" up ./outputs.cue
assert [ -f "./out/test_outputs" ]
rm -f "./out/test_outputs"
}
@test "plan/outputs/directories relative paths" {
cd "$TESTDIR"/plan
"$DAGGER" up ./outputs/directories/relative.cue
assert [ -f "./outputs/directories/out/test_relative" ]
rm -f "./outputs/directories/out/test_relative"
}
@test "plan/outputs/files normal usage" {
cd "$TESTDIR"/plan/outputs/files
"$DAGGER" up ./usage.cue
run ./test_usage
assert_output "Hello World!"
run ls -l "./test_usage"
assert_output --partial "-rwxr-x---"
rm -f "./test_usage"
}
@test "plan/outputs/files relative path" {
cd "$TESTDIR"/plan
"$DAGGER" up ./outputs/files/relative.cue
assert [ -f "./outputs/files/test_relative" ]
rm -f "./outputs/files/test_relative"
}
@test "plan/outputs/files default permissions" {
cd "$TESTDIR"/plan/outputs/files
"$DAGGER" up ./default_permissions.cue
run ls -l "./test_default_permissions"
assert_output --partial "-rw-r--r--"
rm -f "./test_default_permissions"
}
@test "plan/outputs/files no contents" {
cd "$TESTDIR"/plan/outputs/files
run "$DAGGER" up ./no_contents.cue
assert_failure
assert_output --partial "contents is not set"
assert [ ! -f "./test_no_contents" ]
rm -f "./test_no_contents"
} }
@test "plan/platform" { @test "plan/platform" {

74
tests/plan/do/actions.cue Normal file
View File

@ -0,0 +1,74 @@
package main
import (
"dagger.io/dagger"
"universe.dagger.io/yarn"
"universe.dagger.io/docker"
)
dagger.#Plan & {
// All the things!
actions: {
// Run core integration tests
"core-integration": _
// Format all cue files
cuefmt: _
// Lint and format all cue files
cuelint: _
// Build a debug version of the dev dagger binary
"dagger-debug": _
// Test docs
"doc-test": _
// Generate docs
docs: _
// Generate & lint docs
docslint: _
// Run Europa universe tests
"europa-universe-test": _
// Go lint
golint: _
// Show how to get started & what targets are available
help: _
// Install a dev dagger binary
install: _
// Run all integration tests
integration: _
// Lint everything
lint: _
// Run shellcheck
shellcheck: _
// Run all tests
test: _
// Find all TODO items
todo: _
// Run universe tests
"universe-test": _
// Build, test and deploy frontend web client
frontend: {
// Build via yarn
build: yarn.#Build
// Test via headless browser
test: docker.#Run
}
}
}

View File

@ -6,18 +6,18 @@ import (
) )
dagger.#Plan & { dagger.#Plan & {
actions: { actions: test: {
image: dagger.#Pull & { _image: dagger.#Pull & {
source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3" source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3"
} }
exec: dagger.#Exec & { _exec: dagger.#Exec & {
input: image.output input: _image.output
args: ["sh", "-c", "echo -n Hello Europa > /out.txt"] args: ["sh", "-c", "echo -n Hello Europa > /out.txt"]
} }
verify: dagger.#ReadFile & { _verify: dagger.#ReadFile & {
input: exec.output input: _exec.output
path: "/out.txt" path: "/out.txt"
} & { } & {
// assert result // assert result

View File

@ -1,15 +0,0 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
inputs: directories: test: path: "."
actions: verify: dagger.#ReadFile & {
input: inputs.directories.test.contents
path: "test.txt"
} & {
contents: "local dfsadf" // should fail with conflicting values
}
}

View File

@ -1,15 +0,0 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
inputs: directories: test: path: "."
actions: verify: dagger.#ReadFile & {
input: inputs.directories.test.contents
path: "test.txt"
} & {
contents: "local directory"
}
}

View File

@ -0,0 +1,3 @@
package main
inputs: directories: test: path: "./fasdfsdfs"

View File

@ -0,0 +1,31 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
inputs: directories: test: path: string
actions: {
_readFile: dagger.#ReadFile & {
input: inputs.directories.test.contents
path: "test.txt"
}
// Test that file exists and contains correct content
exists: _readFile & {
contents: "local directory"
}
// Test that file does NOT exist
notExists: _readFile & {
contents: "local directory"
}
// Test that file exists and contains conflicting content
conflictingValues: _readFile & {
contents: "local dfsadf"
}
}
}

View File

@ -1,16 +0,0 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
// should fail because path does not exist locally
inputs: directories: test: path: "./fasdfsdfs"
actions: verify: dagger.#ReadFile & {
input: inputs.directories.test.contents
path: "test.txt"
} & {
contents: "local directory"
}
}

View File

@ -0,0 +1,3 @@
package main
inputs: directories: test: path: "."

View File

@ -1,34 +0,0 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
inputs: secrets: echo: command: {
name: "echo"
args: ["hello europa"]
}
actions: {
image: dagger.#Pull & {
source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3"
}
verify: dagger.#Exec & {
input: image.output
mounts: secret: {
dest: "/run/secrets/test"
contents: inputs.secrets.echo.contents
}
args: [
"sh", "-c",
#"""
test "$(cat /run/secrets/test)" = "hello europa"
ls -l /run/secrets/test | grep -- "-r--------"
"""#,
]
}
}
}

View File

@ -1,33 +0,0 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
inputs: secrets: echo: command: {
name: "cat"
args: ["./test.txt"]
}
actions: {
image: dagger.#Pull & {
source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3"
}
verify: dagger.#Exec & {
input: image.output
mounts: secret: {
dest: "/run/secrets/test"
contents: inputs.secrets.echo.contents
}
args: [
"sh", "-c",
#"""
test "$(cat /run/secrets/test)" = "test"
"""#,
]
}
}
}

View File

@ -1,34 +0,0 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
inputs: secrets: echo: command: {
name: "rtyet" // should fail because command doesn't exist
args: ["hello europa"]
}
actions: {
image: dagger.#Pull & {
source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3"
}
verify: dagger.#Exec & {
input: image.output
mounts: secret: {
dest: "/run/secrets/test"
contents: inputs.secrets.echo.contents
}
args: [
"sh", "-c",
#"""
test "$(cat /run/secrets/test)" = "hello europa"
ls -l /run/secrets/test | grep -- "-r--------"
"""#,
]
}
}
}

View File

@ -1,33 +0,0 @@
package main
import (
)
dagger.#Plan & {
inputs: secrets: echo: command: {
name: "cat"
args: ["--sfgjkhf"] // // should fail because invalid option
}
actions: {
image: dagger.#Pull & {
source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3"
}
verify: dagger.#Exec & {
input: image.output
mounts: secret: {
dest: "/run/secrets/test"
contents: inputs.secrets.echo.contents
}
args: [
"sh", "-c",
#"""
test "$(cat /run/secrets/test)" = "hello europa"
ls -l /run/secrets/test | grep -- "-r--------"
"""#,
]
}
}
}

View File

@ -0,0 +1,59 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
inputs: secrets: {
echo: command: {
name: "echo"
args: ["hello europa"]
}
relative: command: {
name: "cat"
args: ["./test.txt"]
}
badCommand: command: {
name: "rtyet" // should fail because command doesn't exist
args: ["hello europa"]
}
badArgs: command: {
name: "cat"
args: ["--sfgjkhf"] // // should fail because invalid option
}
}
actions: {
_image: dagger.#Pull & {
source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3"
}
test: {
[string]: dagger.#Exec & {
input: _image.output
mounts: secret: {
dest: "/run/secrets/test"
// contents: inputs.secrets.echo.contents
}
args: [
"sh", "-c",
#"""
test "$(cat /run/secrets/test)" = "hello europa"
ls -l /run/secrets/test | grep -- "-r--------"
"""#,
]
}
valid: mounts: secret: contents: inputs.secrets.echo.contents
relative: mounts: secret: contents: inputs.secrets.relative.contents
badCommand: mounts: secret: contents: inputs.secrets.badCommand.contents
badArgs: mounts: secret: contents: inputs.secrets.badArgs.contents
}
}
}

View File

@ -1 +1 @@
test hello europa

View File

@ -1,25 +0,0 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
actions: {
image: dagger.#Pull & {
source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3"
}
verify: dagger.#Exec & {
input: image.output
env: FOO: string
args: [
"sh", "-c",
#"""
test -n "$FOO"
"""#,
]
}
}
}

View File

@ -1,26 +0,0 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
inputs: params: foo: string
actions: {
image: dagger.#Pull & {
source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3"
}
verify: dagger.#Exec & {
input: image.output
env: FOO: inputs.params.foo
args: [
"sh", "-c",
#"""
test "$FOO" = "bar"
"""#,
]
}
}
}

31
tests/plan/with/with.cue Normal file
View File

@ -0,0 +1,31 @@
package main
import (
"dagger.io/dagger"
)
dagger.#Plan & {
inputs: params: foo: string
actions: {
_image: dagger.#Pull & {
source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3"
}
test: {
[string]: dagger.#Exec & {
input: _image.output
env: FOO: string
args: [
"sh", "-c",
#"""
test -n "$FOO"
"""#,
]
}
direct: {}
params: env: FOO: inputs.params.foo
}
}
}