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>
This commit is contained in:
Cian Johnston
2024-08-22 11:15:02 +01:00
committed by GitHub
co-authored by Danny Kopping
parent 3514ca3476
commit 82e6070c7a
2 changed files with 86 additions and 9 deletions
+35 -9
View File
@@ -184,16 +184,8 @@ func (r *RootCmd) supportBundle() *serpent.Command {
_ = os.Remove(outputPath) // best effort
return xerrors.Errorf("create support bundle: %w", err)
}
docsURL := bun.Deployment.Config.Values.DocsURL.String()
deployHealthSummary := bun.Deployment.HealthReport.Summarize(docsURL)
if len(deployHealthSummary) > 0 {
cliui.Warn(inv.Stdout, "Deployment health issues detected:", deployHealthSummary...)
}
clientNetcheckSummary := bun.Network.Netcheck.Summarize("Client netcheck:", docsURL)
if len(clientNetcheckSummary) > 0 {
cliui.Warn(inv.Stdout, "Networking issues detected:", deployHealthSummary...)
}
summarizeBundle(inv, bun)
bun.CLILogs = cliLogBuf.Bytes()
if err := writeBundle(bun, zwr); err != nil {
@@ -225,6 +217,40 @@ func (r *RootCmd) supportBundle() *serpent.Command {
return cmd
}
// summarizeBundle makes a best-effort attempt to write a short summary
// of the support bundle to the user's terminal.
func summarizeBundle(inv *serpent.Invocation, bun *support.Bundle) {
if bun == nil {
cliui.Error(inv.Stdout, "No support bundle generated!")
return
}
if bun.Deployment.Config == nil {
cliui.Error(inv.Stdout, "No deployment configuration available!")
return
}
docsURL := bun.Deployment.Config.Values.DocsURL.String()
if bun.Deployment.HealthReport == nil {
cliui.Error(inv.Stdout, "No deployment health report available!")
return
}
deployHealthSummary := bun.Deployment.HealthReport.Summarize(docsURL)
if len(deployHealthSummary) > 0 {
cliui.Warn(inv.Stdout, "Deployment health issues detected:", deployHealthSummary...)
}
if bun.Network.Netcheck == nil {
cliui.Error(inv.Stdout, "No network troubleshooting information available!")
return
}
clientNetcheckSummary := bun.Network.Netcheck.Summarize("Client netcheck:", docsURL)
if len(clientNetcheckSummary) > 0 {
cliui.Warn(inv.Stdout, "Networking issues detected:", deployHealthSummary...)
}
}
func findAgent(agentName string, haystack []codersdk.WorkspaceResource) (*codersdk.WorkspaceAgent, bool) {
for _, res := range haystack {
for _, agt := range res.Agents {
+51
View File
@@ -5,6 +5,9 @@ import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"runtime"
@@ -14,6 +17,7 @@ import (
"tailscale.com/ipn/ipnstate"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/agent"
@@ -156,6 +160,53 @@ func TestSupportBundle(t *testing.T) {
err := inv.Run()
require.ErrorContains(t, err, "failed authorization check")
})
// This ensures that the CLI does not panic when trying to generate a support bundle
// against a fake server that returns an empty response for all requests. This essentially
// ensures that (almost) all of the support bundle generating code paths get a zero value.
t.Run("DontPanic", func(t *testing.T) {
t.Parallel()
for _, code := range []int{
http.StatusOK,
http.StatusUnauthorized,
http.StatusForbidden,
http.StatusNotFound,
http.StatusInternalServerError,
} {
t.Run(http.StatusText(code), func(t *testing.T) {
t.Parallel()
// Start up a fake server
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Logf("received request: %s %s", r.Method, r.URL)
switch r.URL.Path {
case "/api/v2/authcheck":
// Fake auth check
resp := codersdk.AuthorizationResponse{
"Read DeploymentValues": true,
}
w.WriteHeader(http.StatusOK)
assert.NoError(t, json.NewEncoder(w).Encode(resp))
default:
// Simply return a blank response for everything else.
w.WriteHeader(code)
}
}))
defer srv.Close()
u, err := url.Parse(srv.URL)
require.NoError(t, err)
client := codersdk.New(u)
d := t.TempDir()
path := filepath.Join(d, "bundle.zip")
inv, root := clitest.New(t, "support", "bundle", "--url-override", srv.URL, "--output-file", path, "--yes")
clitest.SetupConfig(t, client, root)
err = inv.Run()
require.NoError(t, err)
})
}
})
}
// nolint:revive // It's a control flag, but this is just a test.