diff --git a/cli/cliflag/cliflag_test.go b/cli/cliflag/cliflag_test.go index 542bb04abfd9d..2228b7e10bbc9 100644 --- a/cli/cliflag/cliflag_test.go +++ b/cli/cliflag/cliflag_test.go @@ -16,11 +16,11 @@ import ( //nolint:paralleltest func TestCliflag(t *testing.T) { t.Run("StringDefault", func(t *testing.T) { - var p string + var ptr string flagset, name, shorthand, env, usage := randomFlag() def, _ := cryptorand.String(10) - cliflag.StringVarP(flagset, &p, name, shorthand, env, def, usage) + cliflag.StringVarP(flagset, &ptr, name, shorthand, env, def, usage) got, err := flagset.GetString(name) require.NoError(t, err) require.Equal(t, def, got) @@ -29,24 +29,24 @@ func TestCliflag(t *testing.T) { }) t.Run("StringEnvVar", func(t *testing.T) { - var p string + var ptr string flagset, name, shorthand, env, usage := randomFlag() envValue, _ := cryptorand.String(10) t.Setenv(env, envValue) def, _ := cryptorand.String(10) - cliflag.StringVarP(flagset, &p, name, shorthand, env, def, usage) + cliflag.StringVarP(flagset, &ptr, name, shorthand, env, def, usage) got, err := flagset.GetString(name) require.NoError(t, err) require.Equal(t, envValue, got) }) t.Run("EmptyEnvVar", func(t *testing.T) { - var p string + var ptr string flagset, name, shorthand, _, usage := randomFlag() def, _ := cryptorand.String(10) - cliflag.StringVarP(flagset, &p, name, shorthand, "", def, usage) + cliflag.StringVarP(flagset, &ptr, name, shorthand, "", def, usage) got, err := flagset.GetString(name) require.NoError(t, err) require.Equal(t, def, got) @@ -55,11 +55,11 @@ func TestCliflag(t *testing.T) { }) t.Run("IntDefault", func(t *testing.T) { - var p uint8 + var ptr uint8 flagset, name, shorthand, env, usage := randomFlag() def, _ := cryptorand.Int63n(10) - cliflag.Uint8VarP(flagset, &p, name, shorthand, env, uint8(def), usage) + cliflag.Uint8VarP(flagset, &ptr, name, shorthand, env, uint8(def), usage) got, err := flagset.GetUint8(name) require.NoError(t, err) require.Equal(t, uint8(def), got) @@ -68,37 +68,37 @@ func TestCliflag(t *testing.T) { }) t.Run("IntEnvVar", func(t *testing.T) { - var p uint8 + var ptr uint8 flagset, name, shorthand, env, usage := randomFlag() envValue, _ := cryptorand.Int63n(10) t.Setenv(env, strconv.FormatUint(uint64(envValue), 10)) def, _ := cryptorand.Int() - cliflag.Uint8VarP(flagset, &p, name, shorthand, env, uint8(def), usage) + cliflag.Uint8VarP(flagset, &ptr, name, shorthand, env, uint8(def), usage) got, err := flagset.GetUint8(name) require.NoError(t, err) require.Equal(t, uint8(envValue), got) }) t.Run("IntFailParse", func(t *testing.T) { - var p uint8 + var ptr uint8 flagset, name, shorthand, env, usage := randomFlag() envValue, _ := cryptorand.String(10) t.Setenv(env, envValue) def, _ := cryptorand.Int63n(10) - cliflag.Uint8VarP(flagset, &p, name, shorthand, env, uint8(def), usage) + cliflag.Uint8VarP(flagset, &ptr, name, shorthand, env, uint8(def), usage) got, err := flagset.GetUint8(name) require.NoError(t, err) require.Equal(t, uint8(def), got) }) t.Run("BoolDefault", func(t *testing.T) { - var p bool + var ptr bool flagset, name, shorthand, env, usage := randomFlag() def, _ := cryptorand.Bool() - cliflag.BoolVarP(flagset, &p, name, shorthand, env, def, usage) + cliflag.BoolVarP(flagset, &ptr, name, shorthand, env, def, usage) got, err := flagset.GetBool(name) require.NoError(t, err) require.Equal(t, def, got) @@ -107,26 +107,26 @@ func TestCliflag(t *testing.T) { }) t.Run("BoolEnvVar", func(t *testing.T) { - var p bool + var ptr bool flagset, name, shorthand, env, usage := randomFlag() envValue, _ := cryptorand.Bool() t.Setenv(env, strconv.FormatBool(envValue)) def, _ := cryptorand.Bool() - cliflag.BoolVarP(flagset, &p, name, shorthand, env, def, usage) + cliflag.BoolVarP(flagset, &ptr, name, shorthand, env, def, usage) got, err := flagset.GetBool(name) require.NoError(t, err) require.Equal(t, envValue, got) }) t.Run("BoolFailParse", func(t *testing.T) { - var p bool + var ptr bool flagset, name, shorthand, env, usage := randomFlag() envValue, _ := cryptorand.String(10) t.Setenv(env, envValue) def, _ := cryptorand.Bool() - cliflag.BoolVarP(flagset, &p, name, shorthand, env, def, usage) + cliflag.BoolVarP(flagset, &ptr, name, shorthand, env, def, usage) got, err := flagset.GetBool(name) require.NoError(t, err) require.Equal(t, def, got) diff --git a/cli/start.go b/cli/start.go index 7f7aa6d4c11dc..da0545a79a5b0 100644 --- a/cli/start.go +++ b/cli/start.go @@ -13,6 +13,7 @@ import ( "net/url" "os" "os/signal" + "path/filepath" "time" "github.com/briandowns/spinner" @@ -42,6 +43,7 @@ func start() *cobra.Command { var ( accessURL string address string + cacheDir string dev bool postgresURL string // provisionerDaemonCount is a uint8 to ensure a number > 0. @@ -161,7 +163,7 @@ func start() *cobra.Command { provisionerDaemons := make([]*provisionerd.Server, 0) for i := 0; uint8(i) < provisionerDaemonCount; i++ { - daemonClose, err := newProvisionerDaemon(cmd.Context(), client, logger) + daemonClose, err := newProvisionerDaemon(cmd.Context(), client, logger, cacheDir) if err != nil { return xerrors.Errorf("create provisioner daemon: %w", err) } @@ -305,6 +307,8 @@ func start() *cobra.Command { cliflag.StringVarP(root.Flags(), &accessURL, "access-url", "", "CODER_ACCESS_URL", "", "Specifies the external URL to access Coder") cliflag.StringVarP(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", "The address to serve the API and dashboard") + // systemd uses the CACHE_DIRECTORY environment variable! + cliflag.StringVarP(root.Flags(), &cacheDir, "cache-dir", "", "CACHE_DIRECTORY", filepath.Join(os.TempDir(), ".coder-cache"), "Specifies a directory to cache binaries for provision operations.") cliflag.BoolVarP(root.Flags(), &dev, "dev", "", "CODER_DEV_MODE", false, "Serve Coder in dev mode for tinkering") cliflag.StringVarP(root.Flags(), &postgresURL, "postgres-url", "", "CODER_PG_CONNECTION_URL", "", "URL of a PostgreSQL database to connect to") cliflag.Uint8VarP(root.Flags(), &provisionerDaemonCount, "provisioner-daemons", "", "CODER_PROVISIONER_DAEMONS", 1, "The amount of provisioner daemons to create on start.") @@ -358,14 +362,15 @@ func createFirstUser(cmd *cobra.Command, client *codersdk.Client, cfg config.Roo return nil } -func newProvisionerDaemon(ctx context.Context, client *codersdk.Client, logger slog.Logger) (*provisionerd.Server, error) { +func newProvisionerDaemon(ctx context.Context, client *codersdk.Client, logger slog.Logger, cacheDir string) (*provisionerd.Server, error) { terraformClient, terraformServer := provisionersdk.TransportPipe() go func() { err := terraform.Serve(ctx, &terraform.ServeOptions{ ServeOptions: &provisionersdk.ServeOptions{ Listener: terraformServer, }, - Logger: logger, + CachePath: cacheDir, + Logger: logger, }) if err != nil { panic(err) diff --git a/coder.service b/coder.service index 9e1952688b206..3fc9a01f1e588 100644 --- a/coder.service +++ b/coder.service @@ -16,6 +16,7 @@ PrivateTmp=yes PrivateDevices=yes SecureBits=keep-caps AmbientCapabilities=CAP_IPC_LOCK +CacheDirectory=coder CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE NoNewPrivileges=yes ExecStart=/usr/bin/coder start diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index 62ea5ef9b2764..24656c09520b4 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -87,6 +87,14 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro }) } }() + if t.cachePath != "" { + err = terraform.SetEnv(map[string]string{ + "TF_PLUGIN_CACHE_DIR": t.cachePath, + }) + if err != nil { + return xerrors.Errorf("set terraform plugin cache dir: %w", err) + } + } terraform.SetStdout(writer) t.logger.Debug(shutdown, "running initialization") err = terraform.Init(shutdown) diff --git a/provisioner/terraform/serve.go b/provisioner/terraform/serve.go index bda2944ea0701..ef8f039d51412 100644 --- a/provisioner/terraform/serve.go +++ b/provisioner/terraform/serve.go @@ -34,6 +34,7 @@ type ServeOptions struct { // BinaryPath specifies the "terraform" binary to use. // If omitted, the $PATH will attempt to find it. BinaryPath string + CachePath string Logger slog.Logger } @@ -43,8 +44,9 @@ func Serve(ctx context.Context, options *ServeOptions) error { binaryPath, err := exec.LookPath("terraform") if err != nil { installer := &releases.ExactVersion{ - Product: product.Terraform, - Version: version.Must(version.NewVersion("1.1.7")), + InstallDir: options.CachePath, + Product: product.Terraform, + Version: version.Must(version.NewVersion("1.1.7")), } execPath, err := installer.Install(ctx) @@ -58,11 +60,13 @@ func Serve(ctx context.Context, options *ServeOptions) error { } return provisionersdk.Serve(ctx, &terraform{ binaryPath: options.BinaryPath, + cachePath: options.CachePath, logger: options.Logger, }, options.ServeOptions) } type terraform struct { binaryPath string + cachePath string logger slog.Logger }