Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/go/rpk/pkg/cli/cloud/auth/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@rules_go//go:def.bzl", "go_library")
load("@rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "auth",
Expand All @@ -21,3 +21,14 @@ go_library(
"@com_github_spf13_cobra//:cobra",
],
)

go_test(
name = "auth_test",
srcs = ["list_test.go"],
embed = [":auth"],
deps = [
"//src/go/rpk/pkg/config",
"//src/go/rpk/pkg/out",
"@com_github_stretchr_testify//require",
],
)
56 changes: 46 additions & 10 deletions src/go/rpk/pkg/cli/cloud/auth/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
package auth

import (
"fmt"
"io"
"sort"

"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
Expand All @@ -18,19 +20,46 @@ import (
"github.com/spf13/cobra"
)

type cloudAuthRow struct {
Name string `json:"name" yaml:"name"`
Kind string `json:"kind" yaml:"kind"`
Organization string `json:"organization" yaml:"organization"`
OrganizationID string `json:"organization_id" yaml:"organization_id"`
Current bool `json:"current" yaml:"current"`
}

func printCloudAuthList(f config.OutFormatter, rows []cloudAuthRow, w io.Writer) {
if isText, _, rendered, err := f.Format(rows); !isText {
out.MaybeDie(err, "unable to print in the requested format %q: %v", f.Kind, err)
fmt.Fprintln(w, rendered)
return
}
tw := out.NewTableTo(w, "NAME", "KIND", "ORGANIZATION", "ORGANIZATION-ID")
defer tw.Flush()
for _, r := range rows {
name := r.Name
if r.Current {
name += "*"
}
tw.Print(name, r.Kind, r.Organization, r.OrganizationID)
}
}

func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List rpk cloud authentications",
Args: cobra.ExactArgs(0),
Run: func(*cobra.Command, []string) {
Run: func(cmd *cobra.Command, _ []string) {
f := p.Formatter
if h, ok := f.Help([]cloudAuthRow{}); ok {
out.Exit(h)
}

cfg, err := p.Load(fs)
out.MaybeDie(err, "rpk unable to load config: %v", err)

tw := out.NewTable("name", "kind", "organization", "organization-id")
defer tw.Flush()

y, ok := cfg.ActualRpkYaml()
if !ok {
return
Expand All @@ -44,14 +73,21 @@ func newListCommand(fs afero.Fs, p *config.Params) *cobra.Command {
(l.OrgID == r.OrgID && l.Name < r.Name)))
})

rows := make([]cloudAuthRow, 0, len(y.CloudAuths))
for i := range y.CloudAuths {
a := &y.CloudAuths[i]
name := a.Name
if a.OrgID == y.CurrentCloudAuthOrgID && a.Kind == y.CurrentCloudAuthKind {
name += "*"
}
tw.Print(name, a.Kind, a.Organization, a.OrgID)
rows = append(rows, cloudAuthRow{
Name: a.Name,
Kind: a.Kind,
Organization: a.Organization,
OrganizationID: a.OrgID,
Current: a.OrgID == y.CurrentCloudAuthOrgID && a.Kind == y.CurrentCloudAuthKind,
})
}

printCloudAuthList(f, rows, cmd.OutOrStdout())
},
}
p.InstallFormatFlag(cmd)
return cmd
}
47 changes: 47 additions & 0 deletions src/go/rpk/pkg/cli/cloud/auth/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2026 Redpanda Data, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.md
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0

package auth

import (
"bytes"
"encoding/json"
"testing"

"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/out"
"github.com/stretchr/testify/require"
)

func TestPrintCloudAuthList(t *testing.T) {
data := []cloudAuthRow{
{Name: "acme-sso", Kind: "sso", Organization: "acme", OrganizationID: "org-123", Current: true},
{Name: "acme-client", Kind: "client", Organization: "acme", OrganizationID: "org-123"},
}

t.Run("text marks current with asterisk", func(t *testing.T) {
var buf bytes.Buffer
printCloudAuthList(config.OutFormatter{Kind: "text"}, data, &buf)
require.Equal(t, [][]string{
{"NAME", "KIND", "ORGANIZATION", "ORGANIZATION-ID"},
{"acme-sso*", "sso", "acme", "org-123"},
{"acme-client", "client", "acme", "org-123"},
}, out.TableRows(buf.String()))
})

// Round-trip verifies that structured output uses the Current bool
// and does not embed the asterisk in Name.
t.Run("json round-trip", func(t *testing.T) {
var buf bytes.Buffer
printCloudAuthList(config.OutFormatter{Kind: "json"}, data, &buf)
var got []cloudAuthRow
require.NoError(t, json.Unmarshal(buf.Bytes(), &got))
require.Equal(t, data, got)
})
}
13 changes: 12 additions & 1 deletion src/go/rpk/pkg/cli/cluster/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@rules_go//go:def.bzl", "go_library")
load("@rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "cluster",
Expand Down Expand Up @@ -34,3 +34,14 @@ go_library(
"@org_uber_go_zap//:zap",
],
)

