diff --git a/CLAUDE.md b/CLAUDE.md index e036471..ad7d48f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,6 +57,7 @@ Environment variables: - Do not remove comments added by someone else than yourself. - Errors returned by functions should always be checked unless in test files. - Terminology: in user-facing CLI/help/docs, prefer `emulator` over `container`/`runtime`; use `container`/`runtime` only for internal implementation details. +- Avoid package-level global variables. Use constructor functions that return fresh instances and inject dependencies explicitly. This keeps packages testable in isolation and prevents shared mutable state between tests. # Testing diff --git a/cmd/config.go b/cmd/config.go index a18ccae..3e1ab3b 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -7,25 +7,26 @@ import ( "github.com/spf13/cobra" ) -var configCmd = &cobra.Command{ - Use: "config", - Short: "Manage configuration", +func newConfigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "config", + Short: "Manage configuration", + } + cmd.AddCommand(newConfigPathCmd()) + return cmd } -var configPathCmd = &cobra.Command{ - Use: "path", - Short: "Print the configuration file path", - RunE: func(cmd *cobra.Command, args []string) error { - configPath, err := config.ConfigFilePath() - if err != nil { +func newConfigPathCmd() *cobra.Command { + return &cobra.Command{ + Use: "path", + Short: "Print the configuration file path", + RunE: func(cmd *cobra.Command, args []string) error { + configPath, err := config.ConfigFilePath() + if err != nil { + return err + } + _, err = fmt.Fprintln(cmd.OutOrStdout(), configPath) return err - } - _, err = fmt.Fprintln(cmd.OutOrStdout(), configPath) - return err - }, -} - -func init() { - configCmd.AddCommand(configPathCmd) - rootCmd.AddCommand(configCmd) + }, + } } diff --git a/cmd/help_test.go b/cmd/help_test.go index 3b21602..79b9354 100644 --- a/cmd/help_test.go +++ b/cmd/help_test.go @@ -6,47 +6,18 @@ import ( "strings" "testing" - "github.com/spf13/cobra" - "github.com/spf13/pflag" + "github.com/localstack/lstk/internal/env" ) func executeWithArgs(t *testing.T, args ...string) (string, error) { t.Helper() - - origOut := rootCmd.OutOrStdout() - origErr := rootCmd.ErrOrStderr() - resetCommandState(rootCmd) - buf := new(bytes.Buffer) - rootCmd.SetOut(buf) - rootCmd.SetErr(buf) - rootCmd.SetArgs(args) - - err := rootCmd.ExecuteContext(context.Background()) - out := buf.String() - - rootCmd.SetArgs(nil) - rootCmd.SetOut(origOut) - rootCmd.SetErr(origErr) - resetCommandState(rootCmd) - - return out, err -} - -func resetCommandState(cmd *cobra.Command) { - resetFlagSet(cmd.Flags()) - resetFlagSet(cmd.PersistentFlags()) - - for _, sub := range cmd.Commands() { - resetCommandState(sub) - } -} - -func resetFlagSet(flags *pflag.FlagSet) { - flags.VisitAll(func(flag *pflag.Flag) { - _ = flag.Value.Set(flag.DefValue) - flag.Changed = false - }) + cmd := NewRootCmd(&env.Env{}) + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.SetArgs(args) + err := cmd.ExecuteContext(context.Background()) + return buf.String(), err } func TestRootHelpOutputTemplate(t *testing.T) { diff --git a/cmd/login.go b/cmd/login.go index 251a684..948ec14 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -4,25 +4,24 @@ import ( "fmt" "github.com/localstack/lstk/internal/api" + "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/ui" "github.com/localstack/lstk/internal/version" "github.com/spf13/cobra" ) -var loginCmd = &cobra.Command{ - Use: "login", - Short: "Manage login", - Long: "Manage login and store credentials in system keyring", - PreRunE: initConfig, - RunE: func(cmd *cobra.Command, args []string) error { - if !ui.IsInteractive() { - return fmt.Errorf("login requires an interactive terminal") - } - platformClient := api.NewPlatformClient() - return ui.RunLogin(cmd.Context(), version.Version(), platformClient) - }, -} - -func init() { - rootCmd.AddCommand(loginCmd) +func newLoginCmd(cfg *env.Env) *cobra.Command { + return &cobra.Command{ + Use: "login", + Short: "Manage login", + Long: "Manage login and store credentials in system keyring", + PreRunE: initConfig, + RunE: func(cmd *cobra.Command, args []string) error { + if !ui.IsInteractive() { + return fmt.Errorf("login requires an interactive terminal") + } + platformClient := api.NewPlatformClient(cfg.APIEndpoint) + return ui.RunLogin(cmd.Context(), version.Version(), platformClient, cfg.AuthToken, cfg.ForceFileKeyring, cfg.WebAppURL) + }, + } } diff --git a/cmd/logout.go b/cmd/logout.go index fcb54f8..db3fa55 100644 --- a/cmd/logout.go +++ b/cmd/logout.go @@ -7,37 +7,36 @@ import ( "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/localstack/lstk/internal/ui" "github.com/spf13/cobra" ) -var logoutCmd = &cobra.Command{ - Use: "logout", - Short: "Remove stored authentication credentials", - PreRunE: initConfig, - RunE: func(cmd *cobra.Command, args []string) error { - if ui.IsInteractive() { - return ui.RunLogout(cmd.Context()) - } - - sink := output.NewPlainSink(os.Stdout) - platformClient := api.NewPlatformClient() - tokenStorage, err := auth.NewTokenStorage() - if err != nil { - return fmt.Errorf("failed to initialize token storage: %w", err) - } - a := auth.New(sink, platformClient, tokenStorage, false) - if err := a.Logout(); err != nil { - if errors.Is(err, auth.ErrNotLoggedIn) { - return nil +func newLogoutCmd(cfg *env.Env) *cobra.Command { + return &cobra.Command{ + Use: "logout", + Short: "Remove stored authentication credentials", + PreRunE: initConfig, + RunE: func(cmd *cobra.Command, args []string) error { + platformClient := api.NewPlatformClient(cfg.APIEndpoint) + if ui.IsInteractive() { + return ui.RunLogout(cmd.Context(), platformClient, cfg.AuthToken, cfg.ForceFileKeyring) } - return fmt.Errorf("failed to logout: %w", err) - } - return nil - }, -} -func init() { - rootCmd.AddCommand(logoutCmd) + sink := output.NewPlainSink(os.Stdout) + tokenStorage, err := auth.NewTokenStorage(cfg.ForceFileKeyring) + if err != nil { + return fmt.Errorf("failed to initialize token storage: %w", err) + } + a := auth.New(sink, platformClient, tokenStorage, cfg.AuthToken, "", false) + if err := a.Logout(); err != nil { + if errors.Is(err, auth.ErrNotLoggedIn) { + return nil + } + return fmt.Errorf("failed to logout: %w", err) + } + return nil + }, + } } diff --git a/cmd/logs.go b/cmd/logs.go index 5c194fe..5cfc2c6 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -9,25 +9,24 @@ import ( "github.com/spf13/cobra" ) -var logsCmd = &cobra.Command{ - Use: "logs", - Short: "Show emulator logs", - Long: "Show logs from the emulator. Use --follow to stream in real-time.", - PreRunE: initConfig, - RunE: func(cmd *cobra.Command, args []string) error { - follow, err := cmd.Flags().GetBool("follow") - if err != nil { - return err - } - rt, err := runtime.NewDockerRuntime() - if err != nil { - return err - } - return container.Logs(cmd.Context(), rt, output.NewPlainSink(os.Stdout), follow) - }, -} - -func init() { - rootCmd.AddCommand(logsCmd) - logsCmd.Flags().BoolP("follow", "f", false, "Follow log output") +func newLogsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "logs", + Short: "Show emulator logs", + Long: "Show logs from the emulator. Use --follow to stream in real-time.", + PreRunE: initConfig, + RunE: func(cmd *cobra.Command, args []string) error { + follow, err := cmd.Flags().GetBool("follow") + if err != nil { + return err + } + rt, err := runtime.NewDockerRuntime() + if err != nil { + return err + } + return container.Logs(cmd.Context(), rt, output.NewPlainSink(os.Stdout), follow) + }, + } + cmd.Flags().BoolP("follow", "f", false, "Follow log output") + return cmd } diff --git a/cmd/root.go b/cmd/root.go index 3d06b8a..2a825a2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,51 +16,62 @@ import ( "github.com/spf13/cobra" ) -var rootCmd = &cobra.Command{ - Use: "lstk", - Short: "LocalStack CLI", - Long: "lstk is the command-line interface for LocalStack.", - PreRunE: initConfig, - Run: func(cmd *cobra.Command, args []string) { - rt, err := runtime.NewDockerRuntime() - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - - if err := runStart(cmd.Context(), rt); err != nil { - if !output.IsSilent(err) { +func NewRootCmd(cfg *env.Env) *cobra.Command { + root := &cobra.Command{ + Use: "lstk", + Short: "LocalStack CLI", + Long: "lstk is the command-line interface for LocalStack.", + PreRunE: initConfig, + Run: func(cmd *cobra.Command, args []string) { + rt, err := runtime.NewDockerRuntime() + if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) } - os.Exit(1) - } - }, -} -func init() { - rootCmd.Version = version.Version() - rootCmd.SetVersionTemplate(versionLine() + "\n") + if err := runStart(cmd.Context(), rt, cfg); err != nil { + if !output.IsSilent(err) { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + } + os.Exit(1) + } + }, + } + + root.Version = version.Version() + root.SetVersionTemplate(versionLine() + "\n") + + configureHelp(root) + + root.InitDefaultVersionFlag() + root.Flags().Lookup("version").Usage = "Show version" - configureHelp(rootCmd) + root.AddCommand( + newStartCmd(cfg), + newStopCmd(), + newLoginCmd(cfg), + newLogoutCmd(cfg), + newLogsCmd(), + newConfigCmd(), + newVersionCmd(), + ) - rootCmd.InitDefaultVersionFlag() - rootCmd.Flags().Lookup("version").Usage = "Show version" - rootCmd.AddCommand(startCmd) + return root } func Execute(ctx context.Context) error { - return rootCmd.ExecuteContext(ctx) + cfg := env.Init() + return NewRootCmd(cfg).ExecuteContext(ctx) } -func runStart(ctx context.Context, rt runtime.Runtime) error { - platformClient := api.NewPlatformClient() +func runStart(ctx context.Context, rt runtime.Runtime, cfg *env.Env) error { + platformClient := api.NewPlatformClient(cfg.APIEndpoint) if ui.IsInteractive() { - return ui.Run(ctx, rt, version.Version(), platformClient) + return ui.Run(ctx, rt, version.Version(), platformClient, cfg.AuthToken, cfg.ForceFileKeyring, cfg.WebAppURL) } - return container.Start(ctx, rt, output.NewPlainSink(os.Stdout), platformClient, false) + return container.Start(ctx, rt, output.NewPlainSink(os.Stdout), platformClient, cfg.AuthToken, cfg.ForceFileKeyring, cfg.WebAppURL, false) } func initConfig(_ *cobra.Command, _ []string) error { - env.Init() return config.Init() } diff --git a/cmd/start.go b/cmd/start.go index 748b5c1..342bdbd 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -4,28 +4,31 @@ import ( "fmt" "os" + "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/output" "github.com/localstack/lstk/internal/runtime" "github.com/spf13/cobra" ) -var startCmd = &cobra.Command{ - Use: "start", - Short: "Start emulator", - Long: "Start emulator and services.", - PreRunE: initConfig, - Run: func(cmd *cobra.Command, args []string) { - rt, err := runtime.NewDockerRuntime() - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } - - if err := runStart(cmd.Context(), rt); err != nil { - if !output.IsSilent(err) { +func newStartCmd(cfg *env.Env) *cobra.Command { + return &cobra.Command{ + Use: "start", + Short: "Start emulator", + Long: "Start emulator and services.", + PreRunE: initConfig, + Run: func(cmd *cobra.Command, args []string) { + rt, err := runtime.NewDockerRuntime() + if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + if err := runStart(cmd.Context(), rt, cfg); err != nil { + if !output.IsSilent(err) { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + } + os.Exit(1) } - os.Exit(1) - } - }, + }, + } } diff --git a/cmd/stop.go b/cmd/stop.go index 50e655d..aaa5c4d 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -9,29 +9,27 @@ import ( "github.com/spf13/cobra" ) -var stopCmd = &cobra.Command{ - Use: "stop", - Short: "Stop emulator", - Long: "Stop emulator and services", - PreRunE: initConfig, - Run: func(cmd *cobra.Command, args []string) { - rt, err := runtime.NewDockerRuntime() - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } +func newStopCmd() *cobra.Command { + return &cobra.Command{ + Use: "stop", + Short: "Stop emulator", + Long: "Stop emulator and services", + PreRunE: initConfig, + Run: func(cmd *cobra.Command, args []string) { + rt, err := runtime.NewDockerRuntime() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } - onProgress := func(msg string) { - fmt.Println(msg) - } + onProgress := func(msg string) { + fmt.Println(msg) + } - if err := container.Stop(cmd.Context(), rt, onProgress); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - }, -} - -func init() { - rootCmd.AddCommand(stopCmd) + if err := container.Stop(cmd.Context(), rt, onProgress); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + }, + } } diff --git a/cmd/version.go b/cmd/version.go index 2f2405f..344fb3f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -7,18 +7,16 @@ import ( "github.com/spf13/cobra" ) -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Show version", - Long: "Print version information for the lstk binary.", - RunE: func(cmd *cobra.Command, args []string) error { - _, err := fmt.Fprintln(cmd.OutOrStdout(), versionLine()) - return err - }, -} - -func init() { - rootCmd.AddCommand(versionCmd) +func newVersionCmd() *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Show version", + Long: "Print version information for the lstk binary.", + RunE: func(cmd *cobra.Command, args []string) error { + _, err := fmt.Fprintln(cmd.OutOrStdout(), versionLine()) + return err + }, + } } func versionLine() string { diff --git a/internal/api/client.go b/internal/api/client.go index 8ba9bf5..b1b078a 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -9,7 +9,6 @@ import ( "net/http" "time" - "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/version" ) @@ -68,9 +67,9 @@ type PlatformClient struct { httpClient *http.Client } -func NewPlatformClient() *PlatformClient { +func NewPlatformClient(apiEndpoint string) *PlatformClient { return &PlatformClient{ - baseURL: env.Vars.APIEndpoint, + baseURL: apiEndpoint, httpClient: &http.Client{Timeout: 30 * time.Second}, } } diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 835d402..a22a37f 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/localstack/lstk/internal/api" - "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/output" ) @@ -16,14 +15,16 @@ type Auth struct { tokenStorage AuthTokenStorage login LoginProvider sink output.Sink + authToken string allowLogin bool } -func New(sink output.Sink, platform api.PlatformAPI, storage AuthTokenStorage, allowLogin bool) *Auth { +func New(sink output.Sink, platform api.PlatformAPI, storage AuthTokenStorage, authToken, webAppURL string, allowLogin bool) *Auth { return &Auth{ tokenStorage: storage, - login: newLoginProvider(sink, platform), + login: newLoginProvider(sink, platform, webAppURL), sink: sink, + authToken: authToken, allowLogin: allowLogin, } } @@ -34,7 +35,7 @@ func (a *Auth) GetToken(ctx context.Context) (string, error) { return token, nil } - if token := env.Vars.AuthToken; token != "" { + if token := a.authToken; token != "" { return token, nil } @@ -66,7 +67,7 @@ func (a *Auth) Logout() error { _, err := a.tokenStorage.GetAuthToken() if err != nil { output.EmitSpinnerStop(a.sink) - if env.Vars.AuthToken != "" { + if a.authToken != "" { output.EmitNote(a.sink, "Authenticated via LOCALSTACK_AUTH_TOKEN environment variable; unset it to log out") return nil } diff --git a/internal/auth/login.go b/internal/auth/login.go index b5f8161..68aacf2 100644 --- a/internal/auth/login.go +++ b/internal/auth/login.go @@ -7,7 +7,6 @@ import ( "fmt" "github.com/localstack/lstk/internal/api" - "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/output" "github.com/pkg/browser" ) @@ -19,12 +18,14 @@ type LoginProvider interface { type loginProvider struct { platformClient api.PlatformAPI sink output.Sink + webAppURL string } -func newLoginProvider(sink output.Sink, platformClient api.PlatformAPI) *loginProvider { +func newLoginProvider(sink output.Sink, platformClient api.PlatformAPI, webAppURL string) *loginProvider { return &loginProvider{ platformClient: platformClient, sink: sink, + webAppURL: webAppURL, } } @@ -34,7 +35,7 @@ func (l *loginProvider) Login(ctx context.Context) (string, error) { return "", fmt.Errorf("failed to create auth request: %w", err) } - authURL := fmt.Sprintf("%s/auth/request/%s", getWebAppURL(), authReq.ID) + authURL := fmt.Sprintf("%s/auth/request/%s", l.webAppURL, authReq.ID) output.EmitAuth(l.sink, output.AuthEvent{ Preamble: "Welcome to lstk, a command-line interface for LocalStack", @@ -65,9 +66,6 @@ func (l *loginProvider) Login(ctx context.Context) (string, error) { } } -func getWebAppURL() string { - return env.Vars.WebAppURL -} func (l *loginProvider) completeAuth(ctx context.Context, authReq *api.AuthRequest) (string, error) { output.EmitInfo(l.sink, "Checking if auth request is confirmed...") diff --git a/internal/auth/token_storage.go b/internal/auth/token_storage.go index 3d6b7c1..9d63d1d 100644 --- a/internal/auth/token_storage.go +++ b/internal/auth/token_storage.go @@ -10,7 +10,6 @@ import ( "github.com/99designs/keyring" "github.com/localstack/lstk/internal/config" - "github.com/localstack/lstk/internal/env" ) const ( @@ -33,7 +32,7 @@ type authTokenStorage struct { ring keyring.Keyring } -func NewTokenStorage() (AuthTokenStorage, error) { +func NewTokenStorage(forceFileKeyring bool) (AuthTokenStorage, error) { configDir, err := config.ConfigDir() if err != nil { return nil, err @@ -47,8 +46,7 @@ func NewTokenStorage() (AuthTokenStorage, error) { }, } - // Force file backend if LSTK_KEYRING env var is set to "file" - if env.Vars.Keyring == "file" { + if forceFileKeyring { keyringConfig.AllowedBackends = []keyring.BackendType{keyring.FileBackend} } diff --git a/internal/container/start.go b/internal/container/start.go index 707f38b..a8394b0 100644 --- a/internal/container/start.go +++ b/internal/container/start.go @@ -17,17 +17,17 @@ import ( "github.com/localstack/lstk/internal/runtime" ) -func Start(ctx context.Context, rt runtime.Runtime, sink output.Sink, platformClient api.PlatformAPI, interactive bool) error { +func Start(ctx context.Context, rt runtime.Runtime, sink output.Sink, platformClient api.PlatformAPI, authToken string, forceFileKeyring bool, webAppURL string, interactive bool) error { if err := rt.IsHealthy(ctx); err != nil { rt.EmitUnhealthyError(sink, err) return output.NewSilentError(fmt.Errorf("runtime not healthy: %w", err)) } - tokenStorage, err := auth.NewTokenStorage() + tokenStorage, err := auth.NewTokenStorage(forceFileKeyring) if err != nil { return fmt.Errorf("failed to initialize token storage: %w", err) } - a := auth.New(sink, platformClient, tokenStorage, interactive) + a := auth.New(sink, platformClient, tokenStorage, authToken, webAppURL, interactive) token, err := a.GetToken(ctx) if err != nil { diff --git a/internal/container/start_test.go b/internal/container/start_test.go index caf066b..97004f7 100644 --- a/internal/container/start_test.go +++ b/internal/container/start_test.go @@ -21,7 +21,7 @@ func TestStart_ReturnsEarlyIfRuntimeUnhealthy(t *testing.T) { sink := output.NewPlainSink(io.Discard) - err := Start(context.Background(), mockRT, sink, nil, false) + err := Start(context.Background(), mockRT, sink, nil, "", false, "", false) require.Error(t, err) assert.Contains(t, err.Error(), "runtime not healthy") diff --git a/internal/env/env.go b/internal/env/env.go index f6f6020..49b07bc 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -8,16 +8,14 @@ import ( ) type Env struct { - AuthToken string - APIEndpoint string - WebAppURL string - Keyring string + AuthToken string + APIEndpoint string + WebAppURL string + ForceFileKeyring bool } -var Vars = &Env{} - -// Init initializes environment variable configuration -func Init() { +// Init initializes environment variable configuration and returns the result. +func Init() *Env { viper.SetEnvPrefix("LSTK") viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() @@ -27,10 +25,11 @@ func Init() { // LOCALSTACK_AUTH_TOKEN is not prefixed with LSTK_ // in order to be shared seamlessly with other LocalStack tools - Vars = &Env{ - AuthToken: os.Getenv("LOCALSTACK_AUTH_TOKEN"), - APIEndpoint: viper.GetString("api_endpoint"), - WebAppURL: viper.GetString("web_app_url"), - Keyring: viper.GetString("keyring"), + return &Env{ + AuthToken: os.Getenv("LOCALSTACK_AUTH_TOKEN"), + APIEndpoint: viper.GetString("api_endpoint"), + WebAppURL: viper.GetString("web_app_url"), + ForceFileKeyring: viper.GetString("keyring") == "file", } + } diff --git a/internal/ui/run.go b/internal/ui/run.go index b952cc7..5d5c2c4 100644 --- a/internal/ui/run.go +++ b/internal/ui/run.go @@ -26,7 +26,7 @@ func (s programSender) Send(msg any) { s.p.Send(msg) } -func Run(parentCtx context.Context, rt runtime.Runtime, version string, platformClient api.PlatformAPI) error { +func Run(parentCtx context.Context, rt runtime.Runtime, version string, platformClient api.PlatformAPI, authToken string, forceFileKeyring bool, webAppURL string) error { ctx, cancel := context.WithCancel(parentCtx) defer cancel() @@ -47,7 +47,7 @@ func Run(parentCtx context.Context, rt runtime.Runtime, version string, platform go func() { var err error defer func() { runErrCh <- err }() - err = container.Start(ctx, rt, output.NewTUISink(programSender{p: p}), platformClient, true) + err = container.Start(ctx, rt, output.NewTUISink(programSender{p: p}), platformClient, authToken, forceFileKeyring, webAppURL, true) if err != nil { if errors.Is(err, context.Canceled) { return diff --git a/internal/ui/run_login.go b/internal/ui/run_login.go index 834f629..66cb1dc 100644 --- a/internal/ui/run_login.go +++ b/internal/ui/run_login.go @@ -11,7 +11,7 @@ import ( "github.com/localstack/lstk/internal/output" ) -func RunLogin(parentCtx context.Context, version string, platformClient api.PlatformAPI) error { +func RunLogin(parentCtx context.Context, version string, platformClient api.PlatformAPI, authToken string, forceFileKeyring bool, webAppURL string) error { ctx, cancel := context.WithCancel(parentCtx) defer cancel() @@ -20,13 +20,13 @@ func RunLogin(parentCtx context.Context, version string, platformClient api.Plat runErrCh := make(chan error, 1) go func() { - tokenStorage, err := auth.NewTokenStorage() + tokenStorage, err := auth.NewTokenStorage(forceFileKeyring) if err != nil { runErrCh <- err p.Send(runErrMsg{err: err}) return } - a := auth.New(output.NewTUISink(programSender{p: p}), platformClient, tokenStorage, true) + a := auth.New(output.NewTUISink(programSender{p: p}), platformClient, tokenStorage, authToken, webAppURL, true) _, err = a.GetToken(ctx) runErrCh <- err diff --git a/internal/ui/run_login_test.go b/internal/ui/run_login_test.go index 9c42b22..8407ea6 100644 --- a/internal/ui/run_login_test.go +++ b/internal/ui/run_login_test.go @@ -16,7 +16,6 @@ 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,10 +83,6 @@ func TestLoginFlow_DeviceFlowSuccess(t *testing.T) { mockServer := createMockAPIServer(t, "test-license-token", true) defer mockServer.Close() - t.Setenv("LSTK_API_ENDPOINT", mockServer.URL) - t.Setenv("LOCALSTACK_AUTH_TOKEN", "") - env.Init() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -98,11 +93,11 @@ func TestLoginFlow_DeviceFlowSuccess(t *testing.T) { tm := teatest.NewTestModel(t, NewApp("test", "", "", cancel), teatest.WithInitialTermSize(120, 40)) sender := testModelSender{tm: tm} - platformClient := api.NewPlatformClient() + platformClient := api.NewPlatformClient(mockServer.URL) errCh := make(chan error, 1) go func() { - a := auth.New(output.NewTUISink(sender), platformClient, mockStorage, true) + a := auth.New(output.NewTUISink(sender), platformClient, mockStorage, "", mockServer.URL, true) _, err := a.GetToken(ctx) errCh <- err if err != nil && !errors.Is(err, context.Canceled) { @@ -137,10 +132,6 @@ func TestLoginFlow_DeviceFlowFailure_NotConfirmed(t *testing.T) { mockServer := createMockAPIServer(t, "", false) defer mockServer.Close() - t.Setenv("LSTK_API_ENDPOINT", mockServer.URL) - t.Setenv("LOCALSTACK_AUTH_TOKEN", "") - env.Init() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -150,11 +141,11 @@ func TestLoginFlow_DeviceFlowFailure_NotConfirmed(t *testing.T) { tm := teatest.NewTestModel(t, NewApp("test", "", "", cancel), teatest.WithInitialTermSize(120, 40)) sender := testModelSender{tm: tm} - platformClient := api.NewPlatformClient() + platformClient := api.NewPlatformClient(mockServer.URL) errCh := make(chan error, 1) go func() { - a := auth.New(output.NewTUISink(sender), platformClient, mockStorage, true) + a := auth.New(output.NewTUISink(sender), platformClient, mockStorage, "", mockServer.URL, true) _, err := a.GetToken(ctx) errCh <- err if err != nil && !errors.Is(err, context.Canceled) { @@ -190,10 +181,6 @@ func TestLoginFlow_DeviceFlowCancelWithCtrlC(t *testing.T) { mockServer := createMockAPIServer(t, "", false) defer mockServer.Close() - t.Setenv("LSTK_API_ENDPOINT", mockServer.URL) - t.Setenv("LOCALSTACK_AUTH_TOKEN", "") - env.Init() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -203,11 +190,11 @@ func TestLoginFlow_DeviceFlowCancelWithCtrlC(t *testing.T) { tm := teatest.NewTestModel(t, NewApp("test", "", "", cancel), teatest.WithInitialTermSize(120, 40)) sender := testModelSender{tm: tm} - platformClient := api.NewPlatformClient() + platformClient := api.NewPlatformClient(mockServer.URL) errCh := make(chan error, 1) go func() { - a := auth.New(output.NewTUISink(sender), platformClient, mockStorage, true) + a := auth.New(output.NewTUISink(sender), platformClient, mockStorage, "", mockServer.URL, true) _, err := a.GetToken(ctx) errCh <- err if err != nil && !errors.Is(err, context.Canceled) { diff --git a/internal/ui/run_logout.go b/internal/ui/run_logout.go index 30f7d5e..f4069a2 100644 --- a/internal/ui/run_logout.go +++ b/internal/ui/run_logout.go @@ -11,7 +11,7 @@ import ( "github.com/localstack/lstk/internal/output" ) -func RunLogout(parentCtx context.Context) error { +func RunLogout(parentCtx context.Context, platformClient api.PlatformAPI, authToken string, forceFileKeyring bool) error { _, cancel := context.WithCancel(parentCtx) defer cancel() @@ -20,15 +20,14 @@ func RunLogout(parentCtx context.Context) error { runErrCh := make(chan error, 1) go func() { - tokenStorage, err := auth.NewTokenStorage() + tokenStorage, err := auth.NewTokenStorage(forceFileKeyring) if err != nil { runErrCh <- err p.Send(runErrMsg{err: err}) return } - platformClient := api.NewPlatformClient() - a := auth.New(output.NewTUISink(programSender{p: p}), platformClient, tokenStorage, false) + a := auth.New(output.NewTUISink(programSender{p: p}), platformClient, tokenStorage, authToken, "", false) err = a.Logout() runErrCh <- err