diff --git a/go.mod b/go.mod index d5f147e..892456f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/dwarvesf/devpod-provider-paperspace go 1.21 require ( github.com/google/go-querystring v1.1.0 - github.com/loft-sh/devpod v0.5.19 + github.com/loft-sh/devpod v0.5.20 github.com/loft-sh/log v0.0.0-20240219160058-26d83ffb46ac github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.1 diff --git a/go.sum b/go.sum index b5c7c75..b981ce1 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/loft-sh/devpod v0.5.19 h1:FVOMYgL252ufYA2YHvql2MKh9mQfkcOAmcSGWRTY1Jc= -github.com/loft-sh/devpod v0.5.19/go.mod h1:XStB9NDoQban1420cmkOHviZJ9NXMESJvJWdk4SSJAw= +github.com/loft-sh/devpod v0.5.20 h1:pSw3h6ohKzCL6kDgLTK+dknXupT5BKuzpyXHKq1lBc0= +github.com/loft-sh/devpod v0.5.20/go.mod h1:9q6ezEneJ3kwq5bvNQlxp/I1b723jTnuRQlbbOOvrTg= github.com/loft-sh/log v0.0.0-20240219160058-26d83ffb46ac h1:Gz/7Lb7WgdgIv+KJz87ORA1zvQW52tUqKPGyunlp4dQ= github.com/loft-sh/log v0.0.0-20240219160058-26d83ffb46ac/go.mod h1:YImeRjXH34Yf5E79T7UHBQpDZl9fIaaFRgyZ/bkY+UQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= diff --git a/vendor/github.com/loft-sh/devpod/pkg/config/config.go b/vendor/github.com/loft-sh/devpod/pkg/config/config.go index 0078a96..afcdf03 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/config/config.go +++ b/vendor/github.com/loft-sh/devpod/pkg/config/config.go @@ -5,6 +5,8 @@ import ( "fmt" "os" "path/filepath" + "strconv" + "time" "github.com/ghodss/yaml" "github.com/loft-sh/devpod/pkg/telemetry" @@ -116,8 +118,12 @@ func (c *Config) IDEOptions(ide string) map[string]OptionValue { } func (c *Config) ContextOption(option string) string { - if c.Current().Options != nil && c.Current().Options[option].Value != "" { - return c.Current().Options[option].Value + if c.Contexts != nil { + if _, ok := c.Contexts[c.DefaultContext]; ok && c.Current().Options != nil { + if _, ok := c.Current().Options[option]; ok && c.Current().Options[option].Value != "" { + return c.Current().Options[option].Value + } + } } for _, contextOption := range ContextOptions { @@ -306,3 +312,11 @@ func SaveConfig(config *Config) error { return nil } + +func ParseTimeOption(cfg *Config, opt string) time.Duration { + timeout, err := strconv.ParseInt(cfg.ContextOption(opt), 10, 64) + if err != nil { + timeout = 20 + } + return time.Duration(timeout) * time.Second +} diff --git a/vendor/github.com/loft-sh/devpod/pkg/config/context.go b/vendor/github.com/loft-sh/devpod/pkg/config/context.go index 8a074f6..b4d6761 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/config/context.go +++ b/vendor/github.com/loft-sh/devpod/pkg/config/context.go @@ -13,6 +13,8 @@ const ( ContextOptionDotfilesScript = "DOTFILES_SCRIPT" ContextOptionSSHAgentForwarding = "SSH_AGENT_FORWARDING" ContextOptionSSHConfigPath = "SSH_CONFIG_PATH" + ContextOptionAgentInjectTimeout = "AGENT_INJECT_TIMEOUT" + ContextOptionRegistryCache = "REGISTRY_CACHE" ) var ContextOptions = []ContextOption{ @@ -80,4 +82,14 @@ var ContextOptions = []ContextOption{ Name: ContextOptionSSHConfigPath, Description: "Specifies the path where the ssh config should be written to", }, + { + Name: ContextOptionAgentInjectTimeout, + Description: "Specifies the timeout to inject the agent", + Default: "20", + }, + { + Name: ContextOptionRegistryCache, + Description: "Specifies the registry to use as a build cache, e.g. gcr.io/my-project/my-dev-env", + Default: "", + }, } diff --git a/vendor/github.com/loft-sh/devpod/pkg/config/ide.go b/vendor/github.com/loft-sh/devpod/pkg/config/ide.go index 96c2bb5..12c9569 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/config/ide.go +++ b/vendor/github.com/loft-sh/devpod/pkg/config/ide.go @@ -18,4 +18,5 @@ const ( IDEWebStorm IDE = "webstorm" IDEFleet IDE = "fleet" IDEJupyterNotebook IDE = "jupyternotebook" + IDECursor IDE = "cursor" ) diff --git a/vendor/github.com/loft-sh/devpod/pkg/devcontainer/config/build.go b/vendor/github.com/loft-sh/devpod/pkg/devcontainer/config/build.go index 69e59fd..89a7521 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/devcontainer/config/build.go +++ b/vendor/github.com/loft-sh/devpod/pkg/devcontainer/config/build.go @@ -21,6 +21,7 @@ type BuildInfo struct { ImageMetadata *ImageMetadataConfig ImageName string PrebuildHash string + RegistryCache string Dockerless *BuildInfoDockerless } diff --git a/vendor/github.com/loft-sh/devpod/pkg/devcontainer/config/prebuild.go b/vendor/github.com/loft-sh/devpod/pkg/devcontainer/config/prebuild.go index ce351a4..bc5f536 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/devcontainer/config/prebuild.go +++ b/vendor/github.com/loft-sh/devpod/pkg/devcontainer/config/prebuild.go @@ -15,7 +15,11 @@ import ( "github.com/loft-sh/log/hash" ) -func CalculatePrebuildHash(originalConfig *DevContainerConfig, platform, architecture, contextPath, dockerfilePath, dockerfileContent string, log log.Logger) (string, error) { +func CalculatePrebuildHash( + originalConfig *DevContainerConfig, + platform, architecture, contextPath, dockerfilePath, dockerfileContent string, + buildInfo *ImageBuildInfo, + log log.Logger) (string, error) { parsedConfig := CloneDevContainerConfig(originalConfig) if platform != "" { @@ -57,8 +61,17 @@ func CalculatePrebuildHash(originalConfig *DevContainerConfig, platform, archite } excludes = append(excludes, DevPodContextFeatureFolder+"/") + // find exact files to hash + // todo pass down target or search all + // todo update DirectoryHash function + var includes []string + if buildInfo.Dockerfile != nil { + includes = buildInfo.Dockerfile.BuildContextFiles() + } + log.Debug("Build context files to use for hash are ", includes) + // get hash of the context directory - contextHash, err := util.DirectoryHash(contextPath, excludes) + contextHash, err := util.DirectoryHash(contextPath, excludes, includes) if err != nil { return "", err } diff --git a/vendor/github.com/loft-sh/devpod/pkg/dockerfile/parse.go b/vendor/github.com/loft-sh/devpod/pkg/dockerfile/parse.go index 76fff3a..740faa4 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/dockerfile/parse.go +++ b/vendor/github.com/loft-sh/devpod/pkg/dockerfile/parse.go @@ -73,6 +73,24 @@ func (d *Dockerfile) FindBaseImage(buildArgs map[string]string, target string) s return "" } +// BuildContextFiles traverses a build stage and returns a list of any file path that would affect the build context +func (d *Dockerfile) BuildContextFiles() (files []string) { + // Iterate over all build stages + for _, stage := range d.Stages { + // Add the values of any ADD or COPY instructions + for _, in := range stage.Instructions { + if strings.HasPrefix(in.Value, "ADD") || strings.HasPrefix(in.Value, "COPY") { + // Take all parts except the first (ADD/COPY) and the last (destination on remote), e.g. "COPY src files /app", we want src and files + parts := strings.Split(in.Original, " ") + if len(parts) > 2 { + files = append(files, parts[1:len(parts)-1]...) + } + } + } + } + return files +} + func (d *Dockerfile) replaceVariables(val string, buildArgs map[string]string, baseImageEnv map[string]string, stage *BaseStage, untilLine int) string { newVal := argumentExpression.ReplaceAllFunc([]byte(val), func(match []byte) []byte { subMatches := argumentExpression.FindStringSubmatch(string(match)) diff --git a/vendor/github.com/loft-sh/devpod/pkg/dockerfile/test_Dockerfile b/vendor/github.com/loft-sh/devpod/pkg/dockerfile/test_Dockerfile new file mode 100644 index 0000000..96f591f --- /dev/null +++ b/vendor/github.com/loft-sh/devpod/pkg/dockerfile/test_Dockerfile @@ -0,0 +1,32 @@ +FROM mcr.microsoft.com/devcontainers/go:1.22-bullseye + +ARG TARGETOS +ARG TARGETARCH + +# Install Node.js +RUN \ + --mount=type=cache,target=/var/cache/apt \ + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get update \ + && apt-get install -y --no-install-recommends nodejs \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Set environment variables for Rust +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH \ + RUST_VERSION=1.69.0 + +# Install Protobuf compiler +RUN \ + --mount=type=cache,target=/var/cache/apt \ + apt-get update \ + && apt-get install -y --no-install-recommends protobuf-compiler \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +COPY app /app +COPY files /files + +RUN echo hello \ No newline at end of file diff --git a/vendor/github.com/loft-sh/devpod/pkg/git/clone.go b/vendor/github.com/loft-sh/devpod/pkg/git/clone.go index d60ce25..49628fa 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/git/clone.go +++ b/vendor/github.com/loft-sh/devpod/pkg/git/clone.go @@ -1,10 +1,13 @@ package git import ( + "bytes" "context" "fmt" "io" + "github.com/loft-sh/log" + "github.com/sirupsen/logrus" "github.com/spf13/pflag" ) @@ -15,10 +18,11 @@ const ( BloblessCloneStrategy CloneStrategy = "blobless" TreelessCloneStrategy CloneStrategy = "treeless" ShallowCloneStrategy CloneStrategy = "shallow" + BareCloneStrategy CloneStrategy = "bare" ) type Cloner interface { - Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, stdout, stderr io.Writer) error + Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, log log.Logger) error } func NewCloner(strategy CloneStrategy) Cloner { @@ -29,6 +33,8 @@ func NewCloner(strategy CloneStrategy) Cloner { return &treelessClone{} case ShallowCloneStrategy: return &shallowClone{} + case BareCloneStrategy: + return &bareClone{} case FullCloneStrategy: return &fullClone{} default: @@ -43,7 +49,8 @@ func (s *CloneStrategy) Set(v string) error { case string(FullCloneStrategy), string(BloblessCloneStrategy), string(TreelessCloneStrategy), - string(ShallowCloneStrategy): + string(ShallowCloneStrategy), + string(BareCloneStrategy): { *s = CloneStrategy(v) return nil @@ -65,51 +72,81 @@ type fullClone struct{} var _ Cloner = &fullClone{} -func (c *fullClone) Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, stdout, stderr io.Writer) error { +func (c *fullClone) Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, log log.Logger) error { args := []string{"clone"} args = append(args, extraArgs...) args = append(args, repository, targetDir) - return run(ctx, args, extraEnv, stdout, stderr) + return run(ctx, args, extraEnv, log) } type bloblessClone struct{} var _ Cloner = &bloblessClone{} -func (c *bloblessClone) Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, stdout, stderr io.Writer) error { +func (c *bloblessClone) Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, log log.Logger) error { args := []string{"clone", "--filter=blob:none"} args = append(args, extraArgs...) args = append(args, repository, targetDir) - return run(ctx, args, extraEnv, stdout, stderr) + return run(ctx, args, extraEnv, log) } type treelessClone struct{} var _ Cloner = treelessClone{} -func (c treelessClone) Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, stdout, stderr io.Writer) error { +func (c treelessClone) Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, log log.Logger) error { args := []string{"clone", "--filter=tree:0"} args = append(args, extraArgs...) args = append(args, repository, targetDir) - return run(ctx, args, extraEnv, stdout, stderr) + return run(ctx, args, extraEnv, log) } type shallowClone struct{} var _ Cloner = shallowClone{} -func (c shallowClone) Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, stdout, stderr io.Writer) error { +func (c shallowClone) Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, log log.Logger) error { args := []string{"clone", "--depth=1"} args = append(args, extraArgs...) args = append(args, repository, targetDir) - return run(ctx, args, extraEnv, stdout, stderr) + return run(ctx, args, extraEnv, log) } -func run(ctx context.Context, args []string, extraEnv []string, stdout, stderr io.Writer) error { +type bareClone struct{} + +var _ Cloner = bareClone{} + +func (c bareClone) Clone(ctx context.Context, repository string, targetDir string, extraArgs, extraEnv []string, log log.Logger) error { + args := []string{"clone", "bare", "--depth=1"} + args = append(args, extraArgs...) + args = append(args, repository, targetDir) + return run(ctx, args, extraEnv, log) +} + +func run(ctx context.Context, args []string, extraEnv []string, log log.Logger) error { + var buf bytes.Buffer + + args = append(args, "--progress") + gitCommand := CommandContext(ctx, args...) - gitCommand.Stdout = stdout - gitCommand.Stderr = stderr + gitCommand.Stdout = &buf + gitCommand.Stderr = &buf gitCommand.Env = append(gitCommand.Env, extraEnv...) - return gitCommand.Run() + // git always prints prograss output to stderr, + // we need to check the exit code to decide where the logs should go + if err := gitCommand.Run(); err != nil { + // report as error + if _, err2 := io.Copy(log.Writer(logrus.ErrorLevel, false), &buf); err2 != nil { + return err2 + } + return err + } + + // report as debug + if _, err := io.Copy(log.Writer(logrus.DebugLevel, false), &buf); err != nil { + return err + } + + return nil } diff --git a/vendor/github.com/loft-sh/devpod/pkg/git/git.go b/vendor/github.com/loft-sh/devpod/pkg/git/git.go index 3c2739c..6a890b0 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/git/git.go +++ b/vendor/github.com/loft-sh/devpod/pkg/git/git.go @@ -2,7 +2,7 @@ package git import ( "context" - "io" + "fmt" "os" "os/exec" "regexp" @@ -11,7 +11,7 @@ import ( "github.com/loft-sh/devpod/pkg/command" "github.com/loft-sh/log" - "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) const ( @@ -22,22 +22,20 @@ const ( // WARN: Make sure this matches the regex in /desktop/src/views/Workspaces/CreateWorkspace/CreateWorkspaceInput.tsx! var ( - branchRegEx = regexp.MustCompile(`^([^@]*(?:git@)?@?[^@/]+/[^@/]+/?[^@/]+)@([a-zA-Z0-9\./\-\_]+)$`) - commitRegEx = regexp.MustCompile(`^([^@]*(?:git@)?@?[^@/]+/[^@]+)` + regexp.QuoteMeta(CommitDelimiter) + `([a-zA-Z0-9]+)$`) - prReferenceRegEx = regexp.MustCompile(`^([^@]*(?:git@)?@?[^@/]+/[^@]+)@(` + PullRequestReference + `)$`) - subPathRegEx = regexp.MustCompile(`^([^@]*(?:git@)?@?[^@/]+/[^@]+)` + regexp.QuoteMeta(SubPathDelimiter) + `([a-zA-Z0-9\./\-\_]+)$`) + // Updated regex pattern to support SSH-style Git URLs + repoBaseRegEx = `((?:(?:https?|git|ssh|file):\/\/)?\/?(?:[^@\/\n]+@)?(?:[^:\/\n]+)(?:[:\/][^\/\n]+)+(?:\.git)?)` + branchRegEx = regexp.MustCompile(`^` + repoBaseRegEx + `@([a-zA-Z0-9\./\-\_]+)$`) + commitRegEx = regexp.MustCompile(`^` + repoBaseRegEx + regexp.QuoteMeta(CommitDelimiter) + `([a-zA-Z0-9]+)$`) + prReferenceRegEx = regexp.MustCompile(`^` + repoBaseRegEx + `@(` + PullRequestReference + `)$`) + subPathRegEx = regexp.MustCompile(`^` + repoBaseRegEx + regexp.QuoteMeta(SubPathDelimiter) + `([a-zA-Z0-9\./\-\_]+)$`) ) -func CommandContext(ctx context.Context, args ...string) *exec.Cmd { - cmd := exec.CommandContext(ctx, "git", args...) - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0") - cmd.Env = append(cmd.Env, "GIT_SSH_COMMAND=ssh -oBatchMode=yes -oStrictHostKeyChecking=no") - return cmd -} - func NormalizeRepository(str string) (string, string, string, string, string) { - if !strings.HasPrefix(str, "ssh://") && !strings.HasPrefix(str, "git@") && !strings.HasPrefix(str, "http://") && !strings.HasPrefix(str, "https://") { + if !strings.HasPrefix(str, "ssh://") && + !strings.HasPrefix(str, "git@") && + !strings.HasPrefix(str, "http://") && + !strings.HasPrefix(str, "https://") && + !strings.HasPrefix(str, "file://") { str = "https://" + str } @@ -54,7 +52,7 @@ func NormalizeRepository(str string) (string, string, string, string, string) { subpath := "" if match := subPathRegEx.FindStringSubmatch(str); match != nil { str = match[1] - subpath = match[2] + subpath = strings.TrimSuffix(match[2], "/") } // resolve branch @@ -74,6 +72,14 @@ func NormalizeRepository(str string) (string, string, string, string, string) { return str, prReference, branch, commit, subpath } +func CommandContext(ctx context.Context, args ...string) *exec.Cmd { + cmd := exec.CommandContext(ctx, "git", args...) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0") + cmd.Env = append(cmd.Env, "GIT_SSH_COMMAND=ssh -oBatchMode=yes -oStrictHostKeyChecking=no") + return cmd +} + func PingRepository(str string) bool { if !command.Exists("git") { return false @@ -114,63 +120,80 @@ func NormalizeRepositoryGitInfo(str string) *GitInfo { return NewGitInfo(repository, branch, commit, pr, subpath) } -func CloneRepository(ctx context.Context, gitInfo *GitInfo, targetDir string, helper string, bare bool, cloner Cloner, writer io.Writer, log log.Logger) error { - return CloneRepositoryWithEnv(ctx, gitInfo, []string{}, targetDir, helper, bare, cloner, writer, log) +func CloneRepository(ctx context.Context, gitInfo *GitInfo, targetDir string, helper string, cloner Cloner, log log.Logger) error { + return CloneRepositoryWithEnv(ctx, gitInfo, []string{}, targetDir, helper, cloner, log) } -func CloneRepositoryWithEnv(ctx context.Context, gitInfo *GitInfo, extraEnv []string, targetDir string, helper string, bare bool, cloner Cloner, writer io.Writer, log log.Logger) error { +func CloneRepositoryWithEnv(ctx context.Context, gitInfo *GitInfo, extraEnv []string, targetDir string, helper string, cloner Cloner, log log.Logger) error { if cloner == nil { cloner = NewCloner(FullCloneStrategy) } extraArgs := []string{} - if bare && gitInfo.Commit == "" { - extraArgs = append(extraArgs, "--bare", "--depth=1") - } if helper != "" { extraArgs = append(extraArgs, "--config", "credential.helper="+helper) } + if gitInfo.Branch != "" { extraArgs = append(extraArgs, "--branch", gitInfo.Branch) } - err := cloner.Clone(ctx, gitInfo.Repository, targetDir, extraArgs, extraEnv, writer, writer) - if err != nil { - return errors.Wrap(err, "error cloning repository") + if err := cloner.Clone(ctx, gitInfo.Repository, targetDir, extraArgs, extraEnv, log); err != nil { + return err } if gitInfo.PR != "" { - log.Debugf("Fetching pull request : %s", gitInfo.PR) - - prBranch := GetBranchNameForPR(gitInfo.PR) - - // git fetch origin pull/996/head:PR996 - fetchArgs := []string{"fetch", "origin", gitInfo.PR + ":" + prBranch} - fetchCmd := CommandContext(ctx, fetchArgs...) - fetchCmd.Dir = targetDir - err = fetchCmd.Run() - if err != nil { - return errors.Wrap(err, "error fetching pull request reference") - } - - // git switch PR996 - switchArgs := []string{"switch", prBranch} - switchCmd := CommandContext(ctx, switchArgs...) - switchCmd.Dir = targetDir - err = switchCmd.Run() - if err != nil { - return errors.Wrap(err, "error switching to the branch") - } - } else if gitInfo.Commit != "" { - args := []string{"reset", "--hard", gitInfo.Commit} - gitCommand := CommandContext(ctx, args...) - gitCommand.Dir = targetDir - gitCommand.Stdout = writer - gitCommand.Stderr = writer - err := gitCommand.Run() - if err != nil { - return errors.Wrap(err, "error resetting head to commit") - } + return checkoutPR(ctx, gitInfo, targetDir, log) } + + if gitInfo.Commit != "" { + return checkoutCommit(ctx, gitInfo, targetDir, log) + } + + return nil +} + +func checkoutPR(ctx context.Context, gitInfo *GitInfo, targetDir string, log log.Logger) error { + log.Debugf("Fetching pull request : %s", gitInfo.PR) + + prBranch := GetBranchNameForPR(gitInfo.PR) + + // Try to fetch the pull request by + // checking out the reference GitHub set up for it. Afterwards, switch to it. + // See [this doc](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally#modifying-an-inactive-pull-request-locally) + // Command args: `git fetch origin pull/996/head:PR996` + fetchArgs := []string{"fetch", "origin", gitInfo.PR + ":" + prBranch} + fetchCmd := CommandContext(ctx, fetchArgs...) + fetchCmd.Dir = targetDir + if err := fetchCmd.Run(); err != nil { + return fmt.Errorf("fetch pull request reference: %w", err) + } + + // git switch PR996 + switchArgs := []string{"switch", prBranch} + switchCmd := CommandContext(ctx, switchArgs...) + switchCmd.Dir = targetDir + if err := switchCmd.Run(); err != nil { + return fmt.Errorf("switch to branch: %w", err) + } + + return nil +} + +func checkoutCommit(ctx context.Context, gitInfo *GitInfo, targetDir string, log log.Logger) error { + stdout := log.Writer(logrus.InfoLevel, false) + stderr := log.Writer(logrus.ErrorLevel, false) + defer stdout.Close() + defer stderr.Close() + + args := []string{"reset", "--hard", gitInfo.Commit} + gitCommand := CommandContext(ctx, args...) + gitCommand.Dir = targetDir + gitCommand.Stdout = stdout + gitCommand.Stderr = stderr + if err := gitCommand.Run(); err != nil { + return fmt.Errorf("reset head to commit: %w", err) + } + return nil } diff --git a/vendor/github.com/loft-sh/devpod/pkg/git/install.go b/vendor/github.com/loft-sh/devpod/pkg/git/install.go new file mode 100644 index 0000000..dfdc8ba --- /dev/null +++ b/vendor/github.com/loft-sh/devpod/pkg/git/install.go @@ -0,0 +1,67 @@ +package git + +import ( + "fmt" + "os/exec" + + "github.com/loft-sh/devpod/pkg/command" + "github.com/loft-sh/log" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func InstallBinary(log log.Logger) error { + writer := log.Writer(logrus.InfoLevel, false) + errwriter := log.Writer(logrus.ErrorLevel, false) + defer writer.Close() + defer errwriter.Close() + + // try to install git via apt / apk + if !command.Exists("apt") && !command.Exists("apk") { + // TODO: use golang git implementation + return fmt.Errorf("couldn't find a package manager to install git") + } + + if command.Exists("apt") { + log.Infof("Git command is missing, try to install git with apt...") + cmd := exec.Command("apt", "update") + cmd.Stdout = writer + cmd.Stderr = errwriter + err := cmd.Run() + if err != nil { + return errors.Wrap(err, "run apt update") + } + cmd = exec.Command("apt", "-y", "install", "git") + cmd.Stdout = writer + cmd.Stderr = errwriter + err = cmd.Run() + if err != nil { + return errors.Wrap(err, "run apt install git -y") + } + } else if command.Exists("apk") { + log.Infof("Git command is missing, try to install git with apk...") + cmd := exec.Command("apk", "update") + cmd.Stdout = writer + cmd.Stderr = errwriter + err := cmd.Run() + if err != nil { + return errors.Wrap(err, "run apk update") + } + cmd = exec.Command("apk", "add", "git") + cmd.Stdout = writer + cmd.Stderr = errwriter + err = cmd.Run() + if err != nil { + return errors.Wrap(err, "run apk add git") + } + } + + // is git available now? + if !command.Exists("git") { + return fmt.Errorf("couldn't install git") + } + + log.Donef("Successfully installed git") + + return nil +} diff --git a/vendor/github.com/loft-sh/devpod/pkg/provider/provider.go b/vendor/github.com/loft-sh/devpod/pkg/provider/provider.go index bc2b9ff..dd669f8 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/provider/provider.go +++ b/vendor/github.com/loft-sh/devpod/pkg/provider/provider.go @@ -133,6 +133,9 @@ type ProviderDockerlessOptions struct { // IgnorePaths are additional ignore paths that should be ignored during deletion IgnorePaths string `json:"ignorePaths,omitempty"` + // Registry to use as remote cache + RegistryCache string `json:"registryCache,omitempty"` + // DisableDockerCredentials prevents docker credentials from getting injected DisableDockerCredentials types.StrBool `json:"disableDockerCredentials,omitempty"` } diff --git a/vendor/github.com/loft-sh/devpod/pkg/provider/workspace.go b/vendor/github.com/loft-sh/devpod/pkg/provider/workspace.go index cdd42f0..9eb0121 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/provider/workspace.go +++ b/vendor/github.com/loft-sh/devpod/pkg/provider/workspace.go @@ -2,6 +2,7 @@ package provider import ( "strings" + "time" "github.com/loft-sh/devpod/pkg/config" devcontainerconfig "github.com/loft-sh/devpod/pkg/devcontainer/config" @@ -130,6 +131,18 @@ type ContainerWorkspaceInfo struct { // ContainerTimeout is the timeout in minutes to wait until the agent tries // to delete the container. ContainerTimeout string `json:"containerInactivityTimeout,omitempty"` + + // Source is a WorkspaceSource to be used inside the container + Source WorkspaceSource `json:"source,omitempty"` + + // ContentFolder holds the folder where the content is stored + ContentFolder string `json:"contentFolder,omitempty"` + + // PullFromInsideContainer determines if project should be pulled from Source when container starts + PullFromInsideContainer types.StrBool `json:"pullFromInsideContainer,omitempty"` + + // Agent holds the agent info + Agent ProviderAgentConfig `json:"agent,omitempty"` } type AgentWorkspaceInfo struct { @@ -160,6 +173,12 @@ type AgentWorkspaceInfo struct { // Origin holds the folder where this config was loaded from Origin string `json:"-"` + + // InjectTimeout specifies how long to wait for the agent to be injected into the dev container + InjectTimeout time.Duration `json:"injectTimeout,omitempty"` + + // RegistryCache defines the registry to use for caching builds + RegistryCache string `json:"registryCache,omitempty"` } type CLIOptions struct { @@ -171,6 +190,8 @@ type CLIOptions struct { PrebuildRepositories []string `json:"prebuildRepositories,omitempty"` DevContainerImage string `json:"devContainerImage,omitempty"` DevContainerPath string `json:"devContainerPath,omitempty"` + DevContainerSource string `json:"devContainerSource,omitempty"` + EnvironmentTemplate string `json:"environmentTemplate,omitempty"` WorkspaceEnv []string `json:"workspaceEnv,omitempty"` WorkspaceEnvFile []string `json:"workspaceEnvFile,omitempty"` InitEnv []string `json:"initEnv,omitempty"` @@ -180,8 +201,6 @@ type CLIOptions struct { DisableDaemon bool `json:"disableDaemon,omitempty"` DaemonInterval string `json:"daemonInterval,omitempty"` ForceCredentials bool `json:"forceCredentials,omitempty"` - GitBranch string `json:"gitBranch,omitempty"` - GitCommit string `json:"gitCommit,omitempty"` GitCloneStrategy git.CloneStrategy `json:"gitCloneStrategy,omitempty"` FallbackImage string `json:"fallbackImage,omitempty"` GitSSHSigningKey string `json:"gitSshSigningKey,omitempty"` @@ -200,8 +219,10 @@ type CLIOptions struct { type BuildOptions struct { CLIOptions - Platform string - NoBuild bool + Platform string + RegistryCache string + ExportCache bool + NoBuild bool } func (w WorkspaceSource) String() string { diff --git a/vendor/github.com/loft-sh/devpod/pkg/shell/shell.go b/vendor/github.com/loft-sh/devpod/pkg/shell/shell.go index c080238..0ea716e 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/shell/shell.go +++ b/vendor/github.com/loft-sh/devpod/pkg/shell/shell.go @@ -134,7 +134,13 @@ func GetShell(userName string) ([]string, error) { return []string{shell}, nil } - // fallback to path discovery if unsuccessful + // fallback to $SHELL env var + shell, ok := os.LookupEnv("SHELL") + if ok { + return []string{shell}, nil + } + + // fallback to path discovery _, err = exec.LookPath("bash") if err == nil { return []string{"bash"}, nil diff --git a/vendor/github.com/loft-sh/devpod/pkg/ssh/config.go b/vendor/github.com/loft-sh/devpod/pkg/ssh/config.go index a155d71..e7ee232 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/ssh/config.go +++ b/vendor/github.com/loft-sh/devpod/pkg/ssh/config.go @@ -24,15 +24,15 @@ var ( MarkerEndPrefix = "# DevPod End " ) -func ConfigureSSHConfig(sshConfigPath, context, workspace, user, workdir string, gpgagent bool, log log.Logger) error { - return configureSSHConfigSameFile(sshConfigPath, context, workspace, user, workdir, "", gpgagent, log) +func ConfigureSSHConfig(sshConfigPath, context, workspace, user, workdir string, gpgagent bool, devPodHome string, log log.Logger) error { + return configureSSHConfigSameFile(sshConfigPath, context, workspace, user, workdir, "", gpgagent, devPodHome, log) } -func configureSSHConfigSameFile(sshConfigPath, context, workspace, user, workdir, command string, gpgagent bool, log log.Logger) error { +func configureSSHConfigSameFile(sshConfigPath, context, workspace, user, workdir, command string, gpgagent bool, devPodHome string, log log.Logger) error { configLock.Lock() defer configLock.Unlock() - newFile, err := addHost(sshConfigPath, workspace+"."+"devpod", user, context, workspace, workdir, command, gpgagent) + newFile, err := addHost(sshConfigPath, workspace+"."+"devpod", user, context, workspace, workdir, command, gpgagent, devPodHome) if err != nil { return errors.Wrap(err, "parse ssh config") } @@ -46,7 +46,7 @@ type DevPodSSHEntry struct { Workspace string } -func addHost(path, host, user, context, workspace, workdir, command string, gpgagent bool) (string, error) { +func addHost(path, host, user, context, workspace, workdir, command string, gpgagent bool, devPodHome string) (string, error) { newConfig, err := removeFromConfig(path, host) if err != nil { return "", err @@ -58,10 +58,10 @@ func addHost(path, host, user, context, workspace, workdir, command string, gpga return "", err } - return addHostSection(newConfig, execPath, host, user, context, workspace, workdir, command, gpgagent) + return addHostSection(newConfig, execPath, host, user, context, workspace, workdir, command, gpgagent, devPodHome) } -func addHostSection(config, execPath, host, user, context, workspace, workdir, command string, gpgagent bool) (string, error) { +func addHostSection(config, execPath, host, user, context, workspace, workdir, command string, gpgagent bool, devPodHome string) (string, error) { newLines := []string{} // add new section startMarker := MarkerStartPrefix + host @@ -73,17 +73,24 @@ func addHostSection(config, execPath, host, user, context, workspace, workdir, c newLines = append(newLines, " StrictHostKeyChecking no") newLines = append(newLines, " UserKnownHostsFile /dev/null") newLines = append(newLines, " HostKeyAlgorithms rsa-sha2-256,rsa-sha2-512,ssh-rsa") + + proxyCommand := "" if command != "" { - newLines = append(newLines, fmt.Sprintf(" ProxyCommand \"%s\"", command)) - } else if gpgagent { - newLines = append(newLines, fmt.Sprintf(" ProxyCommand \"%s\" ssh --gpg-agent-forwarding --stdio --context %s --user %s %s", execPath, context, user, workspace)) + proxyCommand = fmt.Sprintf(" ProxyCommand \"%s\"", command) } else { - proxyCommand := fmt.Sprintf(" ProxyCommand \"%s\" ssh --stdio --context %s --user %s %s", execPath, context, user, workspace) - if workdir != "" { - proxyCommand = fmt.Sprintf("%s --workdir %s", proxyCommand, workdir) - } - newLines = append(newLines, proxyCommand) + proxyCommand = fmt.Sprintf(" ProxyCommand \"%s\" ssh --stdio --context %s --user %s %s", execPath, context, user, workspace) + } + + if devPodHome != "" { + proxyCommand = fmt.Sprintf("%s --devpod-home \"%s\"", proxyCommand, devPodHome) + } + if workdir != "" { + proxyCommand = fmt.Sprintf("%s --workdir \"%s\"", proxyCommand, workdir) + } + if gpgagent { + proxyCommand = fmt.Sprintf("%s --gpg-agent-forwarding", proxyCommand) } + newLines = append(newLines, proxyCommand) newLines = append(newLines, " User "+user) newLines = append(newLines, endMarker) diff --git a/vendor/github.com/loft-sh/devpod/pkg/types/types.go b/vendor/github.com/loft-sh/devpod/pkg/types/types.go index f5fb6b3..29bb942 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/types/types.go +++ b/vendor/github.com/loft-sh/devpod/pkg/types/types.go @@ -140,7 +140,7 @@ func (l *LifecycleHook) UnmarshalJSON(data []byte) error { type StrBool string // UnmarshalJSON parses fields that may be numbers or booleans. -func (f *StrBool) UnmarshalJSON(data []byte) error { +func (s *StrBool) UnmarshalJSON(data []byte) error { var jsonObj interface{} err := json.Unmarshal(data, &jsonObj) if err != nil { @@ -148,15 +148,23 @@ func (f *StrBool) UnmarshalJSON(data []byte) error { } switch obj := jsonObj.(type) { case string: - *f = StrBool(obj) + *s = StrBool(obj) return nil case bool: - *f = StrBool(strconv.FormatBool(obj)) + *s = StrBool(strconv.FormatBool(obj)) return nil } return ErrUnsupportedType } +func (s *StrBool) Bool() (bool, error) { + if s == nil { + return false, nil + } + + return strconv.ParseBool(string(*s)) +} + type OptionEnum struct { Value string `json:"value,omitempty"` DisplayName string `json:"displayName,omitempty"` diff --git a/vendor/github.com/loft-sh/devpod/pkg/util/hash/hash.go b/vendor/github.com/loft-sh/devpod/pkg/util/hash/hash.go index b0c1df8..c17f94a 100644 --- a/vendor/github.com/loft-sh/devpod/pkg/util/hash/hash.go +++ b/vendor/github.com/loft-sh/devpod/pkg/util/hash/hash.go @@ -22,7 +22,7 @@ var ( errFileReadOverLimit = errors.New("read files over limit") ) -func DirectoryHash(srcPath string, excludePatterns []string) (string, error) { +func DirectoryHash(srcPath string, excludePatterns, includeFiles []string) (string, error) { srcPath, err := filepath.Abs(srcPath) if err != nil { return "", err @@ -86,6 +86,17 @@ func DirectoryHash(srcPath string, excludePatterns []string) (string, error) { } relFilePath = filepath.ToSlash(relFilePath) + // Ensure file affects build context + include := false + for _, f := range includeFiles { + if strings.HasPrefix(relFilePath, f) { + include = true + } + } + if !include { + return nil + } + skip := false // If "include" is an exact match for the current file diff --git a/vendor/modules.txt b/vendor/modules.txt index b152cdb..83833e2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -54,7 +54,7 @@ github.com/k0kubun/go-ansi # github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 ## explicit github.com/kballard/go-shellquote -# github.com/loft-sh/devpod v0.5.19 +# github.com/loft-sh/devpod v0.5.20 ## explicit; go 1.22.5 github.com/loft-sh/devpod/cmd/flags github.com/loft-sh/devpod/pkg/client