mirror of
https://github.com/coder/coder.git
synced 2026-09-21 12:44:32 +08:00
feat(cli): add hidden netcheck command (#8136)
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -107,6 +107,7 @@ func (r *RootCmd) Core() []*clibase.Cmd {
|
||||
|
||||
// Hidden
|
||||
r.gitssh(),
|
||||
r.netcheck(),
|
||||
r.vscodeSSH(),
|
||||
r.workspaceAgent(),
|
||||
}
|
||||
|
||||
Generated
+28
@@ -4006,6 +4006,34 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaceagents/connection": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Get connection info for workspace agent generic",
|
||||
"operationId": "get-connection-info-for-workspace-agent-generic",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentConnectionInfo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-apidocgen": {
|
||||
"skip": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaceagents/google-instance-identity": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
||||
Generated
+24
@@ -3528,6 +3528,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaceagents/connection": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Agents"],
|
||||
"summary": "Get connection info for workspace agent generic",
|
||||
"operationId": "get-connection-info-for-workspace-agent-generic",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentConnectionInfo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-apidocgen": {
|
||||
"skip": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaceagents/google-instance-identity": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user