Skip to content

feat(cli): add hidden netcheck command #8136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions cli/netcheck.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package cli

import (
"context"
"encoding/json"
"fmt"
"time"

"golang.org/x/xerrors"

"github.com/coder/coder/cli/clibase"
"github.com/coder/coder/coderd/healthcheck"
"github.com/coder/coder/codersdk"
)

func (r *RootCmd) netcheck() *clibase.Cmd {
client := new(codersdk.Client)

cmd := &clibase.Cmd{
Use: "netcheck",
Short: "Print network debug information for DERP and STUN",
Hidden: true,
Middleware: clibase.Chain(
r.InitClient(client),
),
Handler: func(inv *clibase.Invocation) error {
ctx, cancel := context.WithTimeout(inv.Context(), 30*time.Second)
defer cancel()

connInfo, err := client.WorkspaceAgentConnectionInfo(ctx)
if err != nil {
return err
}

_, _ = fmt.Fprint(inv.Stderr, "Gathering a network report. This may take a few seconds...\n\n")

var report healthcheck.DERPReport
report.Run(ctx, &healthcheck.DERPReportOptions{
DERPMap: connInfo.DERPMap,
})

raw, err := json.MarshalIndent(report, "", " ")
if err != nil {
return err
}

n, err := inv.Stdout.Write(raw)
if err != nil {
return err
}
if n != len(raw) {
return xerrors.Errorf("failed to write all bytes to stdout; wrote %d, len %d", n, len(raw))
}

_, _ = inv.Stdout.Write([]byte("\n"))
return nil
},
}

cmd.Options = clibase.OptionSet{}
return cmd
}
34 changes: 34 additions & 0 deletions cli/netcheck_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cli_test

import (
"bytes"
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/healthcheck"
"github.com/coder/coder/pty/ptytest"
)

func TestNetcheck(t *testing.T) {
t.Parallel()

pty := ptytest.New(t)
config := login(t, pty)

var out bytes.Buffer
inv, _ := clitest.New(t, "netcheck", "--global-config", string(config))
inv.Stdout = &out

clitest.StartWithWaiter(t, inv).RequireSuccess()

var report healthcheck.DERPReport
require.NoError(t, json.Unmarshal(out.Bytes(), &report))

assert.True(t, report.Healthy)
require.Len(t, report.Regions, 1)
require.Len(t, report.Regions[1].NodeReports, 1)
}
1 change: 1 addition & 0 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func (r *RootCmd) Core() []*clibase.Cmd {

// Hidden
r.gitssh(),
r.netcheck(),
r.vscodeSSH(),
r.workspaceAgent(),
}
Expand Down
28 changes: 28 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ func New(options *Options) *API {
r.Post("/azure-instance-identity", api.postWorkspaceAuthAzureInstanceIdentity)
r.Post("/aws-instance-identity", api.postWorkspaceAuthAWSInstanceIdentity)
r.Post("/google-instance-identity", api.postWorkspaceAuthGoogleInstanceIdentity)
r.Get("/connection", api.workspaceAgentConnectionGeneric)
r.Route("/me", func(r chi.Router) {
r.Use(httpmw.ExtractWorkspaceAgent(options.Database))
r.Get("/manifest", api.workspaceAgentManifest)
Expand Down
19 changes: 19 additions & 0 deletions coderd/workspaceagents.go
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,25 @@ func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request
})
}

// workspaceAgentConnectionGeneric is the same as workspaceAgentConnection but
// without the workspaceagent path parameter.
//
// @Summary Get connection info for workspace agent generic
// @ID get-connection-info-for-workspace-agent-generic
// @Security CoderSessionToken
// @Produce json
// @Tags Agents
// @Success 200 {object} codersdk.WorkspaceAgentConnectionInfo
// @Router /workspaceagents/connection [get]
// @x-apidocgen {"skip": true}
func (api *API) workspaceAgentConnectionGeneric(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()

httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{
DERPMap: api.DERPMap,
})
}

// @Summary Coordinate workspace agent via Tailnet
// @Description It accepts a WebSocket connection to an agent that listens to
// @Description incoming connections and publishes node updates.
Expand Down
20 changes: 20 additions & 0 deletions codersdk/workspaceagents.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,26 @@ type WorkspaceAgentConnectionInfo struct {
DERPMap *tailcfg.DERPMap `json:"derp_map"`
}

func (c *Client) WorkspaceAgentConnectionInfo(ctx context.Context) (*WorkspaceAgentConnectionInfo, error) {
res, err := c.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/connection", nil)
if err != nil {
return nil, err
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return nil, ReadBodyAsError(res)
}

var info WorkspaceAgentConnectionInfo
err = json.NewDecoder(res.Body).Decode(&info)
if err != nil {
return nil, xerrors.Errorf("decode connection info: %w", err)
}

return &info, nil
}

// @typescript-ignore DialWorkspaceAgentOptions
type DialWorkspaceAgentOptions struct {
Logger slog.Logger
Expand Down