engine: Make paths relative to the CUE file they're defined in

Fixes #1309

Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
Andrea Luzzardi 2021-12-23 19:09:26 +01:00 committed by Sam Alba
parent ec69734c51
commit 9c81a155c9
12 changed files with 134 additions and 30 deletions

View File

@ -1,6 +1,9 @@
package compiler package compiler
import ( import (
"errors"
"path"
"path/filepath"
"sort" "sort"
"strconv" "strconv"
@ -282,6 +285,43 @@ func (v *Value) HasAttr(filter ...string) bool {
return false return false
} }
// Filename returns the CUE filename where the value was defined
func (v *Value) Filename() (string, error) {
pos := v.Cue().Pos()
if !pos.IsValid() {
return "", errors.New("invalid token position")
}
return pos.Filename(), nil
}
// Dirname returns the CUE dirname where the value was defined
func (v *Value) Dirname() (string, error) {
f, err := v.Filename()
if err != nil {
return "", err
}
return filepath.Dir(f), nil
}
// AbsPath returns an absolute path contained in Value
// Paths are relative to the CUE file they were declared in.
func (v *Value) AbsPath() (string, error) {
p, err := v.String()
if err != nil {
return "", nil
}
if filepath.IsAbs(p) {
return p, nil
}
d, err := v.Dirname()
if err != nil {
return "", err
}
return path.Join(d, p), nil
}
func (v *Value) Dereference() *Value { func (v *Value) Dereference() *Value {
dVal := cue.Dereference(v.val) dVal := cue.Dereference(v.val)
return v.cc.Wrap(dVal) return v.cc.Wrap(dVal)

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path/filepath"
"strings" "strings"
"time" "time"
@ -97,16 +96,12 @@ func (p *Plan) registerLocalDirs() error {
} }
for _, v := range imports { for _, v := range imports {
dir, err := v.Value.Lookup("path").String() dir, err := v.Value.Lookup("path").AbsPath()
if err != nil { if err != nil {
return err return err
} }
abs, err := filepath.Abs(dir) if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) {
if err != nil { return fmt.Errorf("path %q does not exist", dir)
return err
}
if _, err := os.Stat(abs); errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("path %q does not exist", abs)
} }
p.context.LocalDirs.Add(dir) p.context.LocalDirs.Add(dir)
} }

View File

