Skip to content

Commit 5d3ac9b

Browse files
authored
Merge pull request #29078 from vbotbuildovich/backport-pr-29060-v25.3.x-930
[v25.3.x] [UX-755] rpk: add spinner for long-running operations
2 parents 0a7b2c6 + 91f75cf commit 5d3ac9b

File tree

9 files changed

+362
-33
lines changed

9 files changed

+362
-33
lines changed

MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ use_repo(
258258
"com_github_avast_retry_go",
259259
"com_github_aws_aws_sdk_go",
260260
"com_github_beevik_ntp",
261+
"com_github_briandowns_spinner",
261262
"com_github_bufbuild_protocompile",
262263
"com_github_cespare_xxhash",
263264
"com_github_containerd_errdefs",

src/go/rpk/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/avast/retry-go v3.0.0+incompatible
2121
github.com/aws/aws-sdk-go v1.55.6
2222
github.com/beevik/ntp v1.5.0
23+
github.com/briandowns/spinner v1.23.2
2324
github.com/bufbuild/protocompile v0.14.1
2425
github.com/cespare/xxhash v1.1.0
2526
github.com/containerd/errdefs v1.0.0

src/go/rpk/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn
4444
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
4545
github.com/beevik/ntp v1.5.0 h1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=
4646
github.com/beevik/ntp v1.5.0/go.mod h1:mJEhBrwT76w9D+IfOEGvuzyuudiW9E52U2BaTrMOYow=
47+
github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w=
48+
github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
4749
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
4850
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
4951
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=

src/go/rpk/pkg/cli/shadow/create.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"context"
1414
"errors"
1515
"fmt"
16+
"os"
1617
"strings"
1718

1819
"buf.build/gen/go/redpandadata/cloud/connectrpc/go/redpanda/api/controlplane/v1/controlplanev1connect"
@@ -86,7 +87,6 @@ Create a Shadow Link without confirmation prompt:
8687
out.Exit("Shadow Link creation cancelled")
8788
}
8889
}
89-
fmt.Println()
9090

9191
successMsgTmpl := "Successfully created shadow link %q with ID %q. To query the status, run:\n 'rpk shadow status %[1]v'"
9292
if prof.CheckFromCloud() {
@@ -103,24 +103,31 @@ Create a Shadow Link without confirmation prompt:
103103
}))
104104
out.MaybeDie(err, "unable to create Shadow Link: %v", err)
105105

106+
spinner := out.NewSpinner(cmd.Context(), "Creating Shadow Link...", out.WithElapsedTime())
106107
isComplete, err := waitForOperation(cmd.Context(), cloudClient, op.Msg.GetOperation().GetId())
107108
if err != nil {
108109
if oErr := new(OperationFailedError); errors.As(err, &oErr) {
109-
out.Die(tryShadowLinkErrReason(cmd.Context(), cloudClient.ShadowLink, oErr))
110+
spinner.Fail(tryShadowLinkErrReason(cmd.Context(), cloudClient.ShadowLink, oErr))
111+
os.Exit(1)
110112
}
111-
out.Die("unable to confirm Shadow Link creation: %v", err)
113+
spinner.Fail(fmt.Sprintf("unable to confirm Shadow Link creation: %v", err))
114+
os.Exit(1)
112115
}
113116
if isComplete {
114-
out.Exit(successMsgTmpl, slCfg.Name, op.Msg.GetOperation().GetResourceId())
117+
spinner.Success(fmt.Sprintf(successMsgTmpl, slCfg.Name, op.Msg.GetOperation().GetResourceId()))
118+
os.Exit(0)
115119
}
120+
spinner.Stop()
116121
out.Exit("Shadow link creation is taking longer than expected. Please check the state of the shadow link using 'rpk shadow describe %v' and 'rpk shadow status %v'", slCfg.Name, slCfg.Name)
117122
}
118123
cl, err := adminapi.NewClient(cmd.Context(), fs, prof)
119124
out.MaybeDie(err, "unable to initialize admin client: %v", err)
120125

