Skip to content
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

Polish UX for sandbox commands #4

Merged
merged 11 commits into from
May 21, 2022
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ module github.com/signadot/cli
go 1.18

require (
github.com/docker/go-units v0.4.0
github.com/go-openapi/runtime v0.24.1
github.com/go-openapi/strfmt v0.21.2
github.com/signadot/go-sdk v0.1.2
github.com/spf13/cobra v1.4.0
github.com/spf13/viper v1.11.0
github.com/theckman/yacspin v0.13.12
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
sigs.k8s.io/yaml v1.3.0
)

require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-openapi/analysis v0.21.3 // indirect
github.com/go-openapi/errors v0.20.2 // indirect
Expand All @@ -29,11 +32,15 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
Expand Down
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,16 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
Expand Down Expand Up @@ -217,6 +220,14 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
Expand All @@ -241,6 +252,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
Expand Down Expand Up @@ -276,6 +289,8 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4=
github.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
Expand Down Expand Up @@ -413,6 +428,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -436,6 +452,7 @@ golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
40 changes: 4 additions & 36 deletions internal/command/cluster/list.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package cluster

import (
"encoding/json"
"fmt"
"io"

"github.com/signadot/cli/internal/config"
"github.com/signadot/cli/internal/sdtab"
"github.com/signadot/cli/internal/print"
"github.com/signadot/go-sdk/client/cluster"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
)

