From 10ab022cf53132c8e41c62fdab56fe2708cfc6c8 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 16 Jul 2021 19:40:39 +0000 Subject: [PATCH 1/8] Add satellite commands --- coder-sdk/interface.go | 9 ++ coder-sdk/satellite.go | 47 ++++++++ internal/cmd/satellites.go | 220 +++++++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 coder-sdk/satellite.go create mode 100644 internal/cmd/satellites.go diff --git a/coder-sdk/interface.go b/coder-sdk/interface.go index e439ca32..268d3b97 100644 --- a/coder-sdk/interface.go +++ b/coder-sdk/interface.go @@ -241,4 +241,13 @@ type Client interface { // SetPolicyTemplate sets the workspace policy template SetPolicyTemplate(ctx context.Context, templateID string, templateScope TemplateScope, dryRun bool) (*SetPolicyTemplateResponse, error) + + // Satellites fetches all satellitess known to the Coder control plane. + Satellites(ctx context.Context) (*Satellites, error) + + // CreateSatellite creates a new satellite entity. + CreateSatellite(ctx context.Context, req CreateSatelliteReq) (*Satellites, error) + + // DeleteSatelliteByID deletes a satellite entity from the Coder control plane. + DeleteSatelliteByID(ctx context.Context, id string) error } diff --git a/coder-sdk/satellite.go b/coder-sdk/satellite.go new file mode 100644 index 00000000..1dce8e7d --- /dev/null +++ b/coder-sdk/satellite.go @@ -0,0 +1,47 @@ +package coder + +import ( + "context" + "net/http" +) + +type Satellite struct { + ID string `json:"id"` + Name string `json:"name"` + Fingerprint string `json:"fingerprint"` +} + +type Satellites struct { + Data []Satellite `json:"data"` +} + +// Satellites fetches all satellitess known to the Coder control plane. +func (c *DefaultClient) Satellites(ctx context.Context) (*Satellites, error) { + var res Satellites + err := c.requestBody(ctx, http.MethodGet, "/api/private/satellites", nil, &res) + if err != nil { + return nil, err + } + return &res, nil +} + +// CreateSatelliteReq defines the request parameters for creating a new satellite entity. +type CreateSatelliteReq struct { + Name string `json:"name"` + PublicKey string `json:"public_key"` +} + +// CreateSatellite creates a new satellite entity. +func (c *DefaultClient) CreateSatellite(ctx context.Context, req CreateSatelliteReq) (*Satellites, error) { + var res Satellites + err := c.requestBody(ctx, http.MethodPost, "/api/private/satellites", req, &res) + if err != nil { + return nil, err + } + return &res, nil +} + +// DeleteSatelliteByID deletes a satellite entity from the Coder control plane. +func (c *DefaultClient) DeleteSatelliteByID(ctx context.Context, id string) error { + return c.requestBody(ctx, http.MethodDelete, "/api/private/satellites/"+id, nil, nil) +} diff --git a/internal/cmd/satellites.go b/internal/cmd/satellites.go new file mode 100644 index 00000000..fdf983ba --- /dev/null +++ b/internal/cmd/satellites.go @@ -0,0 +1,220 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + + "cdr.dev/coder-cli/internal/x/xcobra" + + "github.com/spf13/cobra" + "golang.org/x/xerrors" + + "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/pkg/clog" + "cdr.dev/coder-cli/pkg/tablewriter" +) + +const ( + satelliteKeyPath = "/api/private/satellites/key" +) + +type satelliteKeyResponse struct { + Key string `json:"key"` + Fingerprint string `json:"fingerprint"` +} + +func satellitesCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "satellites", + Short: "Interact with Coder satellite deployments", + Long: "Perform operations on the Coder satellites for the platform.", + Hidden: true, + } + + cmd.AddCommand( + createSatelliteCmd(), + listSatellitesCmd(), + deleteSatelliteCmd(), + ) + return cmd +} + +func createSatelliteCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create [name] [satellite_access_url]", + Args: xcobra.ExactArgs(2), + Short: "create a new satellite.", + Long: "Create a new Coder satellite.", + Example: `# create a new satellite + +coder satellites create eu-west https://eu-west-coder.com`, + RunE: func(cmd *cobra.Command, args []string) error { + var ( + ctx = cmd.Context() + name = args[0] + accessURL = args[1] + ) + + client, err := newClient(ctx, true) + if err != nil { + return xerrors.Errorf("making coder client", err) + } + + sURL, err := url.Parse(accessURL) + if err != nil { + return xerrors.Errorf("parsing satellite access url", err) + } + sURL.Path = satelliteKeyPath + + // Create the http request. + req, err := http.NewRequestWithContext(ctx, http.MethodGet, sURL.String(), nil) + if err != nil { + return xerrors.Errorf("create satellite request: %w", err) + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return xerrors.Errorf("doing satellite request: %w", err) + } + defer func() { _ = res.Body.Close() }() + + if res.StatusCode > 299 { + return fmt.Errorf("unexpected status code %d: %+v", res.StatusCode, res) + } + + var keyRes satelliteKeyResponse + if err := json.NewDecoder(res.Body).Decode(&keyRes); err != nil { + return xerrors.Errorf("decode response body: %w", err) + } + + if keyRes.Key == "" { + return xerrors.Errorf("key field empty in response") + } + if keyRes.Fingerprint == "" { + return xerrors.Errorf("fingerprint field empty in response") + } + + fmt.Printf(`The following satellite will be created: +Name: %s + +Public Key: +%s + +Fingerprint: +%s + +Do you wish to continue? (y) +`, name, keyRes.Key, keyRes.Fingerprint) + err = getConfirmation() + if err != nil { + return err + } + + _, err = client.CreateSatellite(ctx, coder.CreateSatelliteReq{ + Name: name, + PublicKey: keyRes.Key, + }) + if err != nil { + return xerrors.Errorf("making create satellite request: %w", err) + } + + clog.LogSuccess(fmt.Sprintf("satellite %s successfully created", name)) + + return nil + }, + } + + return cmd +} + +func getConfirmation() error { + var response string + + _, err := fmt.Scanln(&response) + if err != nil { + return xerrors.Errorf("scan line: %w", err) + } + + if strings.ToLower(response) != "y" && strings.ToLower(response) != "yes" { + return xerrors.New("request canceled") + } + + return nil +} + +func listSatellitesCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "ls", + Short: "list satellites.", + Long: "List all Coder workspace satellites.", + Example: `# list satellites +coder satellites ls`, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + client, err := newClient(ctx, true) + if err != nil { + return xerrors.Errorf("making coder client", err) + } + + sats, err := client.Satellites(ctx) + if err != nil { + return xerrors.Errorf("get satellites request", err) + } + + err = tablewriter.WriteTable(cmd.OutOrStdout(), len(sats.Data), func(i int) interface{} { + return sats.Data[i] + }) + if err != nil { + return xerrors.Errorf("write table: %w", err) + } + + return nil + }, + } + return cmd +} + +func deleteSatelliteCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "rm [satellite_name]", + Args: xcobra.ExactArgs(1), + Short: "remove a satellite.", + Long: "Remove an existing Coder satellite by name.", + Example: `# remove an existing satellite by name +coder satellites rm my-satellite`, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + name := args[0] + + client, err := newClient(ctx, true) + if err != nil { + return err + } + + sats, err := client.Satellites(ctx) + if err != nil { + return xerrors.Errorf("get satellites request", err) + } + + for _, sat := range sats.Data { + if sat.Name == name { + err = client.DeleteSatelliteByID(ctx, sat.ID) + if err != nil { + return xerrors.Errorf("delete satellites request", err) + } + clog.LogSuccess(fmt.Sprintf("satellite %s successfully deleted", name)) + + return nil + } + } + + return xerrors.Errorf("no satellite found by name '%s'", name) + }, + } + return cmd +} + + From 84c723169870f9e875133ee207e281e7d850f183 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 16 Jul 2021 19:46:28 +0000 Subject: [PATCH 2/8] lint and docs --- coder-sdk/satellite.go | 6 +++--- docs/coder.md | 1 + internal/cmd/cmd.go | 1 + internal/cmd/satellites.go | 31 ++++++++++++++----------------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/coder-sdk/satellite.go b/coder-sdk/satellite.go index 1dce8e7d..b9ed055a 100644 --- a/coder-sdk/satellite.go +++ b/coder-sdk/satellite.go @@ -6,9 +6,9 @@ import ( ) type Satellite struct { - ID string `json:"id"` - Name string `json:"name"` - Fingerprint string `json:"fingerprint"` + ID string `json:"id"` + Name string `json:"name"` + Fingerprint string `json:"fingerprint"` } type Satellites struct { diff --git a/docs/coder.md b/docs/coder.md index ab6254e8..17e7fa7f 100644 --- a/docs/coder.md +++ b/docs/coder.md @@ -16,6 +16,7 @@ coder provides a CLI for working with an existing Coder installation * [coder images](coder_images.md) - Manage Coder images * [coder login](coder_login.md) - Authenticate this client for future operations * [coder logout](coder_logout.md) - Remove local authentication credentials if any exist +* [coder satellites](coder_satellites.md) - Interact with Coder satellite deployments * [coder ssh](coder_ssh.md) - Enter a shell of execute a command over SSH into a Coder workspace * [coder sync](coder_sync.md) - Establish a one way directory sync to a Coder workspace * [coder tokens](coder_tokens.md) - manage Coder API tokens for the active user diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 38e5ae4f..4ad33209 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -40,6 +40,7 @@ func Make() *cobra.Command { genDocsCmd(app), agentCmd(), tunnelCmd(), + satellitesCmd(), ) app.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "show verbose output") return app diff --git a/internal/cmd/satellites.go b/internal/cmd/satellites.go index fdf983ba..26ac32fa 100644 --- a/internal/cmd/satellites.go +++ b/internal/cmd/satellites.go @@ -18,7 +18,7 @@ import ( ) const ( - satelliteKeyPath = "/api/private/satellites/key" + satelliteKeyPath = "/api/private/satellites/key" ) type satelliteKeyResponse struct { @@ -28,10 +28,9 @@ type satelliteKeyResponse struct { func satellitesCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "satellites", - Short: "Interact with Coder satellite deployments", - Long: "Perform operations on the Coder satellites for the platform.", - Hidden: true, + Use: "satellites", + Short: "Interact with Coder satellite deployments", + Long: "Perform operations on the Coder satellites for the platform.", } cmd.AddCommand( @@ -53,19 +52,19 @@ func createSatelliteCmd() *cobra.Command { coder satellites create eu-west https://eu-west-coder.com`, RunE: func(cmd *cobra.Command, args []string) error { var ( - ctx = cmd.Context() - name = args[0] + ctx = cmd.Context() + name = args[0] accessURL = args[1] ) client, err := newClient(ctx, true) if err != nil { - return xerrors.Errorf("making coder client", err) + return xerrors.Errorf("making coder client: %w", err) } sURL, err := url.Parse(accessURL) if err != nil { - return xerrors.Errorf("parsing satellite access url", err) + return xerrors.Errorf("parsing satellite access url: %w", err) } sURL.Path = satelliteKeyPath @@ -90,10 +89,10 @@ coder satellites create eu-west https://eu-west-coder.com`, } if keyRes.Key == "" { - return xerrors.Errorf("key field empty in response") + return xerrors.New("key field empty in response") } if keyRes.Fingerprint == "" { - return xerrors.Errorf("fingerprint field empty in response") + return xerrors.New("fingerprint field empty in response") } fmt.Printf(`The following satellite will be created: @@ -156,12 +155,12 @@ coder satellites ls`, client, err := newClient(ctx, true) if err != nil { - return xerrors.Errorf("making coder client", err) + return xerrors.Errorf("making coder client: %w", err) } sats, err := client.Satellites(ctx) if err != nil { - return xerrors.Errorf("get satellites request", err) + return xerrors.Errorf("get satellites request: %w", err) } err = tablewriter.WriteTable(cmd.OutOrStdout(), len(sats.Data), func(i int) interface{} { @@ -196,14 +195,14 @@ coder satellites rm my-satellite`, sats, err := client.Satellites(ctx) if err != nil { - return xerrors.Errorf("get satellites request", err) + return xerrors.Errorf("get satellites request: %w", err) } for _, sat := range sats.Data { if sat.Name == name { err = client.DeleteSatelliteByID(ctx, sat.ID) if err != nil { - return xerrors.Errorf("delete satellites request", err) + return xerrors.Errorf("delete satellites request: %w", err) } clog.LogSuccess(fmt.Sprintf("satellite %s successfully deleted", name)) @@ -216,5 +215,3 @@ coder satellites rm my-satellite`, } return cmd } - - From 20ab2c4c9779a64dfa11ab895c1197ad5c021712 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 16 Jul 2021 19:50:28 +0000 Subject: [PATCH 3/8] docs --- docs/coder_satellites.md | 27 +++++++++++++++++++++++++ docs/coder_satellites_create.md | 36 +++++++++++++++++++++++++++++++++ docs/coder_satellites_ls.md | 35 ++++++++++++++++++++++++++++++++ docs/coder_satellites_rm.md | 35 ++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 docs/coder_satellites.md create mode 100644 docs/coder_satellites_create.md create mode 100644 docs/coder_satellites_ls.md create mode 100644 docs/coder_satellites_rm.md diff --git a/docs/coder_satellites.md b/docs/coder_satellites.md new file mode 100644 index 00000000..2eaac5b9 --- /dev/null +++ b/docs/coder_satellites.md @@ -0,0 +1,27 @@ +## coder satellites + +Interact with Coder satellite deployments + +### Synopsis + +Perform operations on the Coder satellites for the platform. + +### Options + +``` + -h, --help help for satellites +``` + +### Options inherited from parent commands + +``` + -v, --verbose show verbose output +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder installation +* [coder satellites create](coder_satellites_create.md) - create a new satellite. +* [coder satellites ls](coder_satellites_ls.md) - list satellites. +* [coder satellites rm](coder_satellites_rm.md) - remove a satellite. + diff --git a/docs/coder_satellites_create.md b/docs/coder_satellites_create.md new file mode 100644 index 00000000..ef656ad5 --- /dev/null +++ b/docs/coder_satellites_create.md @@ -0,0 +1,36 @@ +## coder satellites create + +create a new satellite. + +### Synopsis + +Create a new Coder satellite. + +``` +coder satellites create [name] [satellite_access_url] [flags] +``` + +### Examples + +``` +# create a new satellite + +coder satellites create eu-west https://eu-west-coder.com +``` + +### Options + +``` + -h, --help help for create +``` + +### Options inherited from parent commands + +``` + -v, --verbose show verbose output +``` + +### SEE ALSO + +* [coder satellites](coder_satellites.md) - Interact with Coder satellite deployments + diff --git a/docs/coder_satellites_ls.md b/docs/coder_satellites_ls.md new file mode 100644 index 00000000..d2153685 --- /dev/null +++ b/docs/coder_satellites_ls.md @@ -0,0 +1,35 @@ +## coder satellites ls + +list satellites. + +### Synopsis + +List all Coder workspace satellites. + +``` +coder satellites ls [flags] +``` + +### Examples + +``` +# list satellites +coder satellites ls +``` + +### Options + +``` + -h, --help help for ls +``` + +### Options inherited from parent commands + +``` + -v, --verbose show verbose output +``` + +### SEE ALSO + +* [coder satellites](coder_satellites.md) - Interact with Coder satellite deployments + diff --git a/docs/coder_satellites_rm.md b/docs/coder_satellites_rm.md new file mode 100644 index 00000000..44669f6f --- /dev/null +++ b/docs/coder_satellites_rm.md @@ -0,0 +1,35 @@ +## coder satellites rm + +remove a satellite. + +### Synopsis + +Remove an existing Coder satellite by name. + +``` +coder satellites rm [satellite_name] [flags] +``` + +### Examples + +``` +# remove an existing satellite by name +coder satellites rm my-satellite +``` + +### Options + +``` + -h, --help help for rm +``` + +### Options inherited from parent commands + +``` + -v, --verbose show verbose output +``` + +### SEE ALSO + +* [coder satellites](coder_satellites.md) - Interact with Coder satellite deployments + From 56ed80e928b3e634e712062651d609d4c5767410 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 16 Jul 2021 19:55:48 +0000 Subject: [PATCH 4/8] better yes check --- internal/cmd/satellites.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/cmd/satellites.go b/internal/cmd/satellites.go index 26ac32fa..4d2c1d32 100644 --- a/internal/cmd/satellites.go +++ b/internal/cmd/satellites.go @@ -104,7 +104,7 @@ Public Key: Fingerprint: %s -Do you wish to continue? (y) +Do you wish to continue? (y/n) `, name, keyRes.Key, keyRes.Fingerprint) err = getConfirmation() if err != nil { @@ -136,7 +136,8 @@ func getConfirmation() error { return xerrors.Errorf("scan line: %w", err) } - if strings.ToLower(response) != "y" && strings.ToLower(response) != "yes" { + response = strings.ToLower(strings.TrimSpace(response)) + if response != "y" && response != "yes" { return xerrors.New("request canceled") } From 33c5ef3797874a46dd5083147a88c8c728275c9f Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 16 Jul 2021 19:57:15 +0000 Subject: [PATCH 5/8] better domain --- internal/cmd/satellites.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/satellites.go b/internal/cmd/satellites.go index 4d2c1d32..82acb367 100644 --- a/internal/cmd/satellites.go +++ b/internal/cmd/satellites.go @@ -49,7 +49,7 @@ func createSatelliteCmd() *cobra.Command { Long: "Create a new Coder satellite.", Example: `# create a new satellite -coder satellites create eu-west https://eu-west-coder.com`, +coder satellites create eu-west https://eu-west.coder.com`, RunE: func(cmd *cobra.Command, args []string) error { var ( ctx = cmd.Context() From 573ba5988e7e2b99f23cc7192a481601f1c252f8 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 16 Jul 2021 19:59:42 +0000 Subject: [PATCH 6/8] more docs --- docs/coder_satellites_create.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/coder_satellites_create.md b/docs/coder_satellites_create.md index ef656ad5..9ab18362 100644 --- a/docs/coder_satellites_create.md +++ b/docs/coder_satellites_create.md @@ -15,7 +15,7 @@ coder satellites create [name] [satellite_access_url] [flags] ``` # create a new satellite -coder satellites create eu-west https://eu-west-coder.com +coder satellites create eu-west https://eu-west.coder.com ``` ### Options From a6fc0897274489a5e4a0cf2e9368ebf4da6e647f Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 16 Jul 2021 21:49:21 +0000 Subject: [PATCH 7/8] add not found error --- internal/cmd/satellites.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/cmd/satellites.go b/internal/cmd/satellites.go index 82acb367..2b5d2dad 100644 --- a/internal/cmd/satellites.go +++ b/internal/cmd/satellites.go @@ -164,6 +164,10 @@ coder satellites ls`, return xerrors.Errorf("get satellites request: %w", err) } + if len(sats.Data) == 0 { + return xerrors.Errorf("no satellites found") + } + err = tablewriter.WriteTable(cmd.OutOrStdout(), len(sats.Data), func(i int) interface{} { return sats.Data[i] }) From 963b1a5e593a617638b61b5e0244cb6ee52a2263 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 16 Jul 2021 23:36:52 +0000 Subject: [PATCH 8/8] fix types --- coder-sdk/interface.go | 6 +++--- coder-sdk/satellite.go | 18 +++++++++++------- internal/cmd/satellites.go | 8 ++++---- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/coder-sdk/interface.go b/coder-sdk/interface.go index 268d3b97..dafb2114 100644 --- a/coder-sdk/interface.go +++ b/coder-sdk/interface.go @@ -242,11 +242,11 @@ type Client interface { // SetPolicyTemplate sets the workspace policy template SetPolicyTemplate(ctx context.Context, templateID string, templateScope TemplateScope, dryRun bool) (*SetPolicyTemplateResponse, error) - // Satellites fetches all satellitess known to the Coder control plane. - Satellites(ctx context.Context) (*Satellites, error) + // satellites fetches all satellitess known to the Coder control plane. + Satellites(ctx context.Context) ([]Satellite, error) // CreateSatellite creates a new satellite entity. - CreateSatellite(ctx context.Context, req CreateSatelliteReq) (*Satellites, error) + CreateSatellite(ctx context.Context, req CreateSatelliteReq) (*Satellite, error) // DeleteSatelliteByID deletes a satellite entity from the Coder control plane. DeleteSatelliteByID(ctx context.Context, id string) error diff --git a/coder-sdk/satellite.go b/coder-sdk/satellite.go index b9ed055a..975ee32d 100644 --- a/coder-sdk/satellite.go +++ b/coder-sdk/satellite.go @@ -11,18 +11,22 @@ type Satellite struct { Fingerprint string `json:"fingerprint"` } -type Satellites struct { +type satellites struct { Data []Satellite `json:"data"` } +type createSatelliteResponse struct { + Data Satellite `json:"data"` +} + // Satellites fetches all satellitess known to the Coder control plane. -func (c *DefaultClient) Satellites(ctx context.Context) (*Satellites, error) { - var res Satellites +func (c *DefaultClient) Satellites(ctx context.Context) ([]Satellite, error) { + var res satellites err := c.requestBody(ctx, http.MethodGet, "/api/private/satellites", nil, &res) if err != nil { return nil, err } - return &res, nil + return res.Data, nil } // CreateSatelliteReq defines the request parameters for creating a new satellite entity. @@ -32,13 +36,13 @@ type CreateSatelliteReq struct { } // CreateSatellite creates a new satellite entity. -func (c *DefaultClient) CreateSatellite(ctx context.Context, req CreateSatelliteReq) (*Satellites, error) { - var res Satellites +func (c *DefaultClient) CreateSatellite(ctx context.Context, req CreateSatelliteReq) (*Satellite, error) { + var res createSatelliteResponse err := c.requestBody(ctx, http.MethodPost, "/api/private/satellites", req, &res) if err != nil { return nil, err } - return &res, nil + return &res.Data, nil } // DeleteSatelliteByID deletes a satellite entity from the Coder control plane. diff --git a/internal/cmd/satellites.go b/internal/cmd/satellites.go index 2b5d2dad..982451f7 100644 --- a/internal/cmd/satellites.go +++ b/internal/cmd/satellites.go @@ -164,12 +164,12 @@ coder satellites ls`, return xerrors.Errorf("get satellites request: %w", err) } - if len(sats.Data) == 0 { + if len(sats) == 0 { return xerrors.Errorf("no satellites found") } - err = tablewriter.WriteTable(cmd.OutOrStdout(), len(sats.Data), func(i int) interface{} { - return sats.Data[i] + err = tablewriter.WriteTable(cmd.OutOrStdout(), len(sats), func(i int) interface{} { + return sats[i] }) if err != nil { return xerrors.Errorf("write table: %w", err) @@ -203,7 +203,7 @@ coder satellites rm my-satellite`, return xerrors.Errorf("get satellites request: %w", err) } - for _, sat := range sats.Data { + for _, sat := range sats { if sat.Name == name { err = client.DeleteSatelliteByID(ctx, sat.ID) if err != nil {