Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions internal/container/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,17 +211,29 @@ func selectContainersToStart(ctx context.Context, rt runtime.Runtime, sink outpu
continue
}
if err := ports.CheckAvailable(c.Port); err != nil {
configPath, pathErr := config.ConfigFilePath()
if pathErr != nil {
return nil, err
}
return nil, fmt.Errorf("%w\nTo use a different port, edit %s", err, configPath)
emitPortInUseError(sink, c.Port)
return nil, output.NewSilentError(err)
}
filtered = append(filtered, c)
}
return filtered, nil
}

func emitPortInUseError(sink output.Sink, port string) {
actions := []output.ErrorAction{
{Label: "Stop existing emulator:", Value: "lstk stop"},
}
configPath, pathErr := config.ConfigFilePath()
if pathErr == nil {
actions = append(actions, output.ErrorAction{Label: "Use another port in the configuration:", Value: configPath})
}
output.EmitError(sink, output.ErrorEvent{
Title: fmt.Sprintf("Port %s already in use", port),
Summary: "LocalStack may already be running.",
Actions: actions,
})
}

func validateLicense(ctx context.Context, rt runtime.Runtime, sink output.Sink, platformClient api.PlatformAPI, containerConfig runtime.ContainerConfig, token string) error {
version := containerConfig.Tag
if version == "" || version == "latest" {
Expand Down
2 changes: 2 additions & 0 deletions internal/output/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ type SpinnerEvent struct {
MinDuration time.Duration // Minimum time spinner should display (0 = use default)
}

const ErrorActionPrefix = "==> "

type ErrorAction struct {
Label string
Value string
Expand Down
2 changes: 1 addition & 1 deletion internal/output/plain_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func formatErrorEvent(e ErrorEvent) string {
sb.WriteString(e.Detail)
}
for _, action := range e.Actions {
sb.WriteString("\n → ")
sb.WriteString("\n " + ErrorActionPrefix)
sb.WriteString(action.Label)
sb.WriteString(" ")
sb.WriteString(action.Value)
Expand Down
2 changes: 1 addition & 1 deletion internal/output/plain_format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func TestFormatEventLine(t *testing.T) {
{Label: "Start Docker:", Value: "open -a Docker"},
},
},
want: "Error: Docker not running\n Cannot connect to Docker daemon\n Start Docker: open -a Docker",
want: "Error: Docker not running\n Cannot connect to Docker daemon\n ==> Start Docker: open -a Docker",
wantOK: true,
},
{
Expand Down
2 changes: 1 addition & 1 deletion internal/output/plain_sink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func TestPlainSink_EmitsErrorEvent(t *testing.T) {
Actions: []ErrorAction{{Label: "Start Docker:", Value: "open -a Docker"}},
})

expected := "Error: Connection failed\n Cannot connect to Docker\n Start Docker: open -a Docker\n"
expected := "Error: Connection failed\n Cannot connect to Docker\n ==> Start Docker: open -a Docker\n"
assert.Equal(t, expected, out.String())
}

Expand Down
4 changes: 2 additions & 2 deletions internal/ui/components/error_display.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ func (e ErrorDisplay) View(maxWidth int) string {
sb.WriteString("\n")
for i, action := range e.event.Actions {
if i > 0 {
sb.WriteString(styles.SecondaryMessage.Render("⇒ " + action.Label + " " + action.Value))
sb.WriteString(styles.SecondaryMessage.Render(output.ErrorActionPrefix + action.Label + " " + action.Value))
} else {
sb.WriteString(styles.ErrorAction.Render("⇒ " + action.Label + " "))
sb.WriteString(styles.ErrorAction.Render(output.ErrorActionPrefix + action.Label + " "))
sb.WriteString(styles.Message.Bold(true).Render(action.Value))
}
sb.WriteString("\n")
Expand Down
34 changes: 34 additions & 0 deletions internal/ui/components/error_display_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,40 @@ func TestErrorDisplay_ShowView(t *testing.T) {
}
}

func TestErrorDisplay_MultiActionRenders(t *testing.T) {
t.Parallel()

e := NewErrorDisplay()
e = e.Show(output.ErrorEvent{
Title: "Port 4566 already in use",
Summary: "LocalStack may already be running.",
Actions: []output.ErrorAction{
{Label: "Stop existing emulator:", Value: "lstk stop"},
{Label: "Use another port in the configuration:", Value: "/home/user/.config/lstk/config.toml"},
},
})

view := e.View(80)
if !strings.Contains(view, "Port 4566 already in use") {
t.Fatalf("expected view to contain title, got: %q", view)
}
if !strings.Contains(view, "LocalStack may already be running.") {
t.Fatalf("expected view to contain summary, got: %q", view)
}
if !strings.Contains(view, "Stop existing emulator:") {
t.Fatalf("expected view to contain first action label, got: %q", view)
}
if !strings.Contains(view, "lstk stop") {
t.Fatalf("expected view to contain first action value, got: %q", view)
}
if !strings.Contains(view, "Use another port in the configuration:") {
t.Fatalf("expected view to contain second action label, got: %q", view)
}
if !strings.Contains(view, "/home/user/.config/lstk/config.toml") {
t.Fatalf("expected view to contain second action value, got: %q", view)
}
}

func TestErrorDisplay_MinimalEvent(t *testing.T) {
t.Parallel()

Expand Down
6 changes: 4 additions & 2 deletions test/integration/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ func TestStartCommandFailsWhenPortInUse(t *testing.T) {
require.NoError(t, err, "failed to bind port 4566 for test")
defer func() { _ = ln.Close() }()

_, stderr, err := runLstk(t, testContext(t), "", env.With(env.AuthToken, "fake-token"), "start")
stdout, _, err := runLstk(t, testContext(t), "", env.With(env.AuthToken, "fake-token"), "start")
require.Error(t, err, "expected lstk start to fail when port is in use")
assert.Contains(t, stderr, "port 4566 already in use")
assert.Contains(t, stdout, "Port 4566 already in use")
assert.Contains(t, stdout, "LocalStack may already be running.")
assert.Contains(t, stdout, "lstk stop")
}

func cleanup() {
Expand Down
Loading