Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/charmbracelet/lipgloss v0.11.0
github.com/client9/gospell v0.0.0-20160306015952-90dfc71015df
github.com/confluentinc/ccloud-sdk-go-v1-public v0.0.0-20250521223017-0e8f6f971b52
github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260707163957-4e3e503e8b10
github.com/confluentinc/ccloud-sdk-go-v2/ai v0.1.0
github.com/confluentinc/ccloud-sdk-go-v2/apikeys v0.4.0
github.com/confluentinc/ccloud-sdk-go-v2/billing v0.3.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/confluentinc/ccloud-sdk-go-v1-public v0.0.0-20250521223017-0e8f6f971b52 h1:19qEGhkbZa5fopKCe0VPIV+Sasby4Pv10z9ZaktwWso=
github.com/confluentinc/ccloud-sdk-go-v1-public v0.0.0-20250521223017-0e8f6f971b52/go.mod h1:62EMf+5uFEt1BJ2q8WMrUoI9VUSxAbDnmZCGRt/MbA0=
github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260707163957-4e3e503e8b10 h1:7pfKk5+4RkphBMn8TFwEPwbnSKwUjx1VORwJcX0Q8Q0=
github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260707163957-4e3e503e8b10/go.mod h1:75XYxEMix/kXHlKza4pP9P2in9w1tB3vM2TY3G4mHl4=
github.com/confluentinc/ccloud-sdk-go-v2/ai v0.1.0 h1:zSF4OQUJXWH2JeAo9rsq13ibk+JFdzITGR8S7cFMpzw=
github.com/confluentinc/ccloud-sdk-go-v2/ai v0.1.0/go.mod h1:DoxqzzF3JzvJr3fWkvCiOHFlE0GoYpozWxFZ1Ud9ntA=
github.com/confluentinc/ccloud-sdk-go-v2/apikeys v0.4.0 h1:8fWyLwMuy8ec0MVF5Avd54UvbIxhDFhZzanHBVwgxdw=
Expand Down
2 changes: 2 additions & 0 deletions internal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
"github.com/confluentinc/cli/v4/internal/secret"
servicequota "github.com/confluentinc/cli/v4/internal/service-quota"
streamshare "github.com/confluentinc/cli/v4/internal/stream-share"
"github.com/confluentinc/cli/v4/internal/switchover"
"github.com/confluentinc/cli/v4/internal/tableflow"
unifiedstreammanager "github.com/confluentinc/cli/v4/internal/unified-stream-manager"
"github.com/confluentinc/cli/v4/internal/update"
Expand Down Expand Up @@ -138,6 +139,7 @@ func NewConfluentCommand(cfg *config.Config) *cobra.Command {
cmd.AddCommand(servicequota.New(prerunner))
cmd.AddCommand(shell.New(cmd, func() *cobra.Command { return NewConfluentCommand(cfg) }))
cmd.AddCommand(streamshare.New(prerunner))
cmd.AddCommand(switchover.New(prerunner))
cmd.AddCommand(tableflow.New(prerunner))
cmd.AddCommand(unifiedstreammanager.New(cfg, prerunner))
cmd.AddCommand(update.New(cfg, prerunner))
Expand Down
22 changes: 22 additions & 0 deletions internal/switchover/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package switchover

import (
"github.com/spf13/cobra"

"github.com/confluentinc/cli/v4/internal/switchover/endpoint"
"github.com/confluentinc/cli/v4/internal/switchover/pair"
pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
)

func New(prerunner pcmd.PreRunner) *cobra.Command {
cmd := &cobra.Command{
Use: "switchover",
Short: "Manage Kafka disaster recovery switchover pairs and endpoints.",
Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin},
}
Comment on lines +11 to +16

cmd.AddCommand(pair.New(prerunner))
cmd.AddCommand(endpoint.New(prerunner))

return cmd
}
37 changes: 37 additions & 0 deletions internal/switchover/endpoint/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package endpoint

import (
"github.com/spf13/cobra"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
)

type command struct {
*pcmd.AuthenticatedCLICommand
}

type out struct {
Id string `human:"ID" serialized:"id"`
DisplayName string `human:"Display Name" serialized:"display_name"`
SwitchoverPair string `human:"Switchover Pair" serialized:"switchover_pair"`
Environment string `human:"Environment" serialized:"environment"`
DrEndpoint string `human:"DR Endpoint" serialized:"dr_endpoint"`
Phase string `human:"Phase" serialized:"phase"`
}

