mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: add the --ephemeral server flag (#16126)
Another PR to address https://github.com/coder/coder/issues/15109. Changes: - Introduces the `--ephemeral` flag, which changes the Coder config directory to a temporary location. The config directory is where the built-in PostgreSQL stores its data, so using a new one results in a deployment with a fresh state. The `--ephemeral` flag is set to replace the `--in-memory` flag once the in-memory database is removed.
This commit is contained in:
+32
-4
@@ -391,6 +391,21 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
}
|
||||
defer httpServers.Close()
|
||||
|
||||
if vals.EphemeralDeployment.Value() {
|
||||
r.globalConfig = filepath.Join(os.TempDir(), fmt.Sprintf("coder_ephemeral_%d", time.Now().UnixMilli()))
|
||||
if err := os.MkdirAll(r.globalConfig, 0o700); err != nil {
|
||||
return xerrors.Errorf("create ephemeral deployment directory: %w", err)
|
||||
}
|
||||
cliui.Infof(inv.Stdout, "Using an ephemeral deployment directory (%s)", r.globalConfig)
|
||||
defer func() {
|
||||
cliui.Infof(inv.Stdout, "Removing ephemeral deployment directory...")
|
||||
if err := os.RemoveAll(r.globalConfig); err != nil {
|
||||
cliui.Errorf(inv.Stderr, "Failed to remove ephemeral deployment directory: %v", err)
|
||||
} else {
|
||||
cliui.Infof(inv.Stdout, "Removed ephemeral deployment directory")
|
||||
}
|
||||
}()
|
||||
}
|
||||
config := r.createConfig()
|
||||
|
||||
builtinPostgres := false
|
||||
@@ -398,7 +413,16 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
if !vals.InMemoryDatabase && vals.PostgresURL == "" {
|
||||
var closeFunc func() error
|
||||
cliui.Infof(inv.Stdout, "Using built-in PostgreSQL (%s)", config.PostgresPath())
|
||||
pgURL, closeFunc, err := startBuiltinPostgres(ctx, config, logger)
|
||||
customPostgresCacheDir := ""
|
||||
// By default, built-in PostgreSQL will use the Coder root directory
|
||||
// for its cache. However, when a deployment is ephemeral, the root
|
||||
// directory is wiped clean on shutdown, defeating the purpose of using
|
||||
// it as a cache. So here we use a cache directory that will not get
|
||||
// removed on restart.
|
||||
if vals.EphemeralDeployment.Value() {
|
||||
customPostgresCacheDir = cacheDir
|
||||
}
|
||||
pgURL, closeFunc, err := startBuiltinPostgres(ctx, config, logger, customPostgresCacheDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1202,7 +1226,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
ctx, cancel := inv.SignalNotifyContext(ctx, InterruptSignals...)
|
||||
defer cancel()
|
||||
|
||||
url, closePg, err := startBuiltinPostgres(ctx, cfg, logger)
|
||||
url, closePg, err := startBuiltinPostgres(ctx, cfg, logger, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1949,7 +1973,7 @@ func embeddedPostgresURL(cfg config.Root) (string, error) {
|
||||
return fmt.Sprintf("postgres://coder@localhost:%s/coder?sslmode=disable&password=%s", pgPort, pgPassword), nil
|
||||
}
|
||||
|
||||
func startBuiltinPostgres(ctx context.Context, cfg config.Root, logger slog.Logger) (string, func() error, error) {
|
||||
func startBuiltinPostgres(ctx context.Context, cfg config.Root, logger slog.Logger, customCacheDir string) (string, func() error, error) {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
@@ -1976,6 +2000,10 @@ func startBuiltinPostgres(ctx context.Context, cfg config.Root, logger slog.Logg
|
||||
return "", nil, xerrors.Errorf("parse postgres port: %w", err)
|
||||
}
|
||||
|
||||
cachePath := filepath.Join(cfg.PostgresPath(), "cache")
|
||||
if customCacheDir != "" {
|
||||
cachePath = filepath.Join(customCacheDir, "postgres")
|
||||
}
|
||||
stdlibLogger := slog.Stdlib(ctx, logger.Named("postgres"), slog.LevelDebug)
|
||||
ep := embeddedpostgres.NewDatabase(
|
||||
embeddedpostgres.DefaultConfig().
|
||||
@@ -1983,7 +2011,7 @@ func startBuiltinPostgres(ctx context.Context, cfg config.Root, logger slog.Logg
|
||||
BinariesPath(filepath.Join(cfg.PostgresPath(), "bin")).
|
||||
DataPath(filepath.Join(cfg.PostgresPath(), "data")).
|
||||
RuntimePath(filepath.Join(cfg.PostgresPath(), "runtime")).
|
||||
CachePath(filepath.Join(cfg.PostgresPath(), "cache")).
|
||||
CachePath(cachePath).
|
||||
Username("coder").
|
||||
Password(pgPassword).
|
||||
Database("coder").
|
||||
|
||||
@@ -54,7 +54,7 @@ func (r *RootCmd) newCreateAdminUserCommand() *serpent.Command {
|
||||
|
||||
if newUserDBURL == "" {
|
||||
cliui.Infof(inv.Stdout, "Using built-in PostgreSQL (%s)", cfg.PostgresPath())
|
||||
url, closePg, err := startBuiltinPostgres(ctx, cfg, logger)
|
||||
url, closePg, err := startBuiltinPostgres(ctx, cfg, logger, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -177,6 +177,43 @@ func TestServer(t *testing.T) {
|
||||
return err == nil && rawURL != ""
|
||||
}, superDuperLong, testutil.IntervalFast, "failed to get access URL")
|
||||
})
|
||||
t.Run("EphemeralDeployment", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
inv, _ := clitest.New(t,
|
||||
"server",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "http://example.com",
|
||||
"--ephemeral",
|
||||
)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
||||
// Embedded postgres takes a while to fire up.
|
||||
const superDuperLong = testutil.WaitSuperLong * 3
|
||||
ctx, cancelFunc := context.WithCancel(testutil.Context(t, superDuperLong))
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- inv.WithContext(ctx).Run()
|
||||
}()
|
||||
pty.ExpectMatch("Using an ephemeral deployment directory")
|
||||
rootDirLine := pty.ReadLine(ctx)
|
||||
rootDir := strings.TrimPrefix(rootDirLine, "Using an ephemeral deployment directory")
|
||||
rootDir = strings.TrimSpace(rootDir)
|
||||
rootDir = strings.TrimPrefix(rootDir, "(")
|
||||
rootDir = strings.TrimSuffix(rootDir, ")")
|
||||
require.NotEmpty(t, rootDir)
|
||||
require.DirExists(t, rootDir)
|
||||
|
||||
pty.ExpectMatchContext(ctx, "View the Web UI")
|
||||
|
||||
cancelFunc()
|
||||
<-errCh
|
||||
|
||||
require.NoDirExists(t, rootDir)
|
||||
})
|
||||
t.Run("BuiltinPostgresURL", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
root, _ := clitest.New(t, "server", "postgres-builtin-url")
|
||||
|
||||
+4
@@ -446,6 +446,10 @@ cacheDir: [cache dir]
|
||||
# Controls whether data will be stored in an in-memory database.
|
||||
# (default: <unset>, type: bool)
|
||||
inMemoryDatabase: false
|
||||
# Controls whether Coder data, including built-in Postgres, will be stored in a
|
||||
# temporary directory and deleted when the server is stopped.
|
||||
# (default: <unset>, type: bool)
|
||||
ephemeralDeployment: false
|
||||
# Type of auth to use when connecting to postgres. For AWS RDS, using IAM
|
||||
# authentication (awsiamrds) is recommended.
|
||||
# (default: password, type: enum[password\|awsiamrds])
|
||||
|
||||
Generated
+3
@@ -10875,6 +10875,9 @@ const docTemplate = `{
|
||||
"enable_terraform_debug_mode": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ephemeral_deployment": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"experiments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
Generated
+3
@@ -9718,6 +9718,9 @@
|
||||
"enable_terraform_debug_mode": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ephemeral_deployment": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"experiments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
@@ -350,6 +350,7 @@ type DeploymentValues struct {
|
||||
ProxyTrustedOrigins serpent.StringArray `json:"proxy_trusted_origins,omitempty" typescript:",notnull"`
|
||||
CacheDir serpent.String `json:"cache_directory,omitempty" typescript:",notnull"`
|
||||
InMemoryDatabase serpent.Bool `json:"in_memory_database,omitempty" typescript:",notnull"`
|
||||
EphemeralDeployment serpent.Bool `json:"ephemeral_deployment,omitempty" typescript:",notnull"`
|
||||
PostgresURL serpent.String `json:"pg_connection_url,omitempty" typescript:",notnull"`
|
||||
PostgresAuth string `json:"pg_auth,omitempty" typescript:",notnull"`
|
||||
OAuth2 OAuth2Config `json:"oauth2,omitempty" typescript:",notnull"`
|
||||
@@ -2282,6 +2283,15 @@ func (c *DeploymentValues) Options() serpent.OptionSet {
|
||||
Value: &c.InMemoryDatabase,
|
||||
YAML: "inMemoryDatabase",
|
||||
},
|
||||
{
|
||||
Name: "Ephemeral Deployment",
|
||||
Description: "Controls whether Coder data, including built-in Postgres, will be stored in a temporary directory and deleted when the server is stopped.",
|
||||
Flag: "ephemeral",
|
||||
Env: "CODER_EPHEMERAL",
|
||||
Hidden: true,
|
||||
Value: &c.EphemeralDeployment,
|
||||
YAML: "ephemeralDeployment",
|
||||
},
|
||||
{
|
||||
Name: "Postgres Connection URL",
|
||||
Description: "URL of a PostgreSQL database. If empty, PostgreSQL binaries will be downloaded from Maven (https://repo1.maven.org/maven2) and store all data in the config root. Access the built-in database with \"coder server postgres-builtin-url\". Note that any special characters in the URL must be URL-encoded.",
|
||||
|
||||
Generated
+1
@@ -224,6 +224,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
|
||||
"user": {}
|
||||
},
|
||||
"enable_terraform_debug_mode": true,
|
||||
"ephemeral_deployment": true,
|
||||
"experiments": [
|
||||
"string"
|
||||
],
|
||||
|
||||
Generated
+3
@@ -1867,6 +1867,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
"user": {}
|
||||
},
|
||||
"enable_terraform_debug_mode": true,
|
||||
"ephemeral_deployment": true,
|
||||
"experiments": [
|
||||
"string"
|
||||
],
|
||||
@@ -2336,6 +2337,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
"user": {}
|
||||
},
|
||||
"enable_terraform_debug_mode": true,
|
||||
"ephemeral_deployment": true,
|
||||
"experiments": [
|
||||
"string"
|
||||
],
|
||||
@@ -2646,6 +2648,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
| `disable_path_apps` | boolean | false | | |
|
||||
| `docs_url` | [serpent.URL](#serpenturl) | false | | |
|
||||
| `enable_terraform_debug_mode` | boolean | false | | |
|
||||
| `ephemeral_deployment` | boolean | false | | |
|
||||
| `experiments` | array of string | false | | |
|
||||
| `external_auth` | [serpent.Struct-array_codersdk_ExternalAuthConfig](#serpentstruct-array_codersdk_externalauthconfig) | false | | |
|
||||
| `external_token_encryption_keys` | array of string | false | | |
|
||||
|
||||
Generated
+1
@@ -623,6 +623,7 @@ export interface DeploymentValues {
|
||||
readonly proxy_trusted_origins?: string;
|
||||
readonly cache_directory?: string;
|
||||
readonly in_memory_database?: boolean;
|
||||
readonly ephemeral_deployment?: boolean;
|
||||
readonly pg_connection_url?: string;
|
||||
readonly pg_auth?: string;
|
||||
readonly oauth2?: OAuth2Config;
|
||||
|
||||
Reference in New Issue
Block a user