126+
spinner := out.NewSpinner(cmd.Context(), "Creating Shadow Link...")
121127
link, err := cl.ShadowLinkService().CreateShadowLink(cmd.Context(), connect.NewRequest(&adminv2.CreateShadowLinkRequest{
122128
ShadowLink: shadowLinkConfigToProto(slCfg),
123129
}))
130+
spinner.Stop()
124131
out.MaybeDie(err, "unable to create shadow link: %v", handleConnectError(err, "create", slCfg.Name))
125132

126133
out.Exit(successMsgTmpl, link.Msg.GetShadowLink().GetName(), link.Msg.GetShadowLink().GetUid())

src/go/rpk/pkg/cli/shadow/delete.go

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package shadow
1111

1212
import (
1313
"fmt"
14+
"os"
1415
"strings"
1516

1617
controlplanev1 "buf.build/gen/go/redpandadata/cloud/protocolbuffers/go/redpanda/api/controlplane/v1"
@@ -100,28 +101,35 @@ Force delete a Shadow Link with active shadow topics:
100101
}))
101102
out.MaybeDie(err, "unable to delete Shadow Link: %v", err)
102103

104+
spinner := out.NewSpinner(cmd.Context(), "Deleting Shadow Link...", out.WithElapsedTime())
103105
isComplete, err := waitForOperation(cmd.Context(), cloudClient, op.Msg.GetOperation().GetId())
104-
out.MaybeDie(err, "unable to confirm Shadow Link deletion: %v", err)
106+
if err != nil {
107+
spinner.Fail(fmt.Sprintf("unable to confirm Shadow Link deletion: %v", err))
108+
os.Exit(1)
109+
}
105110
if !isComplete {
111+
spinner.Stop()
106112
out.Exit("Shadow link deletion is taking longer than expected. Please check the status of the shadow link using 'rpk shadow status %q'", linkName)
107113
}
108-
} else {
109-
cl, err := adminapi.NewClient(cmd.Context(), fs, prof)
110-
out.MaybeDie(err, "unable to initialize admin client: %v", err)
111-
112-
link, err := cl.ShadowLinkService().GetShadowLink(cmd.Context(), connect.NewRequest(&adminv2.GetShadowLinkRequest{
113-
Name: linkName,
114-
}))
115-
out.MaybeDie(err, "unable to get Redpanda Shadow Link information: %v", handleConnectError(err, "get", linkName))
116-
printShadowLinkInfo(link.Msg.GetShadowLink())
117-
promptConfirm(false)
118-
119-
_, err = cl.ShadowLinkService().DeleteShadowLink(cmd.Context(), connect.NewRequest(&adminv2.DeleteShadowLinkRequest{
120-
Name: linkName,
121-
Force: forceDelete,
122-
}))
123-
out.MaybeDie(err, "unable to delete Redpanda Shadow Link %q: %v", linkName, handleConnectError(err, "delete", linkName))
114+
spinner.Success(fmt.Sprintf("Shadow Link %q deleted successfully", linkName))
115+
os.Exit(0)
124116
}
117+
// Self-hosted path
118+
cl, err := adminapi.NewClient(cmd.Context(), fs, prof)
119+
out.MaybeDie(err, "unable to initialize admin client: %v", err)
120+
121+
link, err := cl.ShadowLinkService().GetShadowLink(cmd.Context(), connect.NewRequest(&adminv2.GetShadowLinkRequest{
122+
Name: linkName,
123+
}))
124+
out.MaybeDie(err, "unable to get Redpanda Shadow Link information: %v", handleConnectError(err, "get", linkName))
125+
printShadowLinkInfo(link.Msg.GetShadowLink())
126+
promptConfirm(false)
127+
128+
_, err = cl.ShadowLinkService().DeleteShadowLink(cmd.Context(), connect.NewRequest(&adminv2.DeleteShadowLinkRequest{
129+
Name: linkName,
130+
Force: forceDelete,
131+
}))
132+
out.MaybeDie(err, "unable to delete Redpanda Shadow Link %q: %v", linkName, handleConnectError(err, "delete", linkName))
125133

126134
fmt.Printf("Shadow Link %q deleted successfully\n", linkName)
127135
},

src/go/rpk/pkg/cli/shadow/update.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package shadow
1111

1212
import (
1313
"fmt"
14+
"os"
1415
"reflect"
1516
"strings"
1617

@@ -142,23 +143,30 @@ Update a Shadow Link configuration:
142143
UpdateMask: fm,
143144
}))
144145
out.MaybeDie(err, "unable to update Shadow Link: %v", handleConnectError(err, "update", linkName))
146+
spinner := out.NewSpinner(cmd.Context(), "Updating Shadow Link...")
145147
isComplete, err := waitForOperation(cmd.Context(), cloudClient, op.Msg.GetOperation().GetId())
146-
out.MaybeDie(err, "unable to confirm Shadow Link update: %v", err)
148+
if err != nil {
149+
spinner.Fail(fmt.Sprintf("unable to confirm Shadow Link update: %v", err))
150+
os.Exit(1)
151+
}
147152
if !isComplete {
153+
spinner.Stop()
148154
out.Exit("Shadow link update is taking longer than expected. Please check the status of the shadow link using 'rpk shadow status %q'", linkName)
149155
}
150-
} else {
151-
updatedSL := shadowLinkConfigToProto(updatedCfg)
152-
fm, err := fieldmaskpb.New(updatedSL, diff...)
153-
out.MaybeDie(err, "unrecognized changed fields: %v; please report this with Redpanda Support", err)
154-
155-
zap.L().Sugar().Debugf("Requesting configuration update for: %v", strings.Join(diff, ", "))
156-
_, err = adminClient.ShadowLinkService().UpdateShadowLink(cmd.Context(), connect.NewRequest(&adminv2.UpdateShadowLinkRequest{
157-
ShadowLink: updatedSL,
158-
UpdateMask: fm,
159-
}))
160-
out.MaybeDie(err, "unable to update Shadow Link: %v", handleConnectError(err, "update", linkName))
156+
spinner.Success(fmt.Sprintf("Successfully updated shadow link %q", linkName))
157+
os.Exit(0)
161158
}
159+
// Self-hosted path
160+
updatedSL := shadowLinkConfigToProto(updatedCfg)
161+
fm, err := fieldmaskpb.New(updatedSL, diff...)
162+
out.MaybeDie(err, "unrecognized changed fields: %v; please report this with Redpanda Support", err)
163+
164+
zap.L().Sugar().Debugf("Requesting configuration update for: %v", strings.Join(diff, ", "))
165+
_, err = adminClient.ShadowLinkService().UpdateShadowLink(cmd.Context(), connect.NewRequest(&adminv2.UpdateShadowLinkRequest{
166+
ShadowLink: updatedSL,
167+
UpdateMask: fm,
168+
}))
169+
out.MaybeDie(err, "unable to update Shadow Link: %v", handleConnectError(err, "update", linkName))
162170
fmt.Printf("Successfully updated shadow link %q.\n", linkName)
163171
},
164172
}

src/go/rpk/pkg/out/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ go_library(
66
"color.go",
77
"in.go",
88
"out.go",
9+
"spinner.go",
910
],
1011
importpath = "github.com/redpanda-data/redpanda/src/go/rpk/pkg/out",
1112
visibility = ["//visibility:public"],
1213
deps = [
1314
"@com_github_alecaivazis_survey_v2//:survey",
15+
"@com_github_briandowns_spinner//:spinner",
1416
"@com_github_fatih_color//:color",
17+
"@com_github_mattn_go_isatty//:go-isatty",
1518
"@com_github_spf13_afero//:afero",
1619
"@com_github_twmb_franz_go_pkg_kadm//:kadm",
1720
"@in_gopkg_yaml_v3//:yaml_v3",
@@ -24,6 +27,7 @@ go_test(
2427
srcs = [
2528
"in_test.go",
2629
"out_test.go",
30+
"spinner_test.go",
2731
],
2832
embed = [":out"],
2933
deps = [

0 commit comments

Comments
 (0)