-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Add infractl wait command
#1768
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
base: master
Are you sure you want to change the base?
Changes from all commits
8f1637e
d2f2702
0359e8a
cae097c
6c26f58
85fb877
7f40427
c1d4ca5
67d3417
6364f4f
5caa302
7d5c885
2f1da5d
33243d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // Package wait implements the infractl wait command. | ||
| package wait | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| "github.com/stackrox/infra/cmd/infractl/cluster/utils" | ||
| "github.com/stackrox/infra/cmd/infractl/common" | ||
| v1 "github.com/stackrox/infra/generated/api/v1" | ||
| "google.golang.org/grpc" | ||
| ) | ||
|
|
||
| const examples = `Wait for the "example-s3maj" cluster to become ready. | ||
| $ infractl wait example-s3maj` | ||
|
|
||
| // Command defines the handler for infractl wait. | ||
| func Command() *cobra.Command { | ||
| // $ infractl wait | ||
| cmd := &cobra.Command{ | ||
| Use: "wait CLUSTER", | ||
| Short: "Wait for a specific cluster", | ||
| Long: "Wait for the the specific cluster to become ready.", | ||
| Example: examples, | ||
| Args: common.ArgsWithHelp(cobra.ExactArgs(1), args), | ||
| RunE: common.WithGRPCHandler(run), | ||
| } | ||
|
|
||
| common.AddMaxWaitErrorsFlag(cmd) | ||
|
|
||
| return cmd | ||
| } | ||
|
|
||
| func args(_ *cobra.Command, args []string) error { | ||
| if args[0] == "" { | ||
| return errors.New("no cluster ID given") | ||
| } | ||
| return utils.ValidateClusterName(args[0]) | ||
| } | ||
|
|
||
| func run(_ context.Context, conn *grpc.ClientConn, cmd *cobra.Command, args []string) (common.PrettyPrinter, error) { | ||
| maxWaitErrors := common.GetMaxWaitErrorsFlagValue(cmd) | ||
|
|
||
| client := v1.NewClusterServiceClient(conn) | ||
| err := common.WaitForCluster(client, &v1.ResourceByID{Id: args[0]}, maxWaitErrors) | ||
|
|
||
| return prettyNoop{}, err | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package wait | ||
|
|
||
| import ( | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| // prettyNoop does not output anything because 'wait' command does not need output. | ||
| type prettyNoop struct { | ||
| } | ||
|
|
||
| func (p prettyNoop) PrettyPrint(cmd *cobra.Command) { | ||
| cmd.Printf("\n") | ||
| } | ||
|
|
||
| func (p prettyNoop) PrettyJSONPrint(cmd *cobra.Command) error { | ||
| cmd.Printf("{}\n") | ||
| return nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package common | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "os" | ||
| "time" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| v1 "github.com/stackrox/infra/generated/api/v1" | ||
| ) | ||
|
|
||
| const ( | ||
| maxWaitErrorsFlagName = "wait-max-errors" | ||
| defaultMaxConsecutiveWaitErrors = 10 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. defaults of 10 * 30, right? 5 minutes may be too short for the default?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should it default to infinite (or functionally longer than any creation will allow) to match the existing behavior? Maybe it isn't used by anyone though and this is a good point to change the default to a sane time limit. |
||
| ) | ||
|
|
||
| // AddMaxWaitErrorsFlag adds a flag definition to cmd. | ||
| func AddMaxWaitErrorsFlag(cmd *cobra.Command) { | ||
| cmd.Flags().Int(maxWaitErrorsFlagName, defaultMaxConsecutiveWaitErrors, "maximum number of consecutive errors before giving up waiting") | ||
| } | ||
|
|
||
| // GetMaxWaitErrorsFlagValue gets effective value of the flag after arguments are parsed. | ||
| func GetMaxWaitErrorsFlagValue(cmd *cobra.Command) int { | ||
| value, err := cmd.Flags().GetInt(maxWaitErrorsFlagName) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| return value | ||
| } | ||
|
|
||
| // WaitForCluster waits for a created cluster to be in a ready state. | ||
| func WaitForCluster(client v1.ClusterServiceClient, clusterID *v1.ResourceByID, maxWaitErrors int) error { | ||
| const timeoutSleep = 30 * time.Second | ||
|
|
||
| nErrors := 0 | ||
|
|
||
| fmt.Fprintf(os.Stderr, "...waiting for %s\n", clusterID.Id) | ||
| for { | ||
| ctx, cancel := ContextWithTimeout() | ||
| cluster, err := client.Info(ctx, clusterID) | ||
| cancel() | ||
|
|
||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "...error %s\n", err) | ||
| nErrors++ | ||
| if nErrors >= maxWaitErrors { | ||
| return errors.New("too many errors while waiting") | ||
| } | ||
| } else { | ||
| nErrors = 0 | ||
| switch cluster.Status { | ||
| case v1.Status_CREATING: | ||
| fmt.Fprintln(os.Stderr, "...creating") | ||
| case v1.Status_READY: | ||
| fmt.Fprintln(os.Stderr, "...ready") | ||
| return nil | ||
| default: | ||
| fmt.Fprintln(os.Stderr, "...failed") | ||
| return errors.New("cluster failed provisioning") | ||
| } | ||
| } | ||
|
|
||
| time.Sleep(timeoutSleep) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| //go:build e2e | ||
| // +build e2e | ||
|
|
||
| package cluster_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stackrox/infra/test/utils" | ||
| "github.com/stackrox/infra/test/utils/mock" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestWait(t *testing.T) { | ||
| utils.CheckContext() | ||
| clusterID, err := mock.InfractlCreateCluster( | ||
| "test-simulate", | ||
| "--lifespan=60s", | ||
| ) | ||
| assert.NoError(t, err) | ||
|
|
||
| err = mock.InfractlWait(clusterID) | ||
| assert.NoError(t, err) | ||
|
|
||
| cluster, err := mock.InfractlGetCluster(clusterID) | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, "READY", cluster.Status) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice. I don't use the --wait. I think I don't because of it being unlimited and not being able to configure the retry interval.