go_test(
name = "cluster_test",
srcs = ["logdirs_test.go"],
embed = [":cluster"],
deps = [
"//src/go/rpk/pkg/config",
"//src/go/rpk/pkg/out",
"@com_github_stretchr_testify//require",
],
)
4 changes: 4 additions & 0 deletions src/go/rpk/pkg/cli/cluster/config/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@ go_test(
"import_test.go",
"list_test.go",
"set_test.go",
"status_test.go",
],
embed = [":config"],
deps = [
"//src/go/rpk/pkg/config",
"//src/go/rpk/pkg/out",
"//src/go/rpk/pkg/publicapi",
"@build_buf_gen_go_redpandadata_cloud_protocolbuffers_go//redpanda/api/controlplane/v1:controlplane",
"@com_github_redpanda_data_common_go_rpadmin//:rpadmin",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
"@in_gopkg_yaml_v3//:yaml_v3",
Expand Down
57 changes: 44 additions & 13 deletions src/go/rpk/pkg/cli/cluster/config/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package config
import (
"errors"
"fmt"
"io"
"slices"

"go.uber.org/zap"
Expand All @@ -22,6 +23,7 @@ import (

controlplanev1 "buf.build/gen/go/redpandadata/cloud/protocolbuffers/go/redpanda/api/controlplane/v1"
"connectrpc.com/connect"
"github.com/redpanda-data/common-go/rpadmin"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/adminapi"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/out"
Expand All @@ -30,6 +32,41 @@ import (
"github.com/spf13/cobra"
)

type nodeConfigStatus struct {
Node int64 `json:"node" yaml:"node"`
ConfigVersion int64 `json:"config_version" yaml:"config_version"`
NeedsRestart bool `json:"needs_restart" yaml:"needs_restart"`
Invalid []string `json:"invalid" yaml:"invalid"`
Unknown []string `json:"unknown" yaml:"unknown"`
}

func buildNodeStatuses(resp rpadmin.ConfigStatusResponse) []nodeConfigStatus {
statuses := make([]nodeConfigStatus, 0, len(resp))
for _, node := range resp {
statuses = append(statuses, nodeConfigStatus{
Node: node.NodeID,
ConfigVersion: node.ConfigVersion,
NeedsRestart: node.Restart,
Invalid: node.Invalid,
Unknown: node.Unknown,
})
}
return statuses
}

func printNodeStatus(f config.OutFormatter, statuses []nodeConfigStatus, w io.Writer) {
if isText, _, t, err := f.Format(statuses); !isText {
out.MaybeDie(err, "unable to print in the requested format %q: %v", f.Kind, err)
fmt.Fprintln(w, t)
return
}
tw := out.NewTableTo(w, "NODE", "CONFIG-VERSION", "NEEDS-RESTART", "INVALID", "UNKNOWN")
defer tw.Flush()
for _, s := range statuses {
tw.Print(s.Node, s.ConfigVersion, s.NeedsRestart, s.Invalid, s.Unknown)
}
}

func newStatusCommand(fs afero.Fs, p *config.Params) *cobra.Command {
cmd := &cobra.Command{
Use: "status",
Expand All @@ -46,6 +83,11 @@ a lower number shows that a node is out of sync, perhaps because it
is offline.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, _ []string) {
f := p.Formatter
if h, ok := f.Help([]nodeConfigStatus{}); ok {
out.Exit(h)
}

vp, err := p.LoadVirtualProfile(fs)
out.MaybeDie(err, "rpk unable to load config: %v", err)

Expand All @@ -71,26 +113,15 @@ is offline.`,
client, err := adminapi.NewClient(cmd.Context(), fs, vp)
out.MaybeDie(err, "unable to initialize admin client: %v", err)

// GET the status endpoint
resp, err := client.ClusterConfigStatus(cmd.Context(), false)
out.MaybeDie(err, "error fetching status: %v", err)

tw := out.NewTable("NODE", "CONFIG-VERSION", "NEEDS-RESTART", "INVALID", "UNKNOWN")
defer tw.Flush()

for _, node := range resp {
tw.PrintStructFields(struct {
ID int64
Version int64
Restart bool
Invalid []string
Unknown []string
}{node.NodeID, node.ConfigVersion, node.Restart, node.Invalid, node.Unknown})
}
printNodeStatus(f, buildNodeStatuses(resp), cmd.OutOrStdout())
}
},
}

p.InstallFormatFlag(cmd)
return cmd
}

Expand Down
57 changes: 57 additions & 0 deletions src/go/rpk/pkg/cli/cluster/config/status_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2026 Redpanda Data, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.md
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0

package config

import (
"strings"
"testing"

"github.com/redpanda-data/common-go/rpadmin"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/out"
"github.com/stretchr/testify/require"
)

func TestBuildNodeStatuses(t *testing.T) {
nodes := rpadmin.ConfigStatusResponse{
{NodeID: 1, ConfigVersion: 5, Restart: true, Invalid: []string{"bad_key"}, Unknown: []string{"new_key"}},
{NodeID: 2, ConfigVersion: 5, Restart: false, Invalid: nil, Unknown: nil},
}

statuses := buildNodeStatuses(nodes)
require.Len(t, statuses, 2)

require.Equal(t, int64(1), statuses[0].Node)
require.Equal(t, int64(5), statuses[0].ConfigVersion)
require.True(t, statuses[0].NeedsRestart)
require.Equal(t, []string{"bad_key"}, statuses[0].Invalid)
require.Equal(t, []string{"new_key"}, statuses[0].Unknown)

require.Equal(t, int64(2), statuses[1].Node)
require.False(t, statuses[1].NeedsRestart)
require.Nil(t, statuses[1].Invalid)
require.Nil(t, statuses[1].Unknown)
}

func TestPrintNodeStatus(t *testing.T) {
statuses := []nodeConfigStatus{
{Node: 1, ConfigVersion: 5, NeedsRestart: true, Invalid: []string{"bad_key"}, Unknown: []string{"new_key"}},
{Node: 2, ConfigVersion: 5, NeedsRestart: false},
}

f := config.OutFormatter{Kind: "text"}
b := &strings.Builder{}
printNodeStatus(f, statuses, b)
require.Equal(t, [][]string{
{"NODE", "CONFIG-VERSION", "NEEDS-RESTART", "INVALID", "UNKNOWN"},
{"1", "5", "true", "[bad_key]", "[new_key]"},
{"2", "5", "false", "[]", "[]"},
}, out.TableRows(b.String()))
}
Loading
Loading