@ -18,8 +18,12 @@ type inputDirectoryTask struct {
} }
func (c *inputDirectoryTask) Run(ctx context.Context, pctx *plancontext.Context, s solver.Solver, v *compiler.Value) (*compiler.Value, error) { func (c *inputDirectoryTask) Run(ctx context.Context, pctx *plancontext.Context, s solver.Solver, v *compiler.Value) (*compiler.Value, error) {
path, err := v.Lookup("path").AbsPath()
if err != nil {
return nil, err
}
var dir struct { var dir struct {
Path string
Include []string Include []string
Exclude []string Exclude []string
} }
@ -29,13 +33,13 @@ func (c *inputDirectoryTask) Run(ctx context.Context, pctx *plancontext.Context,
} }
lg := log.Ctx(ctx) lg := log.Ctx(ctx)
lg.Debug().Str("path", dir.Path).Msg("loading local directory") lg.Debug().Str("path", path).Msg("loading local directory")
opts := []llb.LocalOption{ opts := []llb.LocalOption{
withCustomName(v, "Local %s", dir.Path), withCustomName(v, "Local %s", path),
// Without hint, multiple `llb.Local` operations on the // Without hint, multiple `llb.Local` operations on the
// same path get a different digest. // same path get a different digest.
llb.SessionID(s.SessionID()), llb.SessionID(s.SessionID()),
llb.SharedKeyHint(dir.Path), llb.SharedKeyHint(path),
} }
if len(dir.Include) > 0 { if len(dir.Include) > 0 {
@ -56,13 +60,13 @@ func (c *inputDirectoryTask) Run(ctx context.Context, pctx *plancontext.Context,
st := llb.Scratch().File( st := llb.Scratch().File(
llb.Copy( llb.Copy(
llb.Local( llb.Local(
dir.Path, path,
opts..., opts...,
), ),
"/", "/",
"/", "/",
), ),
withCustomName(v, "Local %s [copy]", dir.Path), withCustomName(v, "Local %s [copy]", path),
) )
result, err := s.Solve(ctx, st, pctx.Platform.Get()) result, err := s.Solve(ctx, st, pctx.Platform.Get())

View File

@ -3,6 +3,7 @@ package task
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"strings" "strings"
@ -35,8 +36,18 @@ func (c *inputSecretExecTask) Run(ctx context.Context, pctx *plancontext.Context
lg := log.Ctx(ctx) lg := log.Ctx(ctx)
lg.Debug().Str("name", secretExec.Command.Name).Str("args", strings.Join(secretExec.Command.Args, " ")).Str("trimSpace", fmt.Sprintf("%t", secretExec.TrimSpace)).Msg("loading secret") lg.Debug().Str("name", secretExec.Command.Name).Str("args", strings.Join(secretExec.Command.Args, " ")).Str("trimSpace", fmt.Sprintf("%t", secretExec.TrimSpace)).Msg("loading secret")
var err error
//#nosec G204: sec audited by @aluzzardi and @mrjones
cmd := exec.CommandContext(ctx, secretExec.Command.Name, secretExec.Command.Args...)
cmd.Env = os.Environ()
cmd.Dir, err = v.Lookup("command.name").Dirname()
if err != nil {
return nil, err
}
// sec audited by @aluzzardi and @mrjones // sec audited by @aluzzardi and @mrjones
out, err := exec.CommandContext(ctx, secretExec.Command.Name, secretExec.Command.Args...).Output() //#nosec G204 out, err := cmd.Output()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,7 +2,6 @@ package task
import ( import (
"context" "context"
"fmt"
"os" "os"
"strings" "strings"
@ -20,25 +19,26 @@ type inputSecretFileTask struct {
} }
func (c *inputSecretFileTask) Run(ctx context.Context, pctx *plancontext.Context, _ solver.Solver, v *compiler.Value) (*compiler.Value, error) { func (c *inputSecretFileTask) Run(ctx context.Context, pctx *plancontext.Context, _ solver.Solver, v *compiler.Value) (*compiler.Value, error) {
var secretFile struct { lg := log.Ctx(ctx)
Path string
TrimSpace bool
}
if err := v.Decode(&secretFile); err != nil { path, err := v.Lookup("path").AbsPath()
if err != nil {
return nil, err return nil, err
} }
lg := log.Ctx(ctx) lg.Debug().Str("path", path).Msg("loading secret")
lg.Debug().Str("path", secretFile.Path).Str("trimSpace", fmt.Sprintf("%t", secretFile.TrimSpace)).Msg("loading secret")
fileBytes, err := os.ReadFile(secretFile.Path) fileBytes, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
plaintext := string(fileBytes) plaintext := string(fileBytes)
if secretFile.TrimSpace { trimSpace, err := v.Lookup("trimSpace").Bool()
if err != nil {
return nil, err
}
if trimSpace {
plaintext = strings.TrimSpace(plaintext) plaintext = strings.TrimSpace(plaintext)
} }

View File

@ -22,7 +22,7 @@ func (c outputDirectoryTask) Run(ctx context.Context, pctx *plancontext.Context,
return nil, err return nil, err
} }
dest, err := v.Lookup("dest").String() dest, err := v.Lookup("dest").AbsPath()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -44,11 +44,18 @@ setup() {
"$DAGGER" --europa up ./plan/inputs/directories/exists.cue "$DAGGER" --europa up ./plan/inputs/directories/exists.cue
} }
@test "plan/inputs/directories relative directories" {
cd "$TESTDIR"
cd "$TESTDIR"/plan/inputs
"$DAGGER" --europa up ./directories/exists.cue
}
@test "plan/inputs/directories not exists" { @test "plan/inputs/directories not exists" {
cd "$TESTDIR" cd "$TESTDIR"
run "$DAGGER" --europa up ./plan/inputs/directories/not_exists.cue run "$DAGGER" --europa up ./plan/inputs/directories/not_exists.cue
assert_failure assert_failure
assert_output --partial 'tests/fasdfsdfs" does not exist' assert_output --partial 'fasdfsdfs" does not exist'
} }
@test "plan/inputs/directories conflicting values" { @test "plan/inputs/directories conflicting values" {
@ -63,6 +70,11 @@ setup() {
"$DAGGER" --europa up ./plan/inputs/secrets/exec.cue "$DAGGER" --europa up ./plan/inputs/secrets/exec.cue
} }
@test "plan/inputs/secrets exec relative" {
cd "$TESTDIR"
"$DAGGER" --europa up ./plan/inputs/secrets/exec.cue
}
@test "plan/inputs/secrets invalid command" { @test "plan/inputs/secrets invalid command" {
cd "$TESTDIR" cd "$TESTDIR"
run "$DAGGER" --europa up ./plan/inputs/secrets/invalid_command.cue run "$DAGGER" --europa up ./plan/inputs/secrets/invalid_command.cue
@ -78,6 +90,14 @@ setup() {
assert [ -f "./out/test" ] assert [ -f "./out/test" ]
} }
@test "plan/outputs relative paths" {
cd "$TESTDIR"/plan
rm -f "./outputs/out/test"
"$DAGGER" --europa up ./outputs/outputs.cue
assert [ -f "./outputs/out/test" ]
}
@test "plan/platform" { @test "plan/platform" {
cd "$TESTDIR" cd "$TESTDIR"

View File

@ -5,7 +5,7 @@ import (
) )
engine.#Plan & { engine.#Plan & {
inputs: directories: test: path: "./plan/inputs/directories" inputs: directories: test: path: "."
actions: verify: engine.#ReadFile & { actions: verify: engine.#ReadFile & {
input: inputs.directories.test.contents input: inputs.directories.test.contents
path: "test.txt" path: "test.txt"

View File

@ -5,7 +5,7 @@ import (
) )
engine.#Plan & { engine.#Plan & {
inputs: directories: test: path: "./plan/inputs/directories" inputs: directories: test: path: "."
actions: verify: engine.#ReadFile & { actions: verify: engine.#ReadFile & {
input: inputs.directories.test.contents input: inputs.directories.test.contents
path: "test.txt" path: "test.txt"

View File

@ -0,0 +1,33 @@
package main
import (
"alpha.dagger.io/europa/dagger/engine"
)
engine.#Plan & {
inputs: secrets: echo: command: {
name: "cat"
args: ["./test.txt"]
}
actions: {
image: engine.#Pull & {
source: "alpine:3.15.0@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3"
}
verify: engine.#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

@ -0,0 +1 @@
test

View File

@ -5,7 +5,7 @@ import "alpha.dagger.io/europa/dagger/engine"
engine.#Plan & { engine.#Plan & {
inputs: secrets: token: command: { inputs: secrets: token: command: {
name: "sops" name: "sops"
args: ["exec-env", "./secrets_sops.yaml", "echo $TestPAT"] args: ["exec-env", "../../secrets_sops.yaml", "echo $TestPAT"]
} }
actions: { actions: {