diff --git a/go.mod b/go.mod index 9c9b28f25..b6dcde1f4 100644 --- a/go.mod +++ b/go.mod @@ -76,7 +76,7 @@ require ( k8s.io/apimachinery v0.29.3 k8s.io/apiserver v0.29.3 oras.land/oras-go/v2 v2.5.0 - sdk.kraft.cloud v0.5.8 + sdk.kraft.cloud v0.5.9-0.20240415081115-907e7fb9726d sigs.k8s.io/kustomize/kyaml v0.17.0 ) diff --git a/go.sum b/go.sum index 5a0dacd95..444807df6 100644 --- a/go.sum +++ b/go.sum @@ -1678,8 +1678,8 @@ oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZH rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sdk.kraft.cloud v0.5.8 h1:KIBEYFRrn1j55XYfyCSHCC7M+uAvgZ68M1nmU1zMBIM= -sdk.kraft.cloud v0.5.8/go.mod h1:kodfr24CDvcfDwU79Qlu4RWwQ46adUzKy7XI5qroxWU= +sdk.kraft.cloud v0.5.9-0.20240415081115-907e7fb9726d h1:eWhwKe4AX6fDD0mEIhNhaLyf+tAhfz2Tq6S4qbhD87w= +sdk.kraft.cloud v0.5.9-0.20240415081115-907e7fb9726d/go.mod h1:kodfr24CDvcfDwU79Qlu4RWwQ46adUzKy7XI5qroxWU= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 h1:TgtAeesdhpm2SGwkQasmbeqDo8th5wOBA5h/AjTKA4I= diff --git a/internal/cli/kraft/cloud/instance/create/create.go b/internal/cli/kraft/cloud/instance/create/create.go index a296c2df0..d30dc436f 100644 --- a/internal/cli/kraft/cloud/instance/create/create.go +++ b/internal/cli/kraft/cloud/instance/create/create.go @@ -31,29 +31,30 @@ import ( ) type CreateOptions struct { - Auth *config.AuthConfig `noattribute:"true"` - Client kraftcloud.KraftCloud `noattribute:"true"` - Env []string `local:"true" long:"env" short:"e" usage:"Environmental variables"` - Features []string `local:"true" long:"feature" short:"f" usage:"List of features to enable"` - Domain []string `local:"true" long:"domain" short:"d" usage:"The domain names to use for the service"` - Image string `noattribute:"true"` - Memory int `local:"true" long:"memory" short:"M" usage:"Specify the amount of memory to allocate (MiB)"` - Metro string `noattribute:"true"` - Name string `local:"true" long:"name" short:"n" usage:"Specify the name of the instance"` - Output string `local:"true" long:"output" short:"o" usage:"Set output format. Options: table,yaml,json,list" default:"table"` - Ports []string `local:"true" long:"port" short:"p" usage:"Specify the port mapping between external to internal"` - Replicas int `local:"true" long:"replicas" short:"R" usage:"Number of replicas of the instance" default:"0"` - Rollout RolloutStrategy `noattribute:"true"` - RolloutQualifier RolloutQualifier `noattribute:"true"` - RolloutWait time.Duration `local:"true" long:"rollout-wait" usage:"Time to wait before performing rolling out action" default:"10s"` - ServiceGroupNameOrUUID string `local:"true" long:"service-group" short:"g" usage:"Attach this instance to an existing service group"` - Start bool `local:"true" long:"start" short:"S" usage:"Immediately start the instance after creation"` - ScaleToZero bool `local:"true" long:"scale-to-zero" short:"0" usage:"Scale the instance to zero after deployment"` - SubDomain []string `local:"true" long:"subdomain" short:"s" usage:"Set the subdomains to use when creating the service"` - Token string `noattribute:"true"` - Volumes []string `local:"true" long:"volumes" short:"v" usage:"List of volumes to attach instance to"` - WaitForImage bool `local:"true" long:"wait-for-image" short:"w" usage:"Wait for the image to be available before creating the instance"` - WaitForImageTimeout time.Duration `local:"true" long:"wait-for-image-timeout" usage:"Time to wait before timing out when waiting for image" default:"60s"` + Auth *config.AuthConfig `noattribute:"true"` + Client kraftcloud.KraftCloud `noattribute:"true"` + Env []string `local:"true" long:"env" short:"e" usage:"Environmental variables"` + Features []string `local:"true" long:"feature" short:"f" usage:"List of features to enable"` + Domain []string `local:"true" long:"domain" short:"d" usage:"The domain names to use for the service"` + Image string `noattribute:"true"` + Memory int `local:"true" long:"memory" short:"M" usage:"Specify the amount of memory to allocate (MiB)"` + Metro string `noattribute:"true"` + Name string `local:"true" long:"name" short:"n" usage:"Specify the name of the instance"` + Output string `local:"true" long:"output" short:"o" usage:"Set output format. Options: table,yaml,json,list" default:"table"` + Ports []string `local:"true" long:"port" short:"p" usage:"Specify the port mapping between external to internal"` + RestartPolicy kcinstances.RestartPolicy `noattribute:"true"` + Replicas int `local:"true" long:"replicas" short:"R" usage:"Number of replicas of the instance" default:"0"` + Rollout RolloutStrategy `noattribute:"true"` + RolloutQualifier RolloutQualifier `noattribute:"true"` + RolloutWait time.Duration `local:"true" long:"rollout-wait" usage:"Time to wait before performing rolling out action" default:"10s"` + ServiceGroupNameOrUUID string `local:"true" long:"service-group" short:"g" usage:"Attach this instance to an existing service group"` + Start bool `local:"true" long:"start" short:"S" usage:"Immediately start the instance after creation"` + ScaleToZero bool `local:"true" long:"scale-to-zero" short:"0" usage:"Scale the instance to zero after deployment"` + SubDomain []string `local:"true" long:"subdomain" short:"s" usage:"Set the subdomains to use when creating the service"` + Token string `noattribute:"true"` + Volumes []string `local:"true" long:"volumes" short:"v" usage:"List of volumes to attach instance to"` + WaitForImage bool `local:"true" long:"wait-for-image" short:"w" usage:"Wait for the image to be available before creating the instance"` + WaitForImageTimeout time.Duration `local:"true" long:"wait-for-image-timeout" usage:"Time to wait before timing out when waiting for image" default:"60s"` } // Create a KraftCloud instance. @@ -161,9 +162,10 @@ func Create(ctx context.Context, opts *CreateOptions, args ...string) (*kcclient } req := kcinstances.CreateRequest{ - Autostart: &opts.Start, - Features: features, - Image: opts.Image, + Autostart: &opts.Start, + Features: features, + Image: opts.Image, + RestartPolicy: &opts.RestartPolicy, } if opts.Name != "" { req.Name = &opts.Name @@ -616,7 +618,7 @@ func NewCmd() *cobra.Command { StrategyPrompt, ), "rollout", - "Set the rollout strategy for an instance which has been previously run in the provided service group.", + "Set the rollout strategy for an instance which has been previously run in the provided service group", ) cmd.Flags().Var( @@ -625,7 +627,16 @@ func NewCmd() *cobra.Command { RolloutQualifierImageName, ), "rollout-qualifier", - "Set the rollout qualifier used to determine which instances should be affected by the strategy in the supplied service group.", + "Set the rollout qualifier used to determine which instances should be affected by the strategy in the supplied service group", + ) + + cmd.Flags().Var( + cmdfactory.NewEnumFlag[kcinstances.RestartPolicy]( + kcinstances.RestartPolicies(), + kcinstances.RestartPolicyNever, + ), + "restart", + "Set the restart policy for the instance (never/always/on-failure)", ) return cmd diff --git a/internal/cli/kraft/cloud/utils/print.go b/internal/cli/kraft/cloud/utils/print.go index 7639bb15e..771234cee 100644 --- a/internal/cli/kraft/cloud/utils/print.go +++ b/internal/cli/kraft/cloud/utils/print.go @@ -64,6 +64,22 @@ var ( } ) +func parseTime(dateTime, format, uuid string) (string, error) { + if len(dateTime) > 0 { + createdTime, err := time.Parse(time.RFC3339, dateTime) + if err != nil { + return "", fmt.Errorf("could not parse time for '%s': %w", uuid, err) + } + if format != "table" { + return dateTime, nil + } else { + return humanize.Time(createdTime), nil + } + } + + return "", nil +} + // PrintInstances pretty-prints the provided set of instances or returns // an error if unable to send to stdout via the provided context. func PrintInstances(ctx context.Context, format string, resp kcclient.ServiceResponse[kcinstances.GetResponseItem]) error { @@ -99,6 +115,15 @@ func PrintInstances(ctx context.Context, format string, resp kcclient.ServiceRes } table.AddField("STATE", cs.Bold) table.AddField("CREATED AT", cs.Bold) + if format != "table" { + table.AddField("STARTED AT", cs.Bold) + table.AddField("STOPPED AT", cs.Bold) + table.AddField("START COUNT", cs.Bold) + table.AddField("RESTART COUNT", cs.Bold) + table.AddField("RESTART ATTEMPTS", cs.Bold) + table.AddField("RESTART NEXT AT", cs.Bold) + } + table.AddField("RESTART POLICY", cs.Bold) table.AddField("IMAGE", cs.Bold) table.AddField("MEMORY", cs.Bold) table.AddField("ARGS", cs.Bold) @@ -108,6 +133,7 @@ func PrintInstances(ctx context.Context, format string, resp kcclient.ServiceRes table.AddField("SERVICE GROUP", cs.Bold) } table.AddField("BOOT TIME", cs.Bold) + table.AddField("UP TIME", cs.Bold) table.EndRow() if config.G[config.KraftKit](ctx).NoColor { @@ -143,17 +169,29 @@ func PrintInstances(ctx context.Context, format string, resp kcclient.ServiceRes } var createdAt string + var stoppedAt string + var startedAt string + var restartNextAt string - if len(instance.CreatedAt) > 0 { - createdTime, err := time.Parse(time.RFC3339, instance.CreatedAt) + createdAt, err = parseTime(instance.CreatedAt, format, instance.UUID) + if err != nil { + return err + } + startedAt, err = parseTime(instance.StartedAt, format, instance.UUID) + if err != nil { + return err + } + stoppedAt, err = parseTime(instance.StoppedAt, format, instance.UUID) + if err != nil { + return err + } + if instance.Restart != nil { + restartNextAt, err = parseTime(instance.Restart.NextAt, format, instance.UUID) if err != nil { - return fmt.Errorf("could not parse time for '%s': %w", instance.UUID, err) - } - if format != "table" { - createdAt = instance.CreatedAt - } else { - createdAt = humanize.Time(createdTime) + return err } + } else { + restartNextAt = "" } if format != "table" { @@ -175,6 +213,21 @@ func PrintInstances(ctx context.Context, format string, resp kcclient.ServiceRes table.AddField(string(instance.State), instanceStateColor[instance.State]) table.AddField(createdAt, nil) + + if format != "table" { + table.AddField(startedAt, nil) + table.AddField(stoppedAt, nil) + table.AddField(strconv.Itoa(instance.StartCount), nil) + table.AddField(strconv.Itoa(instance.RestartCount), nil) + if instance.Restart != nil { + table.AddField(strconv.Itoa(instance.Restart.Attempt), nil) + table.AddField(restartNextAt, nil) + } else { + table.AddField("", nil) + table.AddField("", nil) + } + } + table.AddField(string(instance.RestartPolicy), nil) table.AddField(instance.Image, nil) table.AddField(humanize.IBytes(uint64(instance.MemoryMB)*humanize.MiByte), nil) table.AddField(strings.Join(instance.Args, " "), nil) @@ -203,6 +256,12 @@ func PrintInstances(ctx context.Context, format string, resp kcclient.ServiceRes table.AddField(fmt.Sprintf("%.2f ms", float64(instance.BootTimeUs)/1000), nil) + duration, err := time.ParseDuration(fmt.Sprintf("%dms", instance.UptimeMs)) + if err != nil { + return fmt.Errorf("could not parse uptime for '%s': %w", instance.UUID, err) + } + table.AddField(duration.String(), nil) + table.EndRow() }