From 0fb36ac73cc3ac3b2333246b5ad15bf878a722d9 Mon Sep 17 00:00:00 2001 From: carole-lavillonniere Date: Mon, 16 Feb 2026 16:54:54 +0100 Subject: [PATCH 1/5] add env vars struct --- cmd/root.go | 2 ++ env.example | 10 +++++++--- internal/api/client.go | 9 +++------ internal/auth/auth.go | 4 ++-- internal/auth/login.go | 10 ++-------- internal/auth/token_storage.go | 3 ++- internal/env/env.go | 33 +++++++++++++++++++++++++++++++++ test/integration/main_test.go | 1 + 8 files changed, 52 insertions(+), 20 deletions(-) create mode 100644 internal/env/env.go diff --git a/cmd/root.go b/cmd/root.go index 4b158a3..b5236f2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,6 +8,7 @@ import ( "github.com/localstack/lstk/internal/api" "github.com/localstack/lstk/internal/config" "github.com/localstack/lstk/internal/container" + "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/output" "github.com/localstack/lstk/internal/runtime" "github.com/localstack/lstk/internal/ui" @@ -27,6 +28,7 @@ var rootCmd = &cobra.Command{ if cmd.Name() == "version" { return nil } + env.Init() return config.Init() }, Run: func(cmd *cobra.Command, args []string) { diff --git a/env.example b/env.example index 53c7bba..942358d 100644 --- a/env.example +++ b/env.example @@ -1,7 +1,11 @@ +# Authentication token (optional - will trigger device flow login if not set) export LOCALSTACK_AUTH_TOKEN=ls-... -# Force file-based keyring backend (instead of system keychain) -# export KEYRING=file -# +# API endpoint (defaults to https://api.localstack.cloud) export LOCALSTACK_API_ENDPOINT=https://api.staging.aws.localstack.cloud + +# Web app URL (defaults to https://app.localstack.cloud) export LOCALSTACK_WEB_APP_URL=https://app.staging.aws.localstack.cloud + +# Force file-based keyring backend instead of system keychain (optional) +# export KEYRING=file diff --git a/internal/api/client.go b/internal/api/client.go index e352068..de817b0 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -7,8 +7,9 @@ import ( "fmt" "log" "net/http" - "os" "time" + + "github.com/localstack/lstk/internal/env" ) type PlatformAPI interface { @@ -65,12 +66,8 @@ type PlatformClient struct { } func NewPlatformClient() *PlatformClient { - baseURL := os.Getenv("LOCALSTACK_API_ENDPOINT") - if baseURL == "" { - baseURL = "https://api.localstack.cloud" - } return &PlatformClient{ - baseURL: baseURL, + baseURL: env.Vars.APIEndpoint, httpClient: &http.Client{Timeout: 30 * time.Second}, } } diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 3c957ee..9f56fe3 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -4,10 +4,10 @@ import ( "context" "errors" "fmt" - "os" "github.com/99designs/keyring" "github.com/localstack/lstk/internal/api" + "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/output" ) @@ -33,7 +33,7 @@ func (a *Auth) GetToken(ctx context.Context) (string, error) { return token, nil } - if token := os.Getenv("LOCALSTACK_AUTH_TOKEN"); token != "" { + if token := env.Vars.AuthToken; token != "" { return token, nil } diff --git a/internal/auth/login.go b/internal/auth/login.go index 8d7fa55..c6e4b56 100644 --- a/internal/auth/login.go +++ b/internal/auth/login.go @@ -5,15 +5,13 @@ package auth import ( "context" "fmt" - "os" "github.com/localstack/lstk/internal/api" + "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/output" "github.com/pkg/browser" ) -const webAppURL = "https://app.localstack.cloud" - type LoginProvider interface { Login(ctx context.Context) (string, error) } @@ -82,11 +80,7 @@ func (l *loginProvider) Login(ctx context.Context) (string, error) { } func getWebAppURL() string { - // allows overriding the URL for testing - if url := os.Getenv("LOCALSTACK_WEB_APP_URL"); url != "" { - return url - } - return webAppURL + return env.Vars.WebAppURL } func (l *loginProvider) completeAuth(ctx context.Context, authReq *api.AuthRequest) (string, error) { diff --git a/internal/auth/token_storage.go b/internal/auth/token_storage.go index f9d88d7..fbaa837 100644 --- a/internal/auth/token_storage.go +++ b/internal/auth/token_storage.go @@ -10,6 +10,7 @@ import ( "github.com/99designs/keyring" "github.com/localstack/lstk/internal/config" + "github.com/localstack/lstk/internal/env" ) const ( @@ -45,7 +46,7 @@ func NewTokenStorage() (AuthTokenStorage, error) { } // Force file backend if KEYRING env var is set to "file" - if os.Getenv("KEYRING") == "file" { + if env.Vars.Keyring == "file" { keyringConfig.AllowedBackends = []keyring.BackendType{keyring.FileBackend} } diff --git a/internal/env/env.go b/internal/env/env.go new file mode 100644 index 0000000..872ae08 --- /dev/null +++ b/internal/env/env.go @@ -0,0 +1,33 @@ +package env + +import ( + "strings" + + "github.com/spf13/viper" +) + +type Env struct { + AuthToken string + APIEndpoint string + WebAppURL string + Keyring string +} + +var Vars *Env + +// Init initializes environment variable configuration +func Init() { + viper.SetEnvPrefix("LOCALSTACK") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + + viper.SetDefault("api_endpoint", "https://api.localstack.cloud") + viper.SetDefault("web_app_url", "https://app.localstack.cloud") + + Vars = &Env{ + AuthToken: viper.GetString("auth_token"), + APIEndpoint: viper.GetString("api_endpoint"), + WebAppURL: viper.GetString("web_app_url"), + Keyring: viper.GetString("keyring"), + } +} diff --git a/test/integration/main_test.go b/test/integration/main_test.go index 9699cef..69bebb5 100644 --- a/test/integration/main_test.go +++ b/test/integration/main_test.go @@ -107,6 +107,7 @@ func requireDocker(t *testing.T) { t.Skip("Docker is not available") } // Skip Docker tests on Windows (GitHub Actions doesn't support Linux containers) + // Note: CI env var is not in config.GetEnv() as it's a standard CI environment variable if runtime.GOOS == "windows" && os.Getenv("CI") != "" { t.Skip("Docker tests not supported on Windows CI (nested virtualization not available)") } From cfc0f3cbd45d7fb182226ad6f5ed3f91e6454f4f Mon Sep 17 00:00:00 2001 From: carole-lavillonniere Date: Tue, 17 Feb 2026 13:00:16 +0100 Subject: [PATCH 2/5] centralize env vars in integ tests --- internal/auth/auth_test.go | 2 + internal/ui/run_login_test.go | 5 +++ test/integration/env/env.go | 68 ++++++++++++++++++++++++++++++++ test/integration/license_test.go | 16 ++------ test/integration/login_test.go | 15 ++----- test/integration/main_test.go | 23 ++--------- test/integration/start_test.go | 22 +++-------- 7 files changed, 92 insertions(+), 59 deletions(-) create mode 100644 test/integration/env/env.go diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go index 387660d..43462aa 100644 --- a/internal/auth/auth_test.go +++ b/internal/auth/auth_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/output" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -14,6 +15,7 @@ import ( func TestMain(m *testing.M) { _ = os.Unsetenv("LOCALSTACK_AUTH_TOKEN") + env.Init() m.Run() } diff --git a/internal/ui/run_login_test.go b/internal/ui/run_login_test.go index 19e73ca..10c1785 100644 --- a/internal/ui/run_login_test.go +++ b/internal/ui/run_login_test.go @@ -16,6 +16,7 @@ import ( "github.com/charmbracelet/x/exp/teatest" "github.com/localstack/lstk/internal/api" "github.com/localstack/lstk/internal/auth" + "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/output" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -84,6 +85,8 @@ func TestLoginFlow_DeviceFlowSuccess(t *testing.T) { defer mockServer.Close() t.Setenv("LOCALSTACK_API_ENDPOINT", mockServer.URL) + t.Setenv("LOCALSTACK_AUTH_TOKEN", "") + env.Init() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -140,6 +143,8 @@ func TestLoginFlow_DeviceFlowFailure_NotConfirmed(t *testing.T) { defer mockServer.Close() t.Setenv("LOCALSTACK_API_ENDPOINT", mockServer.URL) + t.Setenv("LOCALSTACK_AUTH_TOKEN", "") + env.Init() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() diff --git a/test/integration/env/env.go b/test/integration/env/env.go new file mode 100644 index 0000000..2afe168 --- /dev/null +++ b/test/integration/env/env.go @@ -0,0 +1,68 @@ +package env + +import ( + "os" + "strings" + "testing" +) + +// Key is a declared environment variable name. +type Key string + +const ( + AuthToken Key = "LOCALSTACK_AUTH_TOKEN" + APIEndpoint Key = "LOCALSTACK_API_ENDPOINT" + Keyring Key = "LOCALSTACK_KEYRING" + CI Key = "CI" +) + +// Get returns the value of the given environment variable. +func Get(key Key) string { + return os.Getenv(string(key)) +} + +// Require returns the value of the given environment variable, failing the test if it is not set. +func Require(t testing.TB, key Key) string { + t.Helper() + v := os.Getenv(string(key)) + if v == "" { + t.Fatalf("%s must be set to run this test", key) + } + return v +} + +// Environ is a slice of "KEY=value" environment variable strings. +type Environ []string + +// Without returns the current process environment excluding the given keys. +func Without(keys ...Key) Environ { + return Environ(os.Environ()).Without(keys...) +} + +// With returns the current process environment with key=value appended. +func With(key Key, value string) Environ { + return Environ(os.Environ()).With(key, value) +} + +// Without returns a copy of e excluding any variable whose key matches one of the given keys. +func (e Environ) Without(keys ...Key) Environ { + var result Environ + for _, entry := range e { + excluded := false + for _, key := range keys { + if strings.HasPrefix(entry, string(key)+"=") { + excluded = true + break + } + } + if !excluded { + result = append(result, entry) + } + } + return result +} + +// With returns a copy of e with key=value appended. +func (e Environ) With(key Key, value string) Environ { + return append(e, string(key)+"="+value) +} diff --git a/test/integration/license_test.go b/test/integration/license_test.go index 3cff9ec..a22232f 100644 --- a/test/integration/license_test.go +++ b/test/integration/license_test.go @@ -6,12 +6,12 @@ import ( "fmt" "net/http" "net/http/httptest" - "os" "os/exec" "testing" "time" "github.com/docker/docker/api/types/container" + "github.com/localstack/lstk/test/integration/env" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -20,8 +20,7 @@ const licenseContainerName = "localstack-aws" func TestLicenseValidationSuccess(t *testing.T) { requireDocker(t) - authToken := os.Getenv("LOCALSTACK_AUTH_TOKEN") - require.NotEmpty(t, authToken, "LOCALSTACK_AUTH_TOKEN must be set to run this test") + authToken := env.Require(t, env.AuthToken) cleanupLicense() t.Cleanup(cleanupLicense) @@ -63,10 +62,7 @@ func TestLicenseValidationSuccess(t *testing.T) { defer cancel() cmd := exec.CommandContext(ctx, binaryPath(), "start") - cmd.Env = append( - os.Environ(), - "LOCALSTACK_API_ENDPOINT="+mockServer.URL, - ) + cmd.Env = env.With(env.APIEndpoint, mockServer.URL) output, err := cmd.CombinedOutput() // Check for validation errors from handler @@ -95,11 +91,7 @@ func TestLicenseValidationFailure(t *testing.T) { defer cancel() cmd := exec.CommandContext(ctx, binaryPath(), "start") - cmd.Env = append( - os.Environ(), - "LOCALSTACK_API_ENDPOINT="+mockServer.URL, - "LOCALSTACK_AUTH_TOKEN=test-token-for-license-validation", - ) + cmd.Env = env.With(env.APIEndpoint, mockServer.URL).With(env.AuthToken, "test-token-for-license-validation") output, err := cmd.CombinedOutput() require.Error(t, err, "expected lstk start to fail with forbidden license") diff --git a/test/integration/login_test.go b/test/integration/login_test.go index efa18f6..bed7dc2 100644 --- a/test/integration/login_test.go +++ b/test/integration/login_test.go @@ -8,13 +8,13 @@ import ( "io" "net/http" "net/http/httptest" - "os" "os/exec" "runtime" "testing" "time" "github.com/creack/pty" + "github.com/localstack/lstk/test/integration/env" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -77,8 +77,7 @@ func TestDeviceFlowSuccess(t *testing.T) { t.Cleanup(cleanup) // Require valid token from environment - licenseToken := os.Getenv("LOCALSTACK_AUTH_TOKEN") - require.NotEmpty(t, licenseToken, "LOCALSTACK_AUTH_TOKEN must be set to run this test") + licenseToken := env.Require(t, env.AuthToken) // Create mock API server that returns the real token mockServer := createMockAPIServer(t, licenseToken, true) @@ -88,10 +87,7 @@ func TestDeviceFlowSuccess(t *testing.T) { defer cancel() cmd := exec.CommandContext(ctx, binaryPath(), "login") - cmd.Env = append( - envWithout("LOCALSTACK_AUTH_TOKEN"), - "LOCALSTACK_API_ENDPOINT="+mockServer.URL, - ) + cmd.Env = env.Without(env.AuthToken).With(env.APIEndpoint, mockServer.URL) ptmx, err := pty.Start(cmd) require.NoError(t, err, "failed to start command in PTY") @@ -153,10 +149,7 @@ func TestDeviceFlowFailure_RequestNotConfirmed(t *testing.T) { defer cancel() cmd := exec.CommandContext(ctx, binaryPath(), "login") - cmd.Env = append( - envWithout("LOCALSTACK_AUTH_TOKEN"), - "LOCALSTACK_API_ENDPOINT="+mockServer.URL, - ) + cmd.Env = env.Without(env.AuthToken).With(env.APIEndpoint, mockServer.URL) ptmx, err := pty.Start(cmd) require.NoError(t, err, "failed to start command in PTY") diff --git a/test/integration/main_test.go b/test/integration/main_test.go index 69bebb5..d0993fa 100644 --- a/test/integration/main_test.go +++ b/test/integration/main_test.go @@ -10,12 +10,12 @@ import ( "os" "path/filepath" "runtime" - "strings" "sync" "testing" "github.com/99designs/keyring" "github.com/docker/docker/client" + "github.com/localstack/lstk/test/integration/env" ) // syncBuffer is a thread-safe buffer for concurrent read/write access. @@ -88,7 +88,7 @@ func TestMain(m *testing.M) { } // Force file backend if KEYRING env var is set to "file" - if os.Getenv("KEYRING") == "file" { + if env.Get(env.Keyring) == "file" { keyringConfig.AllowedBackends = []keyring.BackendType{keyring.FileBackend} } @@ -108,28 +108,11 @@ func requireDocker(t *testing.T) { } // Skip Docker tests on Windows (GitHub Actions doesn't support Linux containers) // Note: CI env var is not in config.GetEnv() as it's a standard CI environment variable - if runtime.GOOS == "windows" && os.Getenv("CI") != "" { + if runtime.GOOS == "windows" && env.Get(env.CI) != "" { t.Skip("Docker tests not supported on Windows CI (nested virtualization not available)") } } -func envWithout(keys ...string) []string { - var env []string - for _, e := range os.Environ() { - excluded := false - for _, key := range keys { - if strings.HasPrefix(e, key+"=") { - excluded = true - break - } - } - if !excluded { - env = append(env, e) - } - } - return env -} - func GetAuthTokenFromKeyring() (string, error) { item, err := ring.Get(keyringAuthTokenKey) if err != nil { diff --git a/test/integration/start_test.go b/test/integration/start_test.go index 55a1fd5..e86128b 100644 --- a/test/integration/start_test.go +++ b/test/integration/start_test.go @@ -2,12 +2,12 @@ package integration_test import ( "context" - "os" "os/exec" "testing" "time" "github.com/docker/docker/api/types/container" + "github.com/localstack/lstk/test/integration/env" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -16,8 +16,7 @@ const containerName = "localstack-aws" func TestStartCommandSucceedsWithValidToken(t *testing.T) { requireDocker(t) - authToken := os.Getenv("LOCALSTACK_AUTH_TOKEN") - require.NotEmpty(t, authToken, "LOCALSTACK_AUTH_TOKEN must be set to run this test") + _ = env.Require(t, env.AuthToken) cleanup() t.Cleanup(cleanup) @@ -29,9 +28,7 @@ func TestStartCommandSucceedsWithValidToken(t *testing.T) { defer cancel() cmd := exec.CommandContext(ctx, binaryPath(), "start") - cmd.Env = append(os.Environ(), - "LOCALSTACK_API_ENDPOINT="+mockServer.URL, - ) + cmd.Env = env.With(env.APIEndpoint, mockServer.URL) output, err := cmd.CombinedOutput() require.NoError(t, err, "lstk start failed: %s", output) @@ -47,8 +44,7 @@ func TestStartCommandSucceedsWithKeyringToken(t *testing.T) { t.Cleanup(cleanup) // Store token in keyring before running command - authToken := os.Getenv("LOCALSTACK_AUTH_TOKEN") - require.NotEmpty(t, authToken, "LOCALSTACK_AUTH_TOKEN must be set to run this test") + authToken := env.Require(t, env.AuthToken) err := SetAuthTokenInKeyring(authToken) require.NoError(t, err, "failed to store token in keyring") @@ -60,10 +56,7 @@ func TestStartCommandSucceedsWithKeyringToken(t *testing.T) { // Run without LOCALSTACK_AUTH_TOKEN should use keyring cmd := exec.CommandContext(ctx, binaryPath(), "start") - cmd.Env = append( - envWithout("LOCALSTACK_AUTH_TOKEN"), - "LOCALSTACK_API_ENDPOINT="+mockServer.URL, - ) + cmd.Env = env.Without(env.AuthToken).With(env.APIEndpoint, mockServer.URL) output, err := cmd.CombinedOutput() require.NoError(t, err, "lstk start failed: %s", output) @@ -85,10 +78,7 @@ func TestStartCommandFailsWithInvalidToken(t *testing.T) { defer cancel() cmd := exec.CommandContext(ctx, binaryPath(), "start") - cmd.Env = append(os.Environ(), - "LOCALSTACK_AUTH_TOKEN=invalid-token", - "LOCALSTACK_API_ENDPOINT="+mockServer.URL, - ) + cmd.Env = env.With(env.AuthToken, "invalid-token").With(env.APIEndpoint, mockServer.URL) output, err := cmd.CombinedOutput() require.Error(t, err, "expected lstk start to fail with invalid token") From 12a18d086a40cda94bf33f04eac7ff7a2a01068d Mon Sep 17 00:00:00 2001 From: carole-lavillonniere Date: Tue, 17 Feb 2026 15:17:12 +0100 Subject: [PATCH 3/5] address comments --- Makefile | 2 +- env.example | 2 +- internal/auth/auth_test.go | 2 -- internal/auth/token_storage.go | 2 +- internal/env/env.go | 2 +- test/integration/main_test.go | 2 +- 6 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 2659176..0e64187 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ test: test-integration: $(BUILD_DIR)/$(BINARY_NAME) @JUNIT=""; [ -n "$$CREATE_JUNIT_REPORT" ] && JUNIT="--junitfile ../../test-integration-results.xml"; \ if [ "$$(uname)" = "Darwin" ]; then \ - cd test/integration && KEYRING=file go run gotest.tools/gotestsum@latest --format testdox $$JUNIT -- -count=1 ./...; \ + cd test/integration && LOCALSTACK_KEYRING=file go run gotest.tools/gotestsum@latest --format testdox $$JUNIT -- -count=1 ./...; \ else \ cd test/integration && go run gotest.tools/gotestsum@latest --format testdox $$JUNIT -- -count=1 ./...; \ fi diff --git a/env.example b/env.example index 942358d..4b900ab 100644 --- a/env.example +++ b/env.example @@ -8,4 +8,4 @@ export LOCALSTACK_API_ENDPOINT=https://api.staging.aws.localstack.cloud export LOCALSTACK_WEB_APP_URL=https://app.staging.aws.localstack.cloud # Force file-based keyring backend instead of system keychain (optional) -# export KEYRING=file +# export LOCALSTACK_KEYRING=file diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go index 43462aa..387660d 100644 --- a/internal/auth/auth_test.go +++ b/internal/auth/auth_test.go @@ -7,7 +7,6 @@ import ( "strings" "testing" - "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/output" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -15,7 +14,6 @@ import ( func TestMain(m *testing.M) { _ = os.Unsetenv("LOCALSTACK_AUTH_TOKEN") - env.Init() m.Run() } diff --git a/internal/auth/token_storage.go b/internal/auth/token_storage.go index fbaa837..c9956b4 100644 --- a/internal/auth/token_storage.go +++ b/internal/auth/token_storage.go @@ -45,7 +45,7 @@ func NewTokenStorage() (AuthTokenStorage, error) { }, } - // Force file backend if KEYRING env var is set to "file" + // Force file backend if LOCALSTACK_KEYRING env var is set to "file" if env.Vars.Keyring == "file" { keyringConfig.AllowedBackends = []keyring.BackendType{keyring.FileBackend} } diff --git a/internal/env/env.go b/internal/env/env.go index 872ae08..5e4c8d9 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -13,7 +13,7 @@ type Env struct { Keyring string } -var Vars *Env +var Vars = &Env{} // Init initializes environment variable configuration func Init() { diff --git a/test/integration/main_test.go b/test/integration/main_test.go index d0993fa..be44016 100644 --- a/test/integration/main_test.go +++ b/test/integration/main_test.go @@ -87,7 +87,7 @@ func TestMain(m *testing.M) { }, } - // Force file backend if KEYRING env var is set to "file" + // Force file backend if LOCALSTACK_KEYRING env var is set to "file" if env.Get(env.Keyring) == "file" { keyringConfig.AllowedBackends = []keyring.BackendType{keyring.FileBackend} } From ab4e8ed094e8f5c571b16ccabf10016393e86c48 Mon Sep 17 00:00:00 2001 From: carole-lavillonniere Date: Wed, 18 Feb 2026 11:49:40 +0100 Subject: [PATCH 4/5] remove comments --- test/integration/env/env.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/integration/env/env.go b/test/integration/env/env.go index 2afe168..eacccc2 100644 --- a/test/integration/env/env.go +++ b/test/integration/env/env.go @@ -6,7 +6,6 @@ import ( "testing" ) -// Key is a declared environment variable name. type Key string const ( @@ -16,12 +15,10 @@ const ( CI Key = "CI" ) -// Get returns the value of the given environment variable. func Get(key Key) string { return os.Getenv(string(key)) } -// Require returns the value of the given environment variable, failing the test if it is not set. func Require(t testing.TB, key Key) string { t.Helper() v := os.Getenv(string(key)) @@ -31,20 +28,16 @@ func Require(t testing.TB, key Key) string { return v } -// Environ is a slice of "KEY=value" environment variable strings. type Environ []string -// Without returns the current process environment excluding the given keys. func Without(keys ...Key) Environ { return Environ(os.Environ()).Without(keys...) } -// With returns the current process environment with key=value appended. func With(key Key, value string) Environ { return Environ(os.Environ()).With(key, value) } -// Without returns a copy of e excluding any variable whose key matches one of the given keys. func (e Environ) Without(keys ...Key) Environ { var result Environ for _, entry := range e { @@ -62,7 +55,6 @@ func (e Environ) Without(keys ...Key) Environ { return result } -// With returns a copy of e with key=value appended. func (e Environ) With(key Key, value string) Environ { return append(e, string(key)+"="+value) } From 7a2457c33e19bc3b279d19326acf4efbf081b88a Mon Sep 17 00:00:00 2001 From: carole-lavillonniere Date: Thu, 19 Feb 2026 10:19:33 +0100 Subject: [PATCH 5/5] rename LOCALSTACK_ env vars to LSTK_ --- .github/workflows/ci.yml | 2 +- Makefile | 2 +- env.example | 8 ++++---- internal/auth/auth.go | 4 ++-- internal/auth/auth_test.go | 2 +- internal/auth/token_storage.go | 2 +- internal/env/env.go | 2 +- internal/ui/run_login_test.go | 8 ++++---- test/integration/env/env.go | 6 +++--- test/integration/main_test.go | 2 +- test/integration/start_test.go | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6fb6d7..8d4617b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,7 +102,7 @@ jobs: run: make test-integration env: CREATE_JUNIT_REPORT: "true" - LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }} + LSTK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }} - name: Upload test results uses: actions/upload-artifact@v6 diff --git a/Makefile b/Makefile index 0e64187..6d8cdb2 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ test: test-integration: $(BUILD_DIR)/$(BINARY_NAME) @JUNIT=""; [ -n "$$CREATE_JUNIT_REPORT" ] && JUNIT="--junitfile ../../test-integration-results.xml"; \ if [ "$$(uname)" = "Darwin" ]; then \ - cd test/integration && LOCALSTACK_KEYRING=file go run gotest.tools/gotestsum@latest --format testdox $$JUNIT -- -count=1 ./...; \ + cd test/integration && LSTK_KEYRING=file go run gotest.tools/gotestsum@latest --format testdox $$JUNIT -- -count=1 ./...; \ else \ cd test/integration && go run gotest.tools/gotestsum@latest --format testdox $$JUNIT -- -count=1 ./...; \ fi diff --git a/env.example b/env.example index 4b900ab..4b55426 100644 --- a/env.example +++ b/env.example @@ -1,11 +1,11 @@ # Authentication token (optional - will trigger device flow login if not set) -export LOCALSTACK_AUTH_TOKEN=ls-... +export LSTK_AUTH_TOKEN=ls-... # API endpoint (defaults to https://api.localstack.cloud) -export LOCALSTACK_API_ENDPOINT=https://api.staging.aws.localstack.cloud +export LSTK_API_ENDPOINT=https://api.staging.aws.localstack.cloud # Web app URL (defaults to https://app.localstack.cloud) -export LOCALSTACK_WEB_APP_URL=https://app.staging.aws.localstack.cloud +export LSTK_WEB_APP_URL=https://app.staging.aws.localstack.cloud # Force file-based keyring backend instead of system keychain (optional) -# export LOCALSTACK_KEYRING=file +# export LSTK_KEYRING=file diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 9f56fe3..fb41c46 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -27,7 +27,7 @@ func New(sink output.Sink, platform api.PlatformAPI, storage AuthTokenStorage, a } } -// GetToken tries in order: 1) keyring 2) LOCALSTACK_AUTH_TOKEN env var 3) device flow login +// GetToken tries in order: 1) keyring 2) LSTK_AUTH_TOKEN env var 3) device flow login func (a *Auth) GetToken(ctx context.Context) (string, error) { if token, err := a.tokenStorage.GetAuthToken(); err == nil && token != "" { return token, nil @@ -38,7 +38,7 @@ func (a *Auth) GetToken(ctx context.Context) (string, error) { } if !a.allowLogin { - return "", fmt.Errorf("authentication required: set LOCALSTACK_AUTH_TOKEN or run in interactive mode") + return "", fmt.Errorf("authentication required: set LSTK_AUTH_TOKEN or run in interactive mode") } output.EmitLog(a.sink, "No existing credentials found. Please log in:") diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go index 387660d..81145f6 100644 --- a/internal/auth/auth_test.go +++ b/internal/auth/auth_test.go @@ -13,7 +13,7 @@ import ( ) func TestMain(m *testing.M) { - _ = os.Unsetenv("LOCALSTACK_AUTH_TOKEN") + _ = os.Unsetenv("LSTK_AUTH_TOKEN") m.Run() } diff --git a/internal/auth/token_storage.go b/internal/auth/token_storage.go index c9956b4..afaa249 100644 --- a/internal/auth/token_storage.go +++ b/internal/auth/token_storage.go @@ -45,7 +45,7 @@ func NewTokenStorage() (AuthTokenStorage, error) { }, } - // Force file backend if LOCALSTACK_KEYRING env var is set to "file" + // Force file backend if LSTK_KEYRING env var is set to "file" if env.Vars.Keyring == "file" { keyringConfig.AllowedBackends = []keyring.BackendType{keyring.FileBackend} } diff --git a/internal/env/env.go b/internal/env/env.go index 5e4c8d9..de0736c 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -17,7 +17,7 @@ var Vars = &Env{} // Init initializes environment variable configuration func Init() { - viper.SetEnvPrefix("LOCALSTACK") + viper.SetEnvPrefix("LSTK") viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() diff --git a/internal/ui/run_login_test.go b/internal/ui/run_login_test.go index 10c1785..d3f3635 100644 --- a/internal/ui/run_login_test.go +++ b/internal/ui/run_login_test.go @@ -84,8 +84,8 @@ func TestLoginFlow_DeviceFlowSuccess(t *testing.T) { mockServer := createMockAPIServer(t, "test-license-token", true) defer mockServer.Close() - t.Setenv("LOCALSTACK_API_ENDPOINT", mockServer.URL) - t.Setenv("LOCALSTACK_AUTH_TOKEN", "") + t.Setenv("LSTK_API_ENDPOINT", mockServer.URL) + t.Setenv("LSTK_AUTH_TOKEN", "") env.Init() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -142,8 +142,8 @@ func TestLoginFlow_DeviceFlowFailure_NotConfirmed(t *testing.T) { mockServer := createMockAPIServer(t, "", false) defer mockServer.Close() - t.Setenv("LOCALSTACK_API_ENDPOINT", mockServer.URL) - t.Setenv("LOCALSTACK_AUTH_TOKEN", "") + t.Setenv("LSTK_API_ENDPOINT", mockServer.URL) + t.Setenv("LSTK_AUTH_TOKEN", "") env.Init() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) diff --git a/test/integration/env/env.go b/test/integration/env/env.go index eacccc2..33e330c 100644 --- a/test/integration/env/env.go +++ b/test/integration/env/env.go @@ -9,9 +9,9 @@ import ( type Key string const ( - AuthToken Key = "LOCALSTACK_AUTH_TOKEN" - APIEndpoint Key = "LOCALSTACK_API_ENDPOINT" - Keyring Key = "LOCALSTACK_KEYRING" + AuthToken Key = "LSTK_AUTH_TOKEN" + APIEndpoint Key = "LSTK_API_ENDPOINT" + Keyring Key = "LSTK_KEYRING" CI Key = "CI" ) diff --git a/test/integration/main_test.go b/test/integration/main_test.go index be44016..82c9a4e 100644 --- a/test/integration/main_test.go +++ b/test/integration/main_test.go @@ -87,7 +87,7 @@ func TestMain(m *testing.M) { }, } - // Force file backend if LOCALSTACK_KEYRING env var is set to "file" + // Force file backend if LSTK_KEYRING env var is set to "file" if env.Get(env.Keyring) == "file" { keyringConfig.AllowedBackends = []keyring.BackendType{keyring.FileBackend} } diff --git a/test/integration/start_test.go b/test/integration/start_test.go index e86128b..2826ab4 100644 --- a/test/integration/start_test.go +++ b/test/integration/start_test.go @@ -54,7 +54,7 @@ func TestStartCommandSucceedsWithKeyringToken(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() - // Run without LOCALSTACK_AUTH_TOKEN should use keyring + // Run without LSTK_AUTH_TOKEN should use keyring cmd := exec.CommandContext(ctx, binaryPath(), "start") cmd.Env = env.Without(env.AuthToken).With(env.APIEndpoint, mockServer.URL) output, err := cmd.CombinedOutput()