func newList(cluster *config.Cluster) *cobra.Command {
Expand All @@ -27,12 +25,6 @@ func newList(cluster *config.Cluster) *cobra.Command {
return cmd
}

type tableRow struct {
Name string `sdtab:"NAME"`
Created string `sdtab:"CREATED"`
Version string `sdtab:"OPERATOR VERSION"`
}

func list(cfg *config.ClusterList, out io.Writer) error {
if err := cfg.InitAPIConfig(); err != nil {
return err
Expand All @@ -45,36 +37,12 @@ func list(cfg *config.ClusterList, out io.Writer) error {

switch cfg.OutputFormat {
case config.OutputFormatDefault:
t := sdtab.New[tableRow](out)
t.AddHeader()
for _, cluster := range clusters {
row := tableRow{
Name: cluster.Name,
Created: cluster.CreatedAt,
Version: cluster.OperatorVersion,
}
t.AddRow(row)
}
if err := t.Flush(); err != nil {
return err
}
return print.ClusterTable(out, clusters)
case config.OutputFormatJSON:
enc := json.NewEncoder(out)
enc.SetIndent("", " ")
if err := enc.Encode(clusters); err != nil {
return err
}
return print.RawJSON(out, clusters)
case config.OutputFormatYAML:
data, err := yaml.Marshal(clusters)
if err != nil {
return err
}
if _, err := out.Write(data); err != nil {
return err
}
return print.RawYAML(out, clusters)
default:
return fmt.Errorf("unsupported output format: %q", cfg.OutputFormat)
}

return nil
}
61 changes: 46 additions & 15 deletions internal/command/sandbox/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (

"github.com/signadot/cli/internal/clio"
"github.com/signadot/cli/internal/config"
"github.com/signadot/cli/internal/poll"
"github.com/signadot/cli/internal/print"
"github.com/signadot/cli/internal/spinner"
"github.com/signadot/go-sdk/client/sandboxes"
"github.com/signadot/go-sdk/models"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -59,24 +62,52 @@ func create(cfg *config.SandboxCreate, out io.Writer) error {

if cfg.Wait {
// Wait for the sandbox to be ready.
fmt.Fprintln(out, "Waiting for sandbox to be ready...")

params := sandboxes.NewGetSandboxReadyParams().WithOrgName(cfg.Org).WithSandboxID(resp.SandboxID)

// We use a hot loop because the server implements rate-limiting for us.
for {
result, err := cfg.Client.Sandboxes.GetSandboxReady(params, nil)
if err != nil {
return err
}
if result.Payload.Ready {
break
}
// TODO: Show status message when it's added to the API response.
if err := waitForReady(cfg, out, resp.SandboxID); err != nil {
fmt.Fprintf(out, "\nThe sandbox was created, but it may not be ready yet. To check status, run:\n\n")
fmt.Fprintf(out, " signadot sandbox get-status %v\n\n", req.Name)
return err
}
}

fmt.Fprintln(out, "Sandbox is ready.")
// Print info on how to access the sandbox.
sbURL := cfg.SandboxDashboardURL(resp.SandboxID)
fmt.Fprintf(out, "\nDashboard page: %v\n\n", sbURL)

if len(resp.PreviewEndpoints) > 0 {
if err := print.PreviewEndpointTable(out, resp.PreviewEndpoints); err != nil {
return err
}
}

return nil
}

func waitForReady(cfg *config.SandboxCreate, out io.Writer, sandboxID string) error {
fmt.Fprintf(out, "Waiting (up to --wait-timeout=%v) for sandbox to be ready...\n", cfg.WaitTimeout)

params := sandboxes.NewGetSandboxReadyParams().WithOrgName(cfg.Org).WithSandboxID(sandboxID)

spin := spinner.Start(out, "Sandbox status")
defer spin.Stop()

err := poll.Until(cfg.WaitTimeout, func() bool {
result, err := cfg.Client.Sandboxes.GetSandboxReady(params, nil)
if err != nil {
// Keep retrying in case it's a transient error.
spin.Messagef("error: %v", err)
return false
}
if !result.Payload.Ready {
// TODO: Show status message when it's added to the API response.
spin.Message("Not Ready")
return false
}
spin.StopMessage("Ready")
return true
})
if err != nil {
spin.StopFail()
return err
}
return nil
}
51 changes: 50 additions & 1 deletion internal/command/sandbox/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import (
"errors"
"fmt"
"io"
"strings"

"github.com/signadot/cli/internal/clio"
"github.com/signadot/cli/internal/config"
"github.com/signadot/cli/internal/poll"
"github.com/signadot/cli/internal/spinner"
"github.com/signadot/go-sdk/client/sandboxes"
"github.com/signadot/go-sdk/models"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -79,7 +82,53 @@ func delete(cfg *config.SandboxDelete, out io.Writer, args []string) error {
return err
}

fmt.Fprintf(out, "Deleted sandbox %q.\n", name)
fmt.Fprintf(out, "Deleted sandbox %q.\n\n", name)

if cfg.Wait {
// Wait for the API server to completely reflect deletion.
if err := waitForDeleted(cfg, out, id); err != nil {
fmt.Fprintf(out, "\nDeletion was initiated, but the sandbox may still exist in a terminating state. To check status, run:\n\n")
fmt.Fprintf(out, " signadot sandbox get-status %v\n\n", name)
return err
}
}

return nil
}

func waitForDeleted(cfg *config.SandboxDelete, out io.Writer, sandboxID string) error {
fmt.Fprintf(out, "Waiting (up to --wait-timeout=%v) for sandbox to finish terminating...\n", cfg.WaitTimeout)

params := sandboxes.NewGetSandboxReadyParams().WithOrgName(cfg.Org).WithSandboxID(sandboxID)

spin := spinner.Start(out, "Sandbox status")
defer spin.Stop()

err := poll.Until(cfg.WaitTimeout, func() bool {
result, err := cfg.Client.Sandboxes.GetSandboxReady(params, nil)
if err != nil {
// If it's a "not found" error, that's what we wanted.
// TODO: Pass through an error code so we don't have to rely on the error message.
if strings.Contains(err.Error(), "can't get sandbox status: not found") {
spin.StopMessage("Terminated")
return true
}

// Otherwise, keep retrying in case it's a transient error.
spin.Messagef("error: %v", err)
return false
}
if result.Payload.Ready {
spin.Message("Ready")
return false
}
// TODO: Show status message when it's added to the API response.
spin.Message("Terminating")
return false
})
if err != nil {
spin.StopFail()
return err
}
return nil
}
33 changes: 4 additions & 29 deletions internal/command/sandbox/get.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package sandbox

import (
"encoding/json"
"fmt"
"io"

"github.com/signadot/cli/internal/config"
"github.com/signadot/cli/internal/sdtab"
"github.com/signadot/cli/internal/print"
"github.com/signadot/go-sdk/client/sandboxes"
"github.com/signadot/go-sdk/models"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
)

func newGet(sandbox *config.Sandbox) *cobra.Command {
Expand Down Expand Up @@ -50,35 +48,12 @@ func get(cfg *config.SandboxGet, out io.Writer, name string) error {

switch cfg.OutputFormat {
case config.OutputFormatDefault:
t := sdtab.New[tableRow](out)
t.AddHeader()
row := tableRow{
Name: sb.Name,
Description: sb.Description,
Cluster: sb.ClusterName,
Created: sb.CreatedAt,
}
t.AddRow(row)
if err := t.Flush(); err != nil {
return err
}
return print.SandboxDetails(cfg, out, sb)
case config.OutputFormatJSON:
enc := json.NewEncoder(out)
enc.SetIndent("", " ")
if err := enc.Encode(sb); err != nil {
return err
}
return print.RawJSON(out, sb)
case config.OutputFormatYAML:
data, err := yaml.Marshal(sb)
if err != nil {
return err
}
if _, err := out.Write(data); err != nil {
return err
}
return print.RawYAML(out, sb)
default:
return fmt.Errorf("unsupported output format: %q", cfg.OutputFormat)
}

return nil
}
Loading