func New(prerunner pcmd.PreRunner) *cobra.Command {
cmd := &cobra.Command{
Use: "endpoint",
Short: "Manage switchover endpoints.",
Long: "Manage switchover endpoints. This API is not yet implemented on the backend; commands will fail against a live Confluent Cloud environment.",
}

c := &command{pcmd.NewAuthenticatedCLICommand(cmd, prerunner)}

cmd.AddCommand(c.newCreateCommand())
cmd.AddCommand(c.newDescribeCommand())
cmd.AddCommand(c.newUpdateCommand())
cmd.AddCommand(c.newActivateCommand())

return cmd
}
37 changes: 37 additions & 0 deletions internal/switchover/endpoint/command_activate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package endpoint

import (
"github.com/spf13/cobra"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
"github.com/confluentinc/cli/v4/pkg/examples"
)

func (c *command) newActivateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "activate <id>",
Short: "Activate a switchover endpoint.",
Long: "Activate a switchover endpoint, applying its desired routing target.",
Args: cobra.ExactArgs(1),
RunE: c.activate,
Example: examples.BuildExampleString(
examples.Example{
Text: `Activate switchover endpoint "se-123456".`,
Code: `confluent switchover endpoint activate se-123456`,
},
),
}

pcmd.AddOutputFlag(cmd)

return cmd
}

func (c *command) activate(cmd *cobra.Command, args []string) error {
result, err := c.V2Client.ActivateSwitchoverEndpoint(args[0])
if err != nil {
return err
}

return printSwitchoverEndpoint(cmd, result)
}
135 changes: 135 additions & 0 deletions internal/switchover/endpoint/command_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package endpoint

import (
"fmt"
"strings"

"github.com/spf13/cobra"

switchoverv1 "github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover/v1"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
"github.com/confluentinc/cli/v4/pkg/examples"
"github.com/confluentinc/cli/v4/pkg/output"
)

func (c *command) newCreateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "create <display-name>",
Short: "Create a switchover endpoint.",
Long: "Create a switchover endpoint bound to a switchover pair.",
Args: cobra.ExactArgs(1),
RunE: c.create,
Example: examples.BuildExampleString(
examples.Example{
Text: `Create switchover endpoint "prod-kafka-dr-endpoint" for switchover pair "sw-123456".`,
Code: `confluent switchover endpoint create prod-kafka-dr-endpoint --switchover-pair sw-123456 --endpoint name=west-platt,resource-id=lkc-west,type=PRIVATE --endpoint name=east-platt,resource-id=lkc-east,type=PRIVATE`,
},
),
}

cmd.Flags().String("switchover-pair", "", "The ID of the switchover pair this endpoint is bound to.")
cmd.Flags().StringArray("endpoint", nil, `An endpoint side, in the form "name=<name>,resource-id=<id>,type=<PRIVATE|PUBLIC>[,cloud=<cloud>][,region=<region>][,gateway=<gateway-id>][,access-point=<access-point-id>]". Must be specified exactly twice.`)
pcmd.AddEnvironmentFlag(cmd, c.AuthenticatedCLICommand)
pcmd.AddContextFlag(cmd, c.CLICommand)
pcmd.AddOutputFlag(cmd)

cobra.CheckErr(cmd.MarkFlagRequired("switchover-pair"))
cobra.CheckErr(cmd.MarkFlagRequired("endpoint"))

return cmd
}

func parseEndpointFlag(raw string) (switchoverv1.SwitchoverV1EndpointConfig, error) {
config := switchoverv1.SwitchoverV1EndpointConfig{}
filter := switchoverv1.SwitchoverV1EndpointFilter{}
for _, part := range strings.Split(raw, ",") {
key, value, ok := strings.Cut(part, "=")
if !ok {
return config, fmt.Errorf(`invalid --endpoint value %q: expected "key=value" pairs`, raw)
}
switch key {
case "name":
config.Name = value
case "resource-id":
filter.ResourceId = value
case "type":
filter.Type = value
case "cloud":
filter.Cloud = switchoverv1.PtrString(value)
case "region":
filter.Region = switchoverv1.PtrString(value)
case "gateway":
filter.Gateway = switchoverv1.PtrString(value)
case "access-point":
filter.AccessPoint = switchoverv1.PtrString(value)
default:
return config, fmt.Errorf(`invalid --endpoint key %q`, key)
}
}
if config.Name == "" || filter.ResourceId == "" || filter.Type == "" {
return config, fmt.Errorf(`invalid --endpoint value %q: "name", "resource-id", and "type" are required`, raw)
}
config.EndpointFilter = filter
return config, nil
}

func (c *command) create(cmd *cobra.Command, args []string) error {
displayName := args[0]

switchoverPairId, err := cmd.Flags().GetString("switchover-pair")
if err != nil {
return err
}

rawEndpoints, err := cmd.Flags().GetStringArray("endpoint")
if err != nil {
return err
}
if len(rawEndpoints) != 2 {
return fmt.Errorf(`exactly two --endpoint flags are required, got %d`, len(rawEndpoints))
}

endpoints := make([]switchoverv1.SwitchoverV1EndpointConfig, len(rawEndpoints))
for i, raw := range rawEndpoints {
endpointConfig, err := parseEndpointFlag(raw)
if err != nil {
return err
}
endpoints[i] = endpointConfig
}

environmentId, err := c.Context.EnvironmentId()
if err != nil {
return err
}

endpoint := switchoverv1.SwitchoverV1SwitchoverEndpoint{
Spec: &switchoverv1.SwitchoverV1SwitchoverEndpointSpec{
DisplayName: switchoverv1.PtrString(displayName),
Endpoints: &endpoints,
Environment: &switchoverv1.EnvScopedObjectReference{Id: environmentId},
SwitchoverPair: &switchoverv1.EnvScopedObjectReference{Id: switchoverPairId},
},
}

result, err := c.V2Client.CreateSwitchoverEndpoint(endpoint)
if err != nil {
return err
}

return printSwitchoverEndpoint(cmd, result)
}

func printSwitchoverEndpoint(cmd *cobra.Command, endpoint switchoverv1.SwitchoverV1SwitchoverEndpoint) error {
table := output.NewTable(cmd)
table.Add(&out{
Id: endpoint.GetId(),
DisplayName: endpoint.Spec.GetDisplayName(),
SwitchoverPair: endpoint.Spec.SwitchoverPair.GetId(),
Environment: endpoint.Spec.Environment.GetId(),
DrEndpoint: endpoint.Spec.GetDrEndpoint(),
Phase: endpoint.Status.GetPhase(),
})
return table.Print()
}
43 changes: 43 additions & 0 deletions internal/switchover/endpoint/command_describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package endpoint

import (
"github.com/spf13/cobra"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
"github.com/confluentinc/cli/v4/pkg/examples"
)

func (c *command) newDescribeCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "describe <id>",
Short: "Describe a switchover endpoint.",
Args: cobra.ExactArgs(1),
RunE: c.describe,
Example: examples.BuildExampleString(
examples.Example{
Text: `Describe switchover endpoint "se-123456".`,
Code: `confluent switchover endpoint describe se-123456`,
},
),
}

pcmd.AddEnvironmentFlag(cmd, c.AuthenticatedCLICommand)
pcmd.AddContextFlag(cmd, c.CLICommand)
pcmd.AddOutputFlag(cmd)

return cmd
}

func (c *command) describe(cmd *cobra.Command, args []string) error {
environmentId, err := c.Context.EnvironmentId()
if err != nil {
return err
}

endpoint, err := c.V2Client.GetSwitchoverEndpoint(args[0], environmentId)
if err != nil {
return err
}

return printSwitchoverEndpoint(cmd, endpoint)
}
53 changes: 53 additions & 0 deletions internal/switchover/endpoint/command_update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package endpoint

import (
"github.com/spf13/cobra"

switchoverv1 "github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover/v1"

pcmd "github.com/confluentinc/cli/v4/pkg/cmd"
"github.com/confluentinc/cli/v4/pkg/examples"
)

func (c *command) newUpdateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "update <id>",
Short: "Update a switchover endpoint.",
Long: "Update a switchover endpoint's display name. This is the only mutable field.",
Args: cobra.ExactArgs(1),
RunE: c.update,
Example: examples.BuildExampleString(
examples.Example{
Text: `Rename switchover endpoint "se-123456".`,
Code: `confluent switchover endpoint update se-123456 --display-name "prod-kafka-dr-endpoint-renamed"`,
},
),
}

cmd.Flags().String("display-name", "", "A human-readable name for the switchover endpoint.")
pcmd.AddOutputFlag(cmd)

cobra.CheckErr(cmd.MarkFlagRequired("display-name"))

return cmd
}

func (c *command) update(cmd *cobra.Command, args []string) error {
displayName, err := cmd.Flags().GetString("display-name")
if err != nil {
return err
}

endpoint := switchoverv1.SwitchoverV1SwitchoverEndpoint{
Spec: &switchoverv1.SwitchoverV1SwitchoverEndpointSpec{
DisplayName: switchoverv1.PtrString(displayName),
},
}

result, err := c.V2Client.UpdateSwitchoverEndpoint(args[0], endpoint)
if err != nil {
return err
}

return printSwitchoverEndpoint(cmd, result)
}
Loading