diff --git a/internal/app/app.go b/internal/app/app.go index 482a87a..8887d05 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -14,6 +14,7 @@ import ( "git.netflux.io/rob/octoplex/internal/mediaserver" "git.netflux.io/rob/octoplex/internal/replicator" "git.netflux.io/rob/octoplex/internal/terminal" + "github.com/docker/docker/client" ) // RunParams holds the parameters for running the application. @@ -69,7 +70,15 @@ func Run(ctx context.Context, params RunParams) error { containerClient, err := container.NewClient(ctx, params.DockerClient, logger.With("component", "container_client")) if err != nil { err = fmt.Errorf("create container client: %w", err) - ui.ShowFatalErrorModal(err) + + var errString string + if client.IsErrConnectionFailed(err) { + errString = "Could not connect to Docker. Is Docker installed and running?" + } else { + errString = err.Error() + } + ui.ShowFatalErrorModal(errString) + emptyUI() <-ui.C() return err @@ -86,7 +95,7 @@ func Run(ctx context.Context, params RunParams) error { }) if err != nil { err = fmt.Errorf("create mediaserver: %w", err) - ui.ShowFatalErrorModal(err) + ui.ShowFatalErrorModal(err.Error()) emptyUI() <-ui.C() return err diff --git a/internal/app/integration_test.go b/internal/app/integration_test.go index 17f52b3..e0a4f58 100644 --- a/internal/app/integration_test.go +++ b/internal/app/integration_test.go @@ -791,3 +791,42 @@ func TestIntegrationDockerClientError(t *testing.T) { <-done } +func TestIntegrationDockerConnectionError(t *testing.T) { + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Minute) + defer cancel() + + logger := testhelpers.NewTestLogger(t).With("component", "integration") + dockerClient, err := dockerclient.NewClientWithOpts(dockerclient.WithHost("http://docker.example.com")) + require.NoError(t, err) + + configService := setupConfigService(t, config.Config{Sources: config.Sources{RTMP: config.RTMPSource{Enabled: true}}}) + screen, screenCaptureC, getContents := setupSimulationScreen(t) + + done := make(chan struct{}) + go func() { + defer func() { + done <- struct{}{} + }() + + err := app.Run(ctx, buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)) + require.ErrorContains(t, err, "dial tcp: lookup docker.example.com") + require.ErrorContains(t, err, "no such host") + }() + + require.EventuallyWithT( + t, + func(t *assert.CollectT) { + assert.True(t, contentsIncludes(getContents(), "An error occurred:"), "expected to see error message") + assert.True(t, contentsIncludes(getContents(), "Could not connect to Docker. Is Docker installed"), "expected to see message") + }, + 5*time.Second, + time.Second, + "expected to see fatal error modal", + ) + printScreen(t, getContents, "Ater displaying the fatal error modal") + + // Quit the app, this should cause the done channel to receive. + sendKey(t, screen, tcell.KeyEnter, ' ') + + <-done +} diff --git a/internal/terminal/terminal.go b/internal/terminal/terminal.go index aa429ec..e21ab19 100644 --- a/internal/terminal/terminal.go +++ b/internal/terminal/terminal.go @@ -386,13 +386,13 @@ func (ui *UI) ShowDestinationErrorModal(name string, err error) { // ShowFatalErrorModal displays the provided error. It sends a CommandQuit to the // command channel when the user selects the Quit button. -func (ui *UI) ShowFatalErrorModal(err error) { +func (ui *UI) ShowFatalErrorModal(errString string) { ui.app.QueueUpdateDraw(func() { ui.showModal( pageNameModalFatalError, fmt.Sprintf( "An error occurred:\n\n%s", - err, + errString, ), []string{"Quit"}, false,