Skip to content

Commit 82e6070

Browse files
fix(cli): ensure that the support bundle command does not panic on zero values (#14392)
We try to write a cute little summary at the end of the bundle, but that could panic if some of the fields of the bundle were nil. Adds a test that essentially ensures nil values in a bundle, and ensures that it can be handled without losing our towels. Co-authored-by: Danny Kopping <danny@coder.com>
1 parent 3514ca3 commit 82e6070

File tree

2 files changed

+86
-9
lines changed

2 files changed

+86
-9
lines changed

cli/support.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,8 @@ func (r *RootCmd) supportBundle() *serpent.Command {
184184
_ = os.Remove(outputPath) // best effort
185185
return xerrors.Errorf("create support bundle: %w", err)
186186
}
187-
docsURL := bun.Deployment.Config.Values.DocsURL.String()
188-
deployHealthSummary := bun.Deployment.HealthReport.Summarize(docsURL)
189-
if len(deployHealthSummary) > 0 {
190-
cliui.Warn(inv.Stdout, "Deployment health issues detected:", deployHealthSummary...)
191-
}
192-
clientNetcheckSummary := bun.Network.Netcheck.Summarize("Client netcheck:", docsURL)
193-
if len(clientNetcheckSummary) > 0 {
194-
cliui.Warn(inv.Stdout, "Networking issues detected:", deployHealthSummary...)
195-
}
196187

188+
summarizeBundle(inv, bun)
197189
bun.CLILogs = cliLogBuf.Bytes()
198190

199191
if err := writeBundle(bun, zwr); err != nil {
@@ -225,6 +217,40 @@ func (r *RootCmd) supportBundle() *serpent.Command {
225217
return cmd
226218
}
227219

220+
// summarizeBundle makes a best-effort attempt to write a short summary
221+
// of the support bundle to the user's terminal.
222+
func summarizeBundle(inv *serpent.Invocation, bun *support.Bundle) {
223+
if bun == nil {
224+
cliui.Error(inv.Stdout, "No support bundle generated!")
225+
return
226+
}
227+
228+
if bun.Deployment.Config == nil {
229+
cliui.Error(inv.Stdout, "No deployment configuration available!")
230+
return
231+
}
232+
233+
docsURL := bun.Deployment.Config.Values.DocsURL.String()
234+
if bun.Deployment.HealthReport == nil {
235+
cliui.Error(inv.Stdout, "No deployment health report available!")
236+
return
237+
}
238+
deployHealthSummary := bun.Deployment.HealthReport.Summarize(docsURL)
239+
if len(deployHealthSummary) > 0 {
240+
cliui.Warn(inv.Stdout, "Deployment health issues detected:", deployHealthSummary...)
241+
}
242+
243+
if bun.Network.Netcheck == nil {
244+
cliui.Error(inv.Stdout, "No network troubleshooting information available!")
245+
return
246+
}
247+
248+
clientNetcheckSummary := bun.Network.Netcheck.Summarize("Client netcheck:", docsURL)
249+
if len(clientNetcheckSummary) > 0 {
250+
cliui.Warn(inv.Stdout, "Networking issues detected:", deployHealthSummary...)
251+
}
252+
}
253+
228254
func findAgent(agentName string, haystack []codersdk.WorkspaceResource) (*codersdk.WorkspaceAgent, bool) {
229255
for _, res := range haystack {
230256
for _, agt := range res.Agents {

cli/support_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"bytes"
66
"encoding/json"
77
"io"
8+
"net/http"
9+
"net/http/httptest"
10+
"net/url"
811
"os"
912
"path/filepath"
1013
"runtime"
@@ -14,6 +17,7 @@ import (
1417
"tailscale.com/ipn/ipnstate"
1518

1619
"github.com/google/uuid"
20+
"github.com/stretchr/testify/assert"
1721
"github.com/stretchr/testify/require"
1822

1923
"github.com/coder/coder/v2/agent"
@@ -156,6 +160,53 @@ func TestSupportBundle(t *testing.T) {
156160
err := inv.Run()
157161
require.ErrorContains(t, err, "failed authorization check")
158162
})
163+
164+
// This ensures that the CLI does not panic when trying to generate a support bundle
165+
// against a fake server that returns an empty response for all requests. This essentially
166+
// ensures that (almost) all of the support bundle generating code paths get a zero value.
167+
t.Run("DontPanic", func(t *testing.T) {
168+
t.Parallel()
169+
170+
for _, code := range []int{
171+
http.StatusOK,
172+
http.StatusUnauthorized,
173+
http.StatusForbidden,
174+
http.StatusNotFound,
175+
http.StatusInternalServerError,
176+
} {
177+
t.Run(http.StatusText(code), func(t *testing.T) {
178+
t.Parallel()
179+
// Start up a fake server
180+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
181+
t.Logf("received request: %s %s", r.Method, r.URL)
182+
switch r.URL.Path {
183+
case "/api/v2/authcheck":
184+
// Fake auth check
185+
resp := codersdk.AuthorizationResponse{
186+
"Read DeploymentValues": true,
187+
}
188+
w.WriteHeader(http.StatusOK)
189+
assert.NoError(t, json.NewEncoder(w).Encode(resp))
190+
default:
191+
// Simply return a blank response for everything else.
192+
w.WriteHeader(code)
193+
}
194+
}))
195+
defer srv.Close()
196+
u, err := url.Parse(srv.URL)
197+
require.NoError(t, err)
198+
client := codersdk.New(u)
199+
200+
d := t.TempDir()
201+
path := filepath.Join(d, "bundle.zip")
202+
203+
inv, root := clitest.New(t, "support", "bundle", "--url-override", srv.URL, "--output-file", path, "--yes")
204+
clitest.SetupConfig(t, client, root)
205+
err = inv.Run()
206+
require.NoError(t, err)
207+
})
208+
}
209+
})
159210
}
160211

161212
// nolint:revive // It's a control flag, but this is just a test.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy