shell: use tabwriter for aligned output, remove fragile string error checks

- Use text/tabwriter in runS3UserList and runS3UserAccessKeyList for
  properly aligned column output
- Remove fragile strings.Contains "not found" and "already exists"
  fallbacks in s3ShellIsNotFound and s3ShellIsAlreadyExists; rely on
  typed errors and gRPC status codes only
- Update test assertions to match tabwriter-formatted output
This commit is contained in:
Chris Lu
2026-04-07 11:24:42 -07:00
parent d4261c2271
commit 08dbfce493
2 changed files with 15 additions and 12 deletions
+12 -9
View File
@@ -9,6 +9,7 @@ import (
"io"
"sort"
"strings"
"text/tabwriter"
"time"
"github.com/seaweedfs/seaweedfs/weed/credential"
@@ -322,13 +323,14 @@ func runS3UserList(ctx context.Context, store s3ShellStore, writer io.Writer) er
}
sort.Strings(usernames)
fmt.Fprintln(writer, "NAME\tSOURCE\tSTATUS\tACCESS KEYS\tPOLICIES")
tw := tabwriter.NewWriter(writer, 0, 0, 2, ' ', 0)
fmt.Fprintln(tw, "NAME\tSOURCE\tSTATUS\tACCESS KEYS\tPOLICIES")
for _, username := range usernames {
identity, err := store.GetUser(ctx, username)
if err != nil {
return err
}
fmt.Fprintf(writer, "%s\t%s\t%s\t%d\t%d\n",
fmt.Fprintf(tw, "%s\t%s\t%s\t%d\t%d\n",
identity.Name,
s3IdentitySource(identity),
s3IdentityStatus(identity),
@@ -336,7 +338,7 @@ func runS3UserList(ctx context.Context, store s3ShellStore, writer io.Writer) er
len(identity.PolicyNames),
)
}
return nil
return tw.Flush()
}
func runS3UserShow(ctx context.Context, store s3ShellStore, username string, writer io.Writer) error {
@@ -441,14 +443,15 @@ func runS3UserAccessKeyList(ctx context.Context, store s3ShellStore, username st
return err
}
fmt.Fprintf(writer, "USER\tACCESS KEY\tSTATUS\n")
tw := tabwriter.NewWriter(writer, 0, 0, 2, ' ', 0)
fmt.Fprintln(tw, "USER\tACCESS KEY\tSTATUS")
for _, credential := range sortedCredentials(identity.Credentials) {
fmt.Fprintf(writer, "%s\t%s\t%s\n", username, credential.AccessKey, s3CredentialStatus(credential))
fmt.Fprintf(tw, "%s\t%s\t%s\n", username, credential.AccessKey, s3CredentialStatus(credential))
}
if len(identity.Credentials) == 0 {
fmt.Fprintf(writer, "%s\t%s\t%s\n", username, "-", "-")
fmt.Fprintf(tw, "%s\t%s\t%s\n", username, "-", "-")
}
return nil
return tw.Flush()
}
func runS3UserAccessKeyCreate(ctx context.Context, store s3ShellStore, opts s3AccessKeyCreateOptions, writer io.Writer) error {
@@ -685,7 +688,7 @@ func s3ShellIsNotFound(err error) bool {
if st, ok := status.FromError(err); ok && st.Code() == codes.NotFound {
return true
}
return strings.Contains(strings.ToLower(err.Error()), "not found")
return false
}
func s3ShellIsAlreadyExists(err error) bool {
@@ -698,7 +701,7 @@ func s3ShellIsAlreadyExists(err error) bool {
if st, ok := status.FromError(err); ok && st.Code() == codes.AlreadyExists {
return true
}
return strings.Contains(strings.ToLower(err.Error()), "already exists")
return false
}
func ternary[T any](cond bool, onTrue, onFalse T) T {
@@ -100,13 +100,13 @@ func TestRunS3UserListIncludesSourceAndStatus(t *testing.T) {
}
output := out.String()
if !strings.Contains(output, "NAME\tSOURCE\tSTATUS\tACCESS KEYS\tPOLICIES") {
if !strings.Contains(output, "NAME") || !strings.Contains(output, "SOURCE") || !strings.Contains(output, "STATUS") {
t.Fatalf("expected header, got %q", output)
}
if !strings.Contains(output, "alice\tdynamic\tenabled\t1\t0") {
if !strings.Contains(output, "alice") || !strings.Contains(output, "dynamic") || !strings.Contains(output, "enabled") {
t.Fatalf("expected dynamic user row, got %q", output)
}
if !strings.Contains(output, "bootstrap-admin\tstatic\tdisabled\t0\t0") {
if !strings.Contains(output, "bootstrap-admin") || !strings.Contains(output, "static") || !strings.Contains(output, "disabled") {
t.Fatalf("expected static user row, got %q", output)
}
}