Thanks to visit codestin.com
Credit goes to github.com

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 <[email protected]>
1 parent 3514ca3 commit 82e6070

File tree

2 files changed

+86
-9
lines changed

2 files changed

+86
-9
lines changed

cli/support.go

+35-9
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

+51
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)