diff --git a/internal/cmd/local/local/install.go b/internal/cmd/local/local/install.go index aae939b..870c468 100644 --- a/internal/cmd/local/local/install.go +++ b/internal/cmd/local/local/install.go @@ -332,7 +332,12 @@ func (c *Command) diagnoseAirbyteChartFailure(ctx context.Context, chartErr erro if err != nil { msg = "unknown: failed to get pod logs." } - pterm.Debug.Println("found logs: ", logs[0:50]) + + preview := logs + if len(preview) > 50 { + preview = preview[:50] + } + pterm.Debug.Println("found logs: ", preview) trace.AttachLog(fmt.Sprintf("%s.log", pod.Name), logs) diff --git a/internal/cmd/local/local/install_test.go b/internal/cmd/local/local/install_test.go index 4f005e2..cb8ac2c 100644 --- a/internal/cmd/local/local/install_test.go +++ b/internal/cmd/local/local/install_test.go @@ -19,6 +19,7 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/repo" + corev1 "k8s.io/api/core/v1" ) const portTest = 9999 @@ -158,7 +159,6 @@ func TestCommand_Install(t *testing.T) { return nil }), ) - if err != nil { t.Fatal(err) } @@ -172,6 +172,67 @@ func TestCommand_Install(t *testing.T) { } } +func TestCommand_InstallError(t *testing.T) { + testErr := errors.New("test error") + valuesYaml := mustReadFile(t, "testdata/test-edition.values.yaml") + + helm := mockHelmClient{ + installOrUpgradeChart: func(ctx context.Context, spec *helmclient.ChartSpec, opts *helmclient.GenericHelmOptions) (*release.Release, error) { + return nil, testErr + }, + getChart: func(name string, _ *action.ChartPathOptions) (*chart.Chart, string, error) { + return &chart.Chart{Metadata: &chart.Metadata{Version: "test.airbyte.version"}}, "", nil + }, + } + + k8sClient := k8stest.MockClient{ + FnIngressExists: func(ctx context.Context, namespace string, ingress string) bool { + return false + }, + FnLogsGet: func(ctx context.Context, namespace, name string) (string, error) { + return "short", nil + }, + FnPodList: func(ctx context.Context, namespace string) (*corev1.PodList, error) { + // embedded structs make it easier to set fields this way + pod := corev1.Pod{} + pod.Name = "test-pod-1" + pod.Status.Phase = corev1.PodFailed + pod.Status.Reason = "test-reason" + return &corev1.PodList{Items: []corev1.Pod{pod}}, nil + }, + } + + tel := telemetry.MockClient{} + + httpClient := mockHTTP{do: func(req *http.Request) (*http.Response, error) { + return &http.Response{StatusCode: 200}, nil + }} + installOpts := &InstallOpts{ + HelmValuesYaml: valuesYaml, + AirbyteChartLoc: testAirbyteChartLoc, + } + + c, err := New( + k8s.TestProvider, + WithPortHTTP(portTest), + WithHelmClient(&helm), + WithK8sClient(&k8sClient), + WithTelemetryClient(&tel), + WithHTTPClient(&httpClient), + WithBrowserLauncher(func(url string) error { + return nil + }), + ) + if err != nil { + t.Fatal(err) + } + err = c.Install(context.Background(), installOpts) + expect := "unable to install airbyte chart:\npod test-pod-1: unknown" + if expect != err.Error() { + t.Errorf("expected %q but got %q", expect, err) + } +} + func mustReadFile(t *testing.T, name string) string { b, err := os.ReadFile(name) if err != nil { diff --git a/internal/cmd/local/local/mock_test.go b/internal/cmd/local/local/mock_test.go index d4618a8..9a3cc6c 100644 --- a/internal/cmd/local/local/mock_test.go +++ b/internal/cmd/local/local/mock_test.go @@ -26,6 +26,9 @@ type mockHelmClient struct { } func (m *mockHelmClient) AddOrUpdateChartRepo(entry repo.Entry) error { + if m.addOrUpdateChartRepo == nil { + return nil + } return m.addOrUpdateChartRepo(entry) } diff --git a/internal/telemetry/segment.go b/internal/telemetry/segment.go index 589e725..3a89f02 100644 --- a/internal/telemetry/segment.go +++ b/internal/telemetry/segment.go @@ -86,7 +86,7 @@ func (s *SegmentClient) User() string { return s.cfg.AnalyticsID.toUUID().String() } -func (s *SegmentClient) Wrap(ctx context.Context, et EventType, f func() error) error { +func (s *SegmentClient) Wrap(ctx context.Context, et EventType, f func() error) (res error) { attemptSuccessFailure := true if err := s.Start(ctx, et); err != nil { @@ -94,13 +94,26 @@ func (s *SegmentClient) Wrap(ctx context.Context, et EventType, f func() error) attemptSuccessFailure = false } + defer func() { + if r := recover(); r != nil { + pterm.Error.Printfln("recovered from panic: %v", r) + err, ok := r.(error) + if !ok { + err = fmt.Errorf("panic: %v", r) + } + res = err + if errTel := s.Failure(ctx, et, err); errTel != nil { + pterm.Debug.Printfln("Unable to send telemetry failure data: %s", errTel) + } + } + }() + if err := f(); err != nil { if attemptSuccessFailure { if errTel := s.Failure(ctx, et, err); errTel != nil { pterm.Debug.Printfln("Unable to send telemetry failure data: %s", errTel) } } - return err } diff --git a/internal/telemetry/segment_test.go b/internal/telemetry/segment_test.go index 57f1c57..b92aeb7 100644 --- a/internal/telemetry/segment_test.go +++ b/internal/telemetry/segment_test.go @@ -864,6 +864,36 @@ func TestSegmentClient_WrapErr(t *testing.T) { } } +func TestSegmentClient_WrapPanic(t *testing.T) { + mDoer := &mockDoer{ + do: func(r *http.Request) (*http.Response, error) { + return &http.Response{Body: io.NopCloser(&strings.Reader{})}, nil + }, + } + opts := []Option{ + WithSessionID(sessionID), + WithHTTPClient(mDoer), + } + ctx := context.Background() + cli := NewSegmentClient(Config{AnalyticsID: UUID(userID)}, opts...) + expect := errors.New("test panic err") + + err := cli.Wrap(ctx, Install, func() error { + panic(expect) + }) + if !errors.Is(err, expect) { + t.Errorf("expected %q but got %q", expect, err) + } + + expectStr := "panic: test str" + err = cli.Wrap(ctx, Install, func() error { + panic("test str") + }) + if err == nil || err.Error() != expectStr { + t.Errorf("expected %q but got %q", expectStr, err) + } +} + // --- mocks var _ Doer = (*mockDoer)(nil)