diff --git a/cmd/envbuilder/main.go b/cmd/envbuilder/main.go index 7e18be2d..1f6741c8 100644 --- a/cmd/envbuilder/main.go +++ b/cmd/envbuilder/main.go @@ -14,7 +14,7 @@ import ( "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/envbuilder" - "github.com/spf13/cobra" + "github.com/coder/serpent" // *Never* remove this. Certificates are not bundled as part // of the container, so this is necessary for all connections @@ -23,15 +23,11 @@ import ( ) func main() { - root := &cobra.Command{ - Use: "envbuilder", - // Hide usage because we don't want to show the - // "envbuilder [command] --help" output on error. - SilenceUsage: true, - SilenceErrors: true, - RunE: func(cmd *cobra.Command, args []string) error { - options := envbuilder.OptionsFromEnv(os.LookupEnv) - + var options envbuilder.Options + cmd := serpent.Command{ + Use: "envbuilder", + Options: options.CLI(), + Handler: func(inv *serpent.Invocation) error { var sendLogs func(ctx context.Context, log ...agentsdk.Log) error agentURL := os.Getenv("CODER_AGENT_URL") agentToken := os.Getenv("CODER_AGENT_TOKEN") @@ -54,7 +50,7 @@ func main() { } var flushAndClose func(ctx context.Context) error sendLogs, flushAndClose = agentsdk.LogsSender(agentsdk.ExternalLogSourceID, client.PatchLogs, slog.Logger{}) - defer flushAndClose(cmd.Context()) + defer flushAndClose(inv.Context()) // This adds the envbuilder subsystem. // If telemetry is enabled in a Coder deployment, @@ -70,23 +66,24 @@ func main() { options.Logger = func(level codersdk.LogLevel, format string, args ...interface{}) { output := fmt.Sprintf(format, args...) - fmt.Fprintln(cmd.ErrOrStderr(), output) + fmt.Fprintln(inv.Stderr, output) if sendLogs != nil { - sendLogs(cmd.Context(), agentsdk.Log{ + sendLogs(inv.Context(), agentsdk.Log{ CreatedAt: time.Now(), Output: output, Level: level, }) } } - err := envbuilder.Run(cmd.Context(), options) + + err := envbuilder.Run(inv.Context(), options) if err != nil { options.Logger(codersdk.LogLevelError, "error: %s", err) } return err }, } - err := root.Execute() + err := cmd.Invoke().WithOS().Run() if err != nil { fmt.Fprintf(os.Stderr, "error: %v", err) os.Exit(1) diff --git a/envbuilder.go b/envbuilder.go index 5fed3019..acc666f8 100644 --- a/envbuilder.go +++ b/envbuilder.go @@ -77,184 +77,14 @@ var ( MagicFile = filepath.Join(MagicDir, "built") ) -type Options struct { - // SetupScript is the script to run before the init script. - // It runs as the root user regardless of the user specified - // in the devcontainer.json file. - - // SetupScript is ran as the root user prior to the init script. - // It is used to configure envbuilder dynamically during the runtime. - // e.g. specifying whether to start `systemd` or `tiny init` for PID 1. - SetupScript string `env:"SETUP_SCRIPT"` - - // InitScript is the script to run to initialize the workspace. - InitScript string `env:"INIT_SCRIPT"` - - // InitCommand is the command to run to initialize the workspace. - InitCommand string `env:"INIT_COMMAND"` - - // InitArgs are the arguments to pass to the init command. - // They are split according to `/bin/sh` rules with - // https://github.com/kballard/go-shellquote - InitArgs string `env:"INIT_ARGS"` - - // CacheRepo is the name of the container registry - // to push the cache image to. If this is empty, the cache - // will not be pushed. - CacheRepo string `env:"CACHE_REPO"` - - // BaseImageCacheDir is the path to a directory where the base - // image can be found. This should be a read-only directory - // solely mounted for the purpose of caching the base image. - BaseImageCacheDir string `env:"BASE_IMAGE_CACHE_DIR"` - - // LayerCacheDir is the path to a directory where built layers - // will be stored. This spawns an in-memory registry to serve - // the layers from. - // - // It will override CacheRepo if both are specified. - LayerCacheDir string `env:"LAYER_CACHE_DIR"` - - // DevcontainerDir is a path to the folder containing - // the devcontainer.json file that will be used to build the - // workspace and can either be an absolute path or a path - // relative to the workspace folder. If not provided, defaults to - // `.devcontainer`. - DevcontainerDir string `env:"DEVCONTAINER_DIR"` - - // DevcontainerJSONPath is a path to a devcontainer.json file - // that is either an absolute path or a path relative to - // DevcontainerDir. This can be used in cases where one wants - // to substitute an edited devcontainer.json file for the one - // that exists in the repo. - // If neither `DevcontainerDir` nor `DevcontainerJSONPath` is provided, - // envbuilder will browse following directories to locate it: - // 1. `.devcontainer/devcontainer.json` - // 2. `.devcontainer.json` - // 3. `.devcontainer//devcontainer.json` - DevcontainerJSONPath string `env:"DEVCONTAINER_JSON_PATH"` - - // DockerfilePath is a relative path to the Dockerfile that - // will be used to build the workspace. This is an alternative - // to using a devcontainer that some might find simpler. - DockerfilePath string `env:"DOCKERFILE_PATH"` - - // BuildContextPath can be specified when a DockerfilePath is specified outside the base WorkspaceFolder. - // This path MUST be relative to the WorkspaceFolder path into which the repo is cloned. - BuildContextPath string `env:"BUILD_CONTEXT_PATH"` - - // CacheTTLDays is the number of days to use cached layers before - // expiring them. Defaults to 7 days. - CacheTTLDays int `env:"CACHE_TTL_DAYS"` - - // DockerConfigBase64 is a base64 encoded Docker config - // file that will be used to pull images from private - // container registries. - DockerConfigBase64 string `env:"DOCKER_CONFIG_BASE64"` - - // FallbackImage specifies an alternative image to use when neither - // an image is declared in the devcontainer.json file nor a Dockerfile is present. - // If there's a build failure (from a faulty Dockerfile) or a misconfiguration, - // this image will be the substitute. - // Set `ExitOnBuildFailure` to true to halt the container if the build faces an issue. - FallbackImage string `env:"FALLBACK_IMAGE"` - - // ExitOnBuildFailure terminates the container upon a build failure. - // This is handy when preferring the `FALLBACK_IMAGE` in cases where - // no devcontainer.json or image is provided. However, it ensures - // that the container stops if the build process encounters an error. - ExitOnBuildFailure bool `env:"EXIT_ON_BUILD_FAILURE"` - - // ForceSafe ignores any filesystem safety checks. - // This could cause serious harm to your system! - // This is used in cases where bypass is needed - // to unblock customers! - ForceSafe bool `env:"FORCE_SAFE"` - - // Insecure bypasses TLS verification when cloning - // and pulling from container registries. - Insecure bool `env:"INSECURE"` - - // IgnorePaths is a comma separated list of paths - // to ignore when building the workspace. - IgnorePaths []string `env:"IGNORE_PATHS"` - - // SkipRebuild skips building if the MagicFile exists. - // This is used to skip building when a container is - // restarting. e.g. docker stop -> docker start - // This value can always be set to true - even if the - // container is being started for the first time. - SkipRebuild bool `env:"SKIP_REBUILD"` - - // GitURL is the URL of the Git repository to clone. - // This is optional! - GitURL string `env:"GIT_URL"` - - // GitCloneDepth is the depth to use when cloning - // the Git repository. - GitCloneDepth int `env:"GIT_CLONE_DEPTH"` - - // GitCloneSingleBranch clones only a single branch - // of the Git repository. - GitCloneSingleBranch bool `env:"GIT_CLONE_SINGLE_BRANCH"` - - // GitUsername is the username to use for Git authentication. - // This is optional! - GitUsername string `env:"GIT_USERNAME"` - - // GitPassword is the password to use for Git authentication. - // This is optional! - GitPassword string `env:"GIT_PASSWORD"` - - // GitHTTPProxyURL is the url for the http proxy. - // This is optional! - GitHTTPProxyURL string `env:"GIT_HTTP_PROXY_URL"` - - // WorkspaceFolder is the path to the workspace folder - // that will be built. This is optional! - WorkspaceFolder string `env:"WORKSPACE_FOLDER"` - - // SSLCertBase64 is the content of an SSL cert file. - // This is useful for self-signed certificates. - SSLCertBase64 string `env:"SSL_CERT_BASE64"` - - // ExportEnvFile is an optional file path to a .env file where - // envbuilder will dump environment variables from devcontainer.json and - // the built container image. - ExportEnvFile string `env:"EXPORT_ENV_FILE"` - - // PostStartScriptPath is the path to a script that will be created by - // envbuilder based on the `postStartCommand` in devcontainer.json, if any - // is specified (otherwise the script is not created). If this is set, the - // specified InitCommand should check for the presence of this script and - // execute it after successful startup. - PostStartScriptPath string `env:"POST_START_SCRIPT_PATH"` - - // Logger is the logger to use for all operations. - Logger func(level codersdk.LogLevel, format string, args ...interface{}) - - // Filesystem is the filesystem to use for all operations. - // Defaults to the host filesystem. - Filesystem billy.Filesystem -} - // DockerConfig represents the Docker configuration file. type DockerConfig configfile.ConfigFile // Run runs the envbuilder. +// Logger is the logf to use for all operations. +// Filesystem is the filesystem to use for all operations. +// Defaults to the host filesystem. func Run(ctx context.Context, options Options) error { - if options.InitScript == "" { - options.InitScript = "sleep infinity" - } - if options.InitCommand == "" { - options.InitCommand = "/bin/sh" - } - if options.IgnorePaths == nil { - // Kubernetes frequently stores secrets in /var/run/secrets, and - // other applications might as well. This seems to be a sensible - // default, but if that changes, it's simple to adjust. - options.IgnorePaths = []string{"/var/run"} - } // Default to the shell! initArgs := []string{"-c", options.InitScript} if options.InitArgs != "" { @@ -268,26 +98,26 @@ func Run(ctx context.Context, options Options) error { options.Filesystem = &osfsWithChmod{osfs.New("/")} } if options.WorkspaceFolder == "" { - var err error - options.WorkspaceFolder, err = DefaultWorkspaceFolder(options.GitURL) + f, err := DefaultWorkspaceFolder(options.GitURL) if err != nil { return err } + options.WorkspaceFolder = f } - logf := options.Logger + stageNumber := 1 - startStage := func(format string, args ...interface{}) func(format string, args ...interface{}) { + startStage := func(format string, args ...any) func(format string, args ...any) { now := time.Now() stageNum := stageNumber stageNumber++ - logf(codersdk.LogLevelInfo, "#%d: %s", stageNum, fmt.Sprintf(format, args...)) + options.Logger(codersdk.LogLevelInfo, "#%d: %s", stageNum, fmt.Sprintf(format, args...)) - return func(format string, args ...interface{}) { - logf(codersdk.LogLevelInfo, "#%d: %s [%s]", stageNum, fmt.Sprintf(format, args...), time.Since(now)) + return func(format string, args ...any) { + options.Logger(codersdk.LogLevelInfo, "#%d: %s [%s]", stageNum, fmt.Sprintf(format, args...), time.Since(now)) } } - logf(codersdk.LogLevelInfo, "%s - Build development environments from repositories in a container", newColor(color.Bold).Sprintf("envbuilder")) + options.Logger(codersdk.LogLevelInfo, "%s - Build development environments from repositories in a container", newColor(color.Bold).Sprintf("envbuilder")) var caBundle []byte if options.SSLCertBase64 != "" { @@ -349,7 +179,7 @@ func Run(ctx context.Context, options Options) error { if line == "" { continue } - logf(codersdk.LogLevelInfo, "#1: %s", strings.TrimSpace(line)) + options.Logger(codersdk.LogLevelInfo, "#1: %s", strings.TrimSpace(line)) } } }() @@ -360,7 +190,7 @@ func Run(ctx context.Context, options Options) error { Insecure: options.Insecure, Progress: writer, SingleBranch: options.GitCloneSingleBranch, - Depth: options.GitCloneDepth, + Depth: int(options.GitCloneDepth), CABundle: caBundle, } @@ -387,8 +217,8 @@ func Run(ctx context.Context, options Options) error { endStage("📦 The repository already exists!") } } else { - logf(codersdk.LogLevelError, "Failed to clone repository: %s", fallbackErr.Error()) - logf(codersdk.LogLevelError, "Falling back to the default image...") + options.Logger(codersdk.LogLevelError, "Failed to clone repository: %s", fallbackErr.Error()) + options.Logger(codersdk.LogLevelError, "Falling back to the default image...") } } @@ -428,8 +258,8 @@ func Run(ctx context.Context, options Options) error { // devcontainer is a standard, so it's reasonable to be the default. devcontainerPath, devcontainerDir, err := findDevcontainerJSON(options) if err != nil { - logf(codersdk.LogLevelError, "Failed to locate devcontainer.json: %s", err.Error()) - logf(codersdk.LogLevelError, "Falling back to the default image...") + options.Logger(codersdk.LogLevelError, "Failed to locate devcontainer.json: %s", err.Error()) + options.Logger(codersdk.LogLevelError, "Falling back to the default image...") } else { // We know a devcontainer exists. // Let's parse it and use it! @@ -450,7 +280,7 @@ func Run(ctx context.Context, options Options) error { if err != nil { return fmt.Errorf("no Dockerfile or image found: %w", err) } - logf(codersdk.LogLevelInfo, "No Dockerfile or image specified; falling back to the default image...") + options.Logger(codersdk.LogLevelInfo, "No Dockerfile or image specified; falling back to the default image...") fallbackDockerfile = defaultParams.DockerfilePath } buildParams, err = devContainer.Compile(options.Filesystem, devcontainerDir, MagicDir, fallbackDockerfile, options.WorkspaceFolder, false) @@ -459,8 +289,8 @@ func Run(ctx context.Context, options Options) error { } scripts = devContainer.LifecycleScripts } else { - logf(codersdk.LogLevelError, "Failed to parse devcontainer.json: %s", err.Error()) - logf(codersdk.LogLevelError, "Falling back to the default image...") + options.Logger(codersdk.LogLevelError, "Failed to parse devcontainer.json: %s", err.Error()) + options.Logger(codersdk.LogLevelError, "Falling back to the default image...") } } } else { @@ -471,8 +301,8 @@ func Run(ctx context.Context, options Options) error { // not defined, show a warning dockerfileDir := filepath.Dir(dockerfilePath) if dockerfileDir != filepath.Clean(options.WorkspaceFolder) && options.BuildContextPath == "" { - logf(codersdk.LogLevelWarn, "given dockerfile %q is below %q and no custom build context has been defined", dockerfilePath, options.WorkspaceFolder) - logf(codersdk.LogLevelWarn, "\t-> set BUILD_CONTEXT_PATH to %q to fix", dockerfileDir) + options.Logger(codersdk.LogLevelWarn, "given dockerfile %q is below %q and no custom build context has been defined", dockerfilePath, options.WorkspaceFolder) + options.Logger(codersdk.LogLevelWarn, "\t-> set BUILD_CONTEXT_PATH to %q to fix", dockerfileDir) } dockerfile, err := options.Filesystem.Open(dockerfilePath) @@ -501,7 +331,7 @@ func Run(ctx context.Context, options Options) error { HijackLogrus(func(entry *logrus.Entry) { for _, line := range strings.Split(entry.Message, "\r") { - logf(codersdk.LogLevelInfo, "#2: %s", color.HiBlackString(line)) + options.Logger(codersdk.LogLevelInfo, "#2: %s", color.HiBlackString(line)) } }) @@ -517,9 +347,9 @@ func Run(ctx context.Context, options Options) error { } // Disable all logging from the registry... - logger := logrus.New() - logger.SetOutput(io.Discard) - entry := logrus.NewEntry(logger) + l := logrus.New() + l.SetOutput(io.Discard) + entry := logrus.NewEntry(l) dcontext.SetDefaultLogger(entry) ctx = dcontext.WithLogger(ctx, entry) @@ -540,7 +370,7 @@ func Run(ctx context.Context, options Options) error { go func() { err := srv.Serve(listener) if err != nil && !errors.Is(err, http.ErrServerClosed) { - logf(codersdk.LogLevelError, "Failed to serve registry: %s", err.Error()) + options.Logger(codersdk.LogLevelError, "Failed to serve registry: %s", err.Error()) } }() closeAfterBuild = func() { @@ -548,7 +378,7 @@ func Run(ctx context.Context, options Options) error { _ = listener.Close() } if options.CacheRepo != "" { - logf(codersdk.LogLevelWarn, "Overriding cache repo with local registry...") + options.Logger(codersdk.LogLevelWarn, "Overriding cache repo with local registry...") } options.CacheRepo = fmt.Sprintf("localhost:%d/local/cache", tcpAddr.Port) } @@ -611,13 +441,13 @@ func Run(ctx context.Context, options Options) error { go func() { scanner := bufio.NewScanner(stdoutReader) for scanner.Scan() { - logf(codersdk.LogLevelInfo, "%s", scanner.Text()) + options.Logger(codersdk.LogLevelInfo, "%s", scanner.Text()) } }() go func() { scanner := bufio.NewScanner(stderrReader) for scanner.Scan() { - logf(codersdk.LogLevelInfo, "%s", scanner.Text()) + options.Logger(codersdk.LogLevelInfo, "%s", scanner.Text()) } }() cacheTTL := time.Hour * 24 * 7 @@ -697,13 +527,13 @@ func Run(ctx context.Context, options Options) error { fallback = true fallbackErr = err case strings.Contains(err.Error(), "unexpected status code 401 Unauthorized"): - logf(codersdk.LogLevelError, "Unable to pull the provided image. Ensure your registry credentials are correct!") + options.Logger(codersdk.LogLevelError, "Unable to pull the provided image. Ensure your registry credentials are correct!") } if !fallback || options.ExitOnBuildFailure { return err } - logf(codersdk.LogLevelError, "Failed to build: %s", err) - logf(codersdk.LogLevelError, "Falling back to the default image...") + options.Logger(codersdk.LogLevelError, "Failed to build: %s", err) + options.Logger(codersdk.LogLevelError, "Falling back to the default image...") buildParams, err = defaultBuildParams() if err != nil { return err @@ -746,10 +576,10 @@ func Run(ctx context.Context, options Options) error { if err != nil { return fmt.Errorf("unmarshal metadata: %w", err) } - logf(codersdk.LogLevelInfo, "#3: 👀 Found devcontainer.json label metadata in image...") + options.Logger(codersdk.LogLevelInfo, "#3: 👀 Found devcontainer.json label metadata in image...") for _, container := range devContainer { if container.RemoteUser != "" { - logf(codersdk.LogLevelInfo, "#3: 🧑 Updating the user to %q!", container.RemoteUser) + options.Logger(codersdk.LogLevelInfo, "#3: 🧑 Updating the user to %q!", container.RemoteUser) configFile.Config.User = container.RemoteUser } @@ -844,7 +674,7 @@ func Run(ctx context.Context, options Options) error { username = buildParams.User } if username == "" { - logf(codersdk.LogLevelWarn, "#3: no user specified, using root") + options.Logger(codersdk.LogLevelWarn, "#3: no user specified, using root") } userInfo, err := getUser(username) @@ -899,7 +729,7 @@ func Run(ctx context.Context, options Options) error { // We execute the initialize script as the root user! os.Setenv("HOME", "/root") - logf(codersdk.LogLevelInfo, "=== Running the setup command %q as the root user...", options.SetupScript) + options.Logger(codersdk.LogLevelInfo, "=== Running the setup command %q as the root user...", options.SetupScript) envKey := "ENVBUILDER_ENV" envFile := filepath.Join("/", MagicDir, "environ") @@ -926,7 +756,7 @@ func Run(ctx context.Context, options Options) error { go func() { scanner := bufio.NewScanner(&buf) for scanner.Scan() { - logf(codersdk.LogLevelInfo, "%s", scanner.Text()) + options.Logger(codersdk.LogLevelInfo, "%s", scanner.Text()) } }() @@ -992,7 +822,7 @@ func Run(ctx context.Context, options Options) error { return fmt.Errorf("set uid: %w", err) } - logf(codersdk.LogLevelInfo, "=== Running the init command %s %+v as the %q user...", options.InitCommand, initArgs, userInfo.user.Username) + options.Logger(codersdk.LogLevelInfo, "=== Running the init command %s %+v as the %q user...", options.InitCommand, initArgs, userInfo.user.Username) err = syscall.Exec(options.InitCommand, append([]string{options.InitCommand}, initArgs...), os.Environ()) if err != nil { @@ -1065,7 +895,7 @@ func findUser(nameOrID string) (*user.User, error) { func execOneLifecycleScript( ctx context.Context, - logf func(level codersdk.LogLevel, format string, args ...interface{}), + logf func(level codersdk.LogLevel, format string, args ...any), s devcontainer.LifecycleScript, scriptName string, userInfo userInfo, @@ -1137,43 +967,6 @@ func createPostStartScript(path string, postStartCommand devcontainer.LifecycleS return nil } -// OptionsFromEnv returns a set of options from environment variables. -func OptionsFromEnv(getEnv func(string) (string, bool)) Options { - options := Options{} - - val := reflect.ValueOf(&options).Elem() - typ := val.Type() - - for i := 0; i < val.NumField(); i++ { - field := val.Field(i) - fieldTyp := typ.Field(i) - env := fieldTyp.Tag.Get("env") - if env == "" { - continue - } - e, ok := getEnv(env) - if !ok { - continue - } - switch fieldTyp.Type.Kind() { - case reflect.String: - field.SetString(e) - case reflect.Bool: - v, _ := strconv.ParseBool(e) - field.SetBool(v) - case reflect.Int: - v, _ := strconv.ParseInt(e, 10, 64) - field.SetInt(v) - case reflect.Slice: - field.Set(reflect.ValueOf(strings.Split(e, ","))) - default: - panic(fmt.Sprintf("unsupported type %s in OptionsFromEnv", fieldTyp.Type.String())) - } - } - - return options -} - // unsetOptionsEnv unsets all environment variables that are used // to configure the options. func unsetOptionsEnv() { @@ -1211,6 +1004,7 @@ func findDevcontainerJSON(options Options) (string, string, error) { if devcontainerDir == "" { devcontainerDir = ".devcontainer" } + // If `devcontainerDir` is not an absolute path, assume it is relative to the workspace folder. if !filepath.IsAbs(devcontainerDir) { devcontainerDir = filepath.Join(options.WorkspaceFolder, devcontainerDir) @@ -1252,16 +1046,15 @@ func findDevcontainerJSON(options Options) (string, string, error) { return "", "", err } - logf := options.Logger for _, fileInfo := range fileInfos { if !fileInfo.IsDir() { - logf(codersdk.LogLevelDebug, `%s is a file`, fileInfo.Name()) + options.Logger(codersdk.LogLevelDebug, `%s is a file`, fileInfo.Name()) continue } location := filepath.Join(devcontainerDir, fileInfo.Name(), "devcontainer.json") if _, err := options.Filesystem.Stat(location); err != nil { - logf(codersdk.LogLevelDebug, `stat %s failed: %s`, location, err.Error()) + options.Logger(codersdk.LogLevelDebug, `stat %s failed: %s`, location, err.Error()) continue } diff --git a/envbuilder_test.go b/envbuilder_test.go index ecd9d663..e38f0a4d 100644 --- a/envbuilder_test.go +++ b/envbuilder_test.go @@ -17,36 +17,3 @@ func TestDefaultWorkspaceFolder(t *testing.T) { require.NoError(t, err) require.Equal(t, envbuilder.EmptyWorkspaceDir, dir) } - -func TestSystemOptions(t *testing.T) { - t.Parallel() - opts := map[string]string{ - "INIT_SCRIPT": "echo hello", - "CACHE_REPO": "kylecarbs/testing", - "CACHE_TTL_DAYS": "30", - "DEVCONTAINER_JSON_PATH": "/tmp/devcontainer.json", - "DOCKERFILE_PATH": "Dockerfile", - "FALLBACK_IMAGE": "ubuntu:latest", - "FORCE_SAFE": "true", - "INSECURE": "false", - "GIT_CLONE_DEPTH": "1", - "GIT_URL": "https://github.com/coder/coder", - "WORKSPACE_FOLDER": "/workspaces/coder", - "GIT_HTTP_PROXY_URL": "http://company-proxy.com:8081", - } - env := envbuilder.OptionsFromEnv(func(s string) (string, bool) { - return opts[s], true - }) - require.Equal(t, "echo hello", env.InitScript) - require.Equal(t, "kylecarbs/testing", env.CacheRepo) - require.Equal(t, "/tmp/devcontainer.json", env.DevcontainerJSONPath) - require.Equal(t, 30, env.CacheTTLDays) - require.Equal(t, "Dockerfile", env.DockerfilePath) - require.Equal(t, "ubuntu:latest", env.FallbackImage) - require.True(t, env.ForceSafe) - require.False(t, env.Insecure) - require.Equal(t, 1, env.GitCloneDepth) - require.Equal(t, "https://github.com/coder/coder", env.GitURL) - require.Equal(t, "/workspaces/coder", env.WorkspaceFolder) - require.Equal(t, "http://company-proxy.com:8081", env.GitHTTPProxyURL) -} diff --git a/go.mod b/go.mod index 3f3e3ab4..8fa755aa 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/coder/envbuilder -go 1.21.1 +go 1.21.4 -toolchain go1.21.5 +toolchain go1.21.9 // There are a few options we need added to Kaniko! // See: https://github.com/GoogleContainerTools/kaniko/compare/main...coder:kaniko:main @@ -20,6 +20,7 @@ require ( github.com/GoogleContainerTools/kaniko v1.9.2 github.com/breml/rootcerts v0.2.10 github.com/coder/coder/v2 v2.3.3 + github.com/coder/serpent v0.7.0 github.com/containerd/containerd v1.7.11 github.com/distribution/distribution/v3 v3.0.0-20230629214736-bac7f02e02a1 github.com/docker/cli v23.0.5+incompatible @@ -33,11 +34,10 @@ require ( github.com/moby/buildkit v0.11.6 github.com/otiai10/copy v1.14.0 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a golang.org/x/sync v0.6.0 - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 ) require ( @@ -88,12 +88,14 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.21.1 // indirect github.com/aws/smithy-go v1.19.0 // indirect github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230522190001-adf1bafd791a // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect github.com/cilium/ebpf v0.12.3 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 // indirect github.com/coder/retry v1.5.1 // indirect github.com/coder/terraform-provider-coder v0.13.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect @@ -161,7 +163,6 @@ require ( github.com/hashicorp/yamux v0.1.1 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect github.com/illarion/gonotify v1.0.1 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -171,7 +172,9 @@ require ( github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect @@ -195,6 +198,7 @@ require ( github.com/moby/sys/symlink v0.2.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect github.com/open-policy-agent/opa v0.58.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect @@ -204,6 +208,8 @@ require ( github.com/outcaste-io/ristretto v0.2.3 // indirect github.com/philhofer/fwd v1.1.2 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect + github.com/pion/transport/v2 v2.0.0 // indirect + github.com/pion/udp v0.1.4 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -212,6 +218,7 @@ require ( github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rootless-containers/rootlesskit v1.1.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect @@ -255,15 +262,15 @@ require ( go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect golang.org/x/crypto v0.19.0 // indirect - golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.20.0 // indirect + golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/tools v0.18.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index 10d41283..06659e2a 100644 --- a/go.sum +++ b/go.sum @@ -198,8 +198,12 @@ github.com/coder/gvisor v0.0.0-20230714132058-be2e4ac102c3 h1:gtuDFa+InmMVUYiurB github.com/coder/gvisor v0.0.0-20230714132058-be2e4ac102c3/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q= github.com/coder/kaniko v0.0.0-20240103181425-f83d15201044 h1:28V9fkQdceB0FzjyavTU6r+II5NwRpJqNdzUSfe6RPU= github.com/coder/kaniko v0.0.0-20240103181425-f83d15201044/go.mod h1:byIUWxhLPDuO0o38iG+ffFWmIhUCSc8/N1INJZhjcUY= +github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= +github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc= github.com/coder/retry v1.5.1/go.mod h1:blHMk9vs6LkoRT9ZHyuZo360cufXEhrxqvEzeMtRGoY= +github.com/coder/serpent v0.7.0 h1:zGpD2GlF3lKIVkMjNGKbkip88qzd5r/TRcc30X/SrT0= +github.com/coder/serpent v0.7.0/go.mod h1:REkJ5ZFHQUWFTPLExhXYZ1CaHFjxvGNRlLXLdsI08YA= github.com/coder/tailscale v1.1.1-0.20240214140224-3788ab894ba1 h1:A7dZHNidAVH6Kxn5D3hTEH+iRO8slnM0aRer6/cxlyE= github.com/coder/tailscale v1.1.1-0.20240214140224-3788ab894ba1/go.mod h1:L8tPrwSi31RAMEMV8rjb0vYTGs7rXt8rAHbqY/p41j4= github.com/coder/terraform-provider-coder v0.13.0 h1:MjW7O+THAiqIYcxyiuBoGbFEduqgjp7tUZhSkiwGxwo= @@ -509,8 +513,6 @@ github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwso github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -690,6 +692,11 @@ github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2 github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4= +github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc= +github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8= +github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -723,6 +730,7 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA= github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -747,8 +755,6 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= @@ -930,16 +936,16 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE= -golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= @@ -952,13 +958,14 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1009,6 +1016,7 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1019,6 +1027,7 @@ golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -1052,14 +1061,14 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= diff --git a/options.go b/options.go new file mode 100644 index 00000000..9066d64b --- /dev/null +++ b/options.go @@ -0,0 +1,274 @@ +package envbuilder + +import ( + "github.com/coder/coder/v2/codersdk" + "github.com/coder/serpent" + "github.com/go-git/go-billy/v5" +) + +// Options contains the configuration for the envbuilder. +type Options struct { + SetupScript string + InitScript string + InitCommand string + InitArgs string + CacheRepo string + BaseImageCacheDir string + LayerCacheDir string + DevcontainerDir string + DevcontainerJSONPath string + DockerfilePath string + BuildContextPath string + CacheTTLDays int64 + DockerConfigBase64 string + FallbackImage string + ExitOnBuildFailure bool + ForceSafe bool + Insecure bool + IgnorePaths []string + SkipRebuild bool + GitURL string + GitCloneDepth int64 + GitCloneSingleBranch bool + GitUsername string + GitPassword string + GitHTTPProxyURL string + WorkspaceFolder string + SSLCertBase64 string + ExportEnvFile string + PostStartScriptPath string + // Logger is the logger to use for all operations. + Logger func(level codersdk.LogLevel, format string, args ...interface{}) + // Filesystem is the filesystem to use for all operations. + // Defaults to the host filesystem. + Filesystem billy.Filesystem +} + +// Generate CLI options for the envbuilder command. +func (o *Options) CLI() serpent.OptionSet { + return serpent.OptionSet{ + { + Flag: "setup-script", + Env: "SETUP_SCRIPT", + Value: serpent.StringOf(&o.SetupScript), + Description: "The script to run before the init script. It runs as " + + "the root user regardless of the user specified in the devcontainer.json " + + "file.\n\nSetupScript is ran as the root user prior to the init script. " + + "It is used to configure envbuilder dynamically during the runtime. e.g. " + + "specifying whether to start systemd or tiny init for PID 1.", + }, + { + Flag: "init-script", + Env: "INIT_SCRIPT", + Default: "sleep infinity", + Value: serpent.StringOf(&o.InitScript), + Description: "The script to run to initialize the workspace.", + }, + { + Flag: "init-command", + Env: "INIT_COMMAND", + Default: "/bin/sh", + Value: serpent.StringOf(&o.InitCommand), + Description: "The command to run to initialize the workspace.", + }, + { + Flag: "init-args", + Env: "INIT_ARGS", + Value: serpent.StringOf(&o.InitArgs), + Description: "The arguments to pass to the init command. They are " + + "split according to /bin/sh rules with " + + "https://github.com/kballard/go-shellquote.", + }, + { + Flag: "cache-repo", + Env: "CACHE_REPO", + Value: serpent.StringOf(&o.CacheRepo), + Description: "The name of the container registry to push the cache " + + "image to. If this is empty, the cache will not be pushed.", + }, + { + Flag: "base-image-cache-dir", + Env: "BASE_IMAGE_CACHE_DIR", + Value: serpent.StringOf(&o.BaseImageCacheDir), + Description: "The path to a directory where the base image " + + "can be found. This should be a read-only directory solely mounted " + + "for the purpose of caching the base image.", + }, + { + Flag: "layer-cache-dir", + Env: "LAYER_CACHE_DIR", + Value: serpent.StringOf(&o.LayerCacheDir), + Description: "The path to a directory where built layers will " + + "be stored. This spawns an in-memory registry to serve the layers " + + "from.", + }, + { + Flag: "devcontainer-dir", + Env: "DEVCONTAINER_DIR", + Value: serpent.StringOf(&o.DevcontainerDir), + Description: "The path to the folder containing the " + + "devcontainer.json file that will be used to build the workspace " + + "and can either be an absolute path or a path relative to the " + + "workspace folder. If not provided, defaults to `.devcontainer`.", + }, + { + Flag: "devcontainer-json-path", + Env: "DEVCONTAINER_JSON_PATH", + Value: serpent.StringOf(&o.DevcontainerJSONPath), + Description: "The path to a devcontainer.json file that " + + "is either an absolute path or a path relative to DevcontainerDir. " + + "This can be used in cases where one wants to substitute an edited " + + "devcontainer.json file for the one that exists in the repo.", + }, + { + Flag: "dockerfile-path", + Env: "DOCKERFILE_PATH", + Value: serpent.StringOf(&o.DockerfilePath), + Description: "The relative path to the Dockerfile that will " + + "be used to build the workspace. This is an alternative to using " + + "a devcontainer that some might find simpler.", + }, + { + Flag: "build-context-path", + Env: "BUILD_CONTEXT_PATH", + Value: serpent.StringOf(&o.BuildContextPath), + Description: "Can be specified when a DockerfilePath is " + + "specified outside the base WorkspaceFolder. This path MUST be " + + "relative to the WorkspaceFolder path into which the repo is cloned.", + }, + { + Flag: "cache-ttl-days", + Env: "CACHE_TTL_DAYS", + Value: serpent.Int64Of(&o.CacheTTLDays), + Description: "The number of days to use cached layers before " + + "expiring them. Defaults to 7 days.", + }, + { + Flag: "docker-config-base64", + Env: "DOCKER_CONFIG_BASE64", + Value: serpent.StringOf(&o.DockerConfigBase64), + Description: "The base64 encoded Docker config file that " + + "will be used to pull images from private container registries.", + }, + { + Flag: "fallback-image", + Env: "FALLBACK_IMAGE", + Value: serpent.StringOf(&o.FallbackImage), + Description: "Specifies an alternative image to use when neither " + + "an image is declared in the devcontainer.json file nor a Dockerfile " + + "is present. If there's a build failure (from a faulty Dockerfile) " + + "or a misconfiguration, this image will be the substitute. Set " + + "ExitOnBuildFailure to true to halt the container if the build " + + "faces an issue.", + }, + { + Flag: "exit-on-build-failure", + Env: "EXIT_ON_BUILD_FAILURE", + Value: serpent.BoolOf(&o.ExitOnBuildFailure), + Description: "Terminates the container upon a build failure. " + + "This is handy when preferring the FALLBACK_IMAGE in cases where " + + "no devcontainer.json or image is provided. However, it ensures " + + "that the container stops if the build process encounters an error.", + }, + { + Flag: "force-safe", + Env: "FORCE_SAFE", + Value: serpent.BoolOf(&o.ForceSafe), + Description: "Ignores any filesystem safety checks. This could cause " + + "serious harm to your system! This is used in cases where bypass " + + "is needed to unblock customers.", + }, + { + Flag: "insecure", + Env: "INSECURE", + Value: serpent.BoolOf(&o.Insecure), + Description: "Bypass TLS verification when cloning and pulling from " + + "container registries.", + }, + { + Flag: "ignore-paths", + Env: "IGNORE_PATHS", + Value: serpent.StringArrayOf(&o.IgnorePaths), + Default: "/var/run", + Description: "The comma separated list of paths to ignore when " + + "building the workspace.", + }, + { + Flag: "skip-rebuild", + Env: "SKIP_REBUILD", + Value: serpent.BoolOf(&o.SkipRebuild), + Description: "Skip building if the MagicFile exists. This is used " + + "to skip building when a container is restarting. e.g. docker stop -> " + + "docker start This value can always be set to true - even if the " + + "container is being started for the first time.", + }, + { + Flag: "git-url", + Env: "GIT_URL", + Value: serpent.StringOf(&o.GitURL), + Description: "The URL of the Git repository to clone. This is optional.", + }, + { + Flag: "git-clone-depth", + Env: "GIT_CLONE_DEPTH", + Value: serpent.Int64Of(&o.GitCloneDepth), + Description: "The depth to use when cloning the Git repository.", + }, + { + Flag: "git-clone-single-branch", + Env: "GIT_CLONE_SINGLE_BRANCH", + Value: serpent.BoolOf(&o.GitCloneSingleBranch), + Description: "Clone only a single branch of the Git repository.", + }, + { + Flag: "git-username", + Env: "GIT_USERNAME", + Value: serpent.StringOf(&o.GitUsername), + Description: "The username to use for Git authentication. This is optional.", + }, + { + Flag: "git-password", + Env: "GIT_PASSWORD", + Value: serpent.StringOf(&o.GitPassword), + Description: "The password to use for Git authentication. This is optional.", + }, + { + Flag: "git-http-proxy-url", + Env: "GIT_HTTP_PROXY_URL", + Value: serpent.StringOf(&o.GitHTTPProxyURL), + Description: "The URL for the HTTP proxy. This is optional.", + }, + { + Flag: "workspace-folder", + Env: "WORKSPACE_FOLDER", + Value: serpent.StringOf(&o.WorkspaceFolder), + Description: "The path to the workspace folder that will " + + "be built. This is optional.", + }, + { + Flag: "ssl-cert-base64", + Env: "SSL_CERT_BASE64", + Value: serpent.StringOf(&o.SSLCertBase64), + Description: "The content of an SSL cert file. This is useful " + + "for self-signed certificates.", + }, + { + Flag: "export-env-file", + Env: "EXPORT_ENV_FILE", + Value: serpent.StringOf(&o.ExportEnvFile), + Description: "Optional file path to a .env file where " + + "envbuilder will dump environment variables from devcontainer.json " + + "and the built container image.", + }, + { + Flag: "post-start-script-path", + Env: "POST_START_SCRIPT_PATH", + Value: serpent.StringOf(&o.PostStartScriptPath), + Description: "The path to a script that will be created " + + "by envbuilder based on the postStartCommand in devcontainer.json, " + + "if any is specified (otherwise the script is not created). If this " + + "is set, the specified InitCommand should check for the presence of " + + "this script and execute it after successful startup.", + }, + } +}