diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..97cbcae --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. + // Pointez pour afficher la description des attributs existants. + // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug yey run", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/src", + "args": ["run"], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/dev/build b/dev/build index 938e03f..ce29e1c 100755 --- a/dev/build +++ b/dev/build @@ -1,2 +1,2 @@ #!/bin/bash -go build -ldflags "-X main.version=dev-$(date +%F-%T)" -o /usr/local/bin/yeydev ./src \ No newline at end of file +go build -ldflags "-X main.version=dev-$(date +%F-%T)" -o /usr/local/bin/yey ./src \ No newline at end of file diff --git a/src/cmd/run/run.go b/src/cmd/run/run.go index d411e9b..01435d9 100644 --- a/src/cmd/run/run.go +++ b/src/cmd/run/run.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "os" + "path/filepath" + "strings" yey "github.com/silphid/yey/src/internal" "github.com/silphid/yey/src/internal/docker" @@ -84,7 +86,39 @@ func run(ctx context.Context, name string, options Options) error { } } - return docker.Start(ctx, yeyContext, containerName) + workDir, err := getContainerWorkDir(yeyContext) + if err != nil { + return err + } + + var runOptions []docker.RunOption + if workDir != "" { + runOptions = append(runOptions, docker.WithWorkDir(workDir)) + } + + return docker.Start(ctx, yeyContext, containerName, runOptions...) +} + +func getContainerWorkDir(yeyContext yey.Context) (string, error) { + workDir, err := os.Getwd() + if err != nil { + return "", err + } + + for key, value := range yeyContext.Mounts { + // Where is work dir relatively to mount dir? + subDir, err := filepath.Rel(key, workDir) + if err != nil { + return "", err + } + + // Is work dir within mount dir? + if !strings.HasPrefix(subDir, fmt.Sprintf("..%c", filepath.Separator)) { + return filepath.Join(value, subDir), nil + } + } + + return "", nil } func readAndBuildDockerfile(ctx context.Context, build yey.DockerBuild) (string, error) { diff --git a/src/internal/contextFile.go b/src/internal/contextFile.go index a400492..103fe46 100644 --- a/src/internal/contextFile.go +++ b/src/internal/contextFile.go @@ -90,9 +90,10 @@ func parseContextFile(dir string, data []byte) (Contexts, error) { } if dir != "" { - contexts.Context = resolveContextPaths(dir, contexts.Context) - for name, context := range contexts.Named { - contexts.Named[name] = resolveContextPaths(dir, context) + var err error + contexts, err = resolveContextsPaths(dir, contexts) + if err != nil { + return Contexts{}, err } } @@ -145,24 +146,69 @@ func LoadContexts() (Contexts, error) { return contexts, nil } -func resolveContextPaths(dir string, context Context) Context { +func resolveContextsPaths(dir string, contexts Contexts) (Contexts, error) { + var err error + contexts.Context, err = resolveContextPaths(dir, contexts.Context) + if err != nil { + return Contexts{}, err + } + for name, context := range contexts.Named { + contexts.Named[name], err = resolveContextPaths(dir, context) + if err != nil { + return Contexts{}, err + } + } + return contexts, nil +} + +func resolveContextPaths(dir string, context Context) (Context, error) { clone := context.Clone() - clone.Build.Dockerfile = resolvePath(dir, context.Build.Dockerfile) - clone.Build.Context = resolvePath(dir, clone.Build.Context) - for key, value := range clone.Mounts { - clone.Mounts[resolvePath(dir, key)] = value + + // Resolve dockerfile path + var err error + clone.Build.Dockerfile, err = resolvePath(dir, context.Build.Dockerfile) + if err != nil { + return Context{}, err } - return clone + + // Resolve build context dir + clone.Build.Context, err = resolvePath(dir, clone.Build.Context) + if err != nil { + return Context{}, err + } + + // Resolve mount dirs + clone.Mounts = make(map[string]string, len(context.Mounts)) + for key, value := range context.Mounts { + key, err = resolvePath(dir, key) + if err != nil { + return Context{}, err + } + clone.Mounts[key] = value + } + + return clone, nil } -func resolvePath(dir, path string) string { +func resolvePath(dir, path string) (string, error) { if path == "" { - return "" + return "", nil + } + + // Resolve home dir + var err error + if path == "~" { + path, err = homedir.Dir() + } else { + path, err = homedir.Expand(path) + } + if err != nil { + return "", err } if filepath.IsAbs(path) { - return path + return path, nil } - return filepath.Join(dir, path) + return filepath.Join(dir, path), nil } diff --git a/src/internal/docker/cli.go b/src/internal/docker/cli.go index 3dcc8a2..90190e3 100644 --- a/src/internal/docker/cli.go +++ b/src/internal/docker/cli.go @@ -10,12 +10,28 @@ import ( "regexp" "strings" - "github.com/mitchellh/go-homedir" yey "github.com/silphid/yey/src/internal" "github.com/silphid/yey/src/internal/logging" ) -func Start(ctx context.Context, yeyCtx yey.Context, containerName string) error { +type runOptions struct { + workDir string +} + +type RunOption func(*runOptions) + +func WithWorkDir(wd string) RunOption { + return func(ro *runOptions) { + ro.workDir = wd + } +} + +func Start(ctx context.Context, yeyCtx yey.Context, containerName string, opts ...RunOption) error { + var options runOptions + for _, opt := range opts { + opt(&options) + } + // Determine whether we need to run or exec container status, err := getContainerStatus(ctx, containerName) if err != nil { @@ -24,11 +40,11 @@ func Start(ctx context.Context, yeyCtx yey.Context, containerName string) error switch status { case "": - return runContainer(ctx, yeyCtx, containerName) + return runContainer(ctx, yeyCtx, containerName, options) case "exited": return startContainer(ctx, containerName) case "running": - return execContainer(ctx, containerName, yeyCtx.Cmd) + return execContainer(ctx, containerName, yeyCtx.Cmd, options) default: return fmt.Errorf("container %q in unexpected state %q", containerName, status) } @@ -106,7 +122,7 @@ func getContainerStatus(ctx context.Context, name string) (string, error) { return strings.TrimSpace(string(output)), nil } -func runContainer(ctx context.Context, yeyCtx yey.Context, containerName string) error { +func runContainer(ctx context.Context, yeyCtx yey.Context, containerName string, options runOptions) error { cwd, err := os.Getwd() if err != nil { return err @@ -129,16 +145,12 @@ func runContainer(ctx context.Context, yeyCtx yey.Context, containerName string) args = append(args, "--env", fmt.Sprintf("%s=%s", name, value)) } - home, err := homedir.Dir() - if err != nil { - return fmt.Errorf("failed to detect user home directory: %w", err) - } - + // Mount binds for key, value := range yeyCtx.Mounts { args = append( args, "--volume", - fmt.Sprintf("%s:%s", strings.ReplaceAll(key, "$HOME", home), value), + fmt.Sprintf("%s:%s", key, value), ) } @@ -146,6 +158,10 @@ func runContainer(ctx context.Context, yeyCtx yey.Context, containerName string) args = append(args, "--rm") } + if options.workDir != "" { + args = append(args, "--workdir", options.workDir) + } + args = append(args, yeyCtx.Image) args = append(args, yeyCtx.Cmd...) @@ -156,8 +172,13 @@ func startContainer(ctx context.Context, containerName string) error { return attachStdPipes(exec.CommandContext(ctx, "docker", "start", "-i", containerName)).Run() } -func execContainer(ctx context.Context, containerName string, cmd []string) error { - args := append([]string{"exec", "-ti", containerName}, cmd...) +func execContainer(ctx context.Context, containerName string, cmd []string, options runOptions) error { + args := []string{"exec", "-ti"} + if options.workDir != "" { + args = append(args, "--workdir", options.workDir) + } + args = append(args, containerName) + args = append(args, cmd...) return attachStdPipes(exec.CommandContext(ctx, "docker", args...)).Run() }