From c5a1bf2496994ec5c90366c0e9fc3b0fd422a68e Mon Sep 17 00:00:00 2001 From: YOUR_FULL_NAME Date: Tue, 18 Aug 2026 13:24:36 -0700 Subject: [PATCH 1/5] switchover: adopt CRN references and surface new response metadata Bumps the switchover SDK to the api#2669 regeneration (ccloud-sdk-go-v2-internal#859) and adopts the field renames + new fields it brings: - CRN references (api #252/#2545): member_id/environment/parent_resource_id/ network_id/access_point become member_crn, environment_crn, parent_resource_crn, network_crn, access_point_crn. Create/failover commands assemble the CRNs from the current organization plus the flag-supplied IDs, so the flag surface stays ID-based. - New response metadata (api #2669): first_active on `pair describe` (human "First Active" row; already carried verbatim in -o json/yaml), and cloud/region/connection_type appended to each endpoint side in `endpoint describe`. SDK pinned by pseudo-version (no local replace directive). make build clean; verified `pair describe` renders First Active against a live READY pair on devel. (SKIP=go-generate: the repo's mock-regen pre-commit hook fails on clean HEAD in this environment -- travisjeffery/mocker cannot load packages under this Go toolchain, unrelated to this change. All other hooks ran.) Co-Authored-By: Claude Opus 5 (1M context) --- go.mod | 2 +- go.sum | 4 ++-- internal/switchover/endpoint/command.go | 18 +++++++++++------ .../switchover/endpoint/command_create.go | 16 +++++++++------ internal/switchover/endpoint/command_list.go | 4 ++-- internal/switchover/pair/command.go | 6 ++++-- internal/switchover/pair/command_create.go | 20 +++++++++++++------ internal/switchover/pair/command_list.go | 2 +- .../switchover/pair/command_trigger_switch.go | 8 ++++++-- 9 files changed, 52 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index 30f43c701e..c3758d1845 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +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-20260728173342-25f438c2593b + github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260818200130-b17ef6e8f63f 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 diff --git a/go.sum b/go.sum index 13c899cce4..a25e96b11f 100644 --- a/go.sum +++ b/go.sum @@ -172,8 +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-20260728173342-25f438c2593b h1:9KvPeP2mC2MZNprKA1N2ueqCJEWLZAgRmVMIJdDXhAY= -github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260728173342-25f438c2593b/go.mod h1:yv1VtjqyqD7G5gpdrzR2hGyIAfEOuLA3aTQW0TZ6Iek= +github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260818200130-b17ef6e8f63f h1:0K5J5Ke+qB9csUiyuNco+wJdxdklXoqFUoK9SZ2n1mo= +github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260818200130-b17ef6e8f63f/go.mod h1:yv1VtjqyqD7G5gpdrzR2hGyIAfEOuLA3aTQW0TZ6Iek= 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= diff --git a/internal/switchover/endpoint/command.go b/internal/switchover/endpoint/command.go index b39c6b738e..511047dacf 100644 --- a/internal/switchover/endpoint/command.go +++ b/internal/switchover/endpoint/command.go @@ -91,8 +91,8 @@ func newEndpointOut(endpoint switchoverv1.SwitchoverV1SwitchoverEndpoint) *out { return &out{ Id: endpoint.GetId(), DisplayName: endpoint.Spec.GetDisplayName(), - SwitchoverPair: endpoint.Spec.GetParentResourceId(), - Environment: endpoint.Spec.GetEnvironment(), + SwitchoverPair: endpoint.Spec.GetParentResourceCrn(), + Environment: endpoint.Spec.GetEnvironmentCrn(), Target: endpoint.Spec.GetTarget(), Phase: endpoint.Status.GetPhase(), Endpoints: formatEndpoints(endpoint.Spec.GetEndpoints()), @@ -105,15 +105,21 @@ func formatEndpoints(endpoints []switchoverv1.SwitchoverV1EndpointConfig) string for i, endpoint := range endpoints { filter := endpoint.EndpointFilter parts := []string{endpoint.GetName(), filter.GetType()} - if networkId := filter.GetNetworkId(); networkId != "" { - parts = append(parts, "network="+networkId) + if networkCrn := filter.GetNetworkCrn(); networkCrn != "" { + parts = append(parts, "network="+networkCrn) } - if accessPoint := filter.GetAccessPoint(); accessPoint != "" { - parts = append(parts, "access-point="+accessPoint) + if accessPointCrn := filter.GetAccessPointCrn(); accessPointCrn != "" { + parts = append(parts, "access-point="+accessPointCrn) } if hostname := endpoint.GetHostname(); hostname != "" { parts = append(parts, "hostname="+hostname) } + if cloud, region := endpoint.GetCloud(), endpoint.GetRegion(); cloud != "" || region != "" { + parts = append(parts, strings.TrimPrefix(cloud+"/"+region, "/")) + } + if connectionType := endpoint.GetConnectionType(); connectionType != "" { + parts = append(parts, connectionType) + } lines[i] = strings.Join(parts, " ") } return strings.Join(lines, "\n") diff --git a/internal/switchover/endpoint/command_create.go b/internal/switchover/endpoint/command_create.go index fcedfdc540..7efa30b88e 100644 --- a/internal/switchover/endpoint/command_create.go +++ b/internal/switchover/endpoint/command_create.go @@ -54,9 +54,9 @@ func parseEndpointFlag(raw string) (switchoverv1.SwitchoverV1EndpointConfig, err case "type": filter.Type = value case "network": - filter.NetworkId = switchoverv1.PtrString(value) + filter.NetworkCrn = switchoverv1.PtrString(value) case "access-point": - filter.AccessPoint = switchoverv1.PtrString(value) + filter.AccessPointCrn = switchoverv1.PtrString(value) default: return config, fmt.Errorf(`invalid --endpoint key %q`, key) } @@ -98,12 +98,16 @@ func (c *command) create(cmd *cobra.Command, args []string) error { return err } + // Flags stay ID-based; the CRNs the backend expects are assembled here (ORC-9794). + environmentCrn := fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s", c.Context.GetCurrentOrganization(), environmentId) + parentResourceCrn := fmt.Sprintf("%s/switchover-pair=%s", environmentCrn, switchoverPairId) + endpoint := switchoverv1.SwitchoverV1SwitchoverEndpoint{ Spec: &switchoverv1.SwitchoverV1SwitchoverEndpointSpec{ - DisplayName: switchoverv1.PtrString(displayName), - Endpoints: &endpoints, - Environment: switchoverv1.PtrString(environmentId), - ParentResourceId: switchoverv1.PtrString(switchoverPairId), + DisplayName: switchoverv1.PtrString(displayName), + Endpoints: &endpoints, + EnvironmentCrn: switchoverv1.PtrString(environmentCrn), + ParentResourceCrn: switchoverv1.PtrString(parentResourceCrn), }, } diff --git a/internal/switchover/endpoint/command_list.go b/internal/switchover/endpoint/command_list.go index ceabfb76b1..a6624fa700 100644 --- a/internal/switchover/endpoint/command_list.go +++ b/internal/switchover/endpoint/command_list.go @@ -65,8 +65,8 @@ func (c *command) list(cmd *cobra.Command, _ []string) error { list.Add(&listOut{ Id: endpoint.GetId(), DisplayName: endpoint.Spec.GetDisplayName(), - SwitchoverPair: endpoint.Spec.GetParentResourceId(), - Environment: endpoint.Spec.GetEnvironment(), + SwitchoverPair: endpoint.Spec.GetParentResourceCrn(), + Environment: endpoint.Spec.GetEnvironmentCrn(), Phase: endpoint.Status.GetPhase(), }) } diff --git a/internal/switchover/pair/command.go b/internal/switchover/pair/command.go index 14300e7a49..7b111c3fb3 100644 --- a/internal/switchover/pair/command.go +++ b/internal/switchover/pair/command.go @@ -27,6 +27,7 @@ type out struct { DisplayName string `human:"Display Name"` Environment string `human:"Environment"` ActiveMember string `human:"Active Member"` + FirstActive string `human:"First Active,omitempty"` FailoverType string `human:"Failover Type,omitempty"` Phase string `human:"Phase"` Members string `human:"Members,omitempty"` @@ -92,8 +93,9 @@ func newPairOut(pair switchoverv1.SwitchoverV1SwitchoverPair) *out { return &out{ Id: pair.GetId(), DisplayName: pair.Spec.GetDisplayName(), - Environment: pair.Spec.GetEnvironment(), + Environment: pair.Spec.GetEnvironmentCrn(), ActiveMember: pair.Spec.GetActiveMember(), + FirstActive: pair.Spec.GetFirstActive(), FailoverType: pair.Spec.GetFailoverType(), Phase: pair.Status.GetPhase(), Members: formatMembers(pair.Spec.GetMembers()), @@ -108,7 +110,7 @@ func formatMembers(members []switchoverv1.SwitchoverV1SwitchoverPairMember) stri if member.Location != nil { location = fmt.Sprintf(", %s/%s", member.Location.GetCloud(), member.Location.GetRegion()) } - lines[i] = fmt.Sprintf("%s (%s%s)", member.GetName(), member.GetMemberId(), location) + lines[i] = fmt.Sprintf("%s (%s%s)", member.GetName(), member.GetMemberCrn(), location) } return strings.Join(lines, "\n") } diff --git a/internal/switchover/pair/command_create.go b/internal/switchover/pair/command_create.go index 7af14e2a62..d28ce89fab 100644 --- a/internal/switchover/pair/command_create.go +++ b/internal/switchover/pair/command_create.go @@ -51,12 +51,12 @@ func parseMemberFlag(raw string) (switchoverv1.SwitchoverV1SwitchoverPairMember, case "name": member.Name = value case "id": - member.MemberId = value + member.MemberCrn = value default: return member, fmt.Errorf(`invalid --member key %q: expected "name" or "id"`, key) } } - if member.Name == "" || member.MemberId == "" { + if member.Name == "" || member.MemberCrn == "" { return member, fmt.Errorf(`invalid --member value %q: both "name" and "id" are required`, raw) } return member, nil @@ -92,12 +92,20 @@ func (c *command) create(cmd *cobra.Command, args []string) error { return err } + // The backend addresses every reference by CRN (ORC-9794): spec.environment_crn and each + // member's member_crn. Flags stay ID-based -- the CRNs are assembled here from the current + // organization plus the environment/cluster IDs the user supplied. + environmentCrn := fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s", c.Context.GetCurrentOrganization(), environmentId) + for i, member := range members { + members[i].MemberCrn = fmt.Sprintf("%s/cloud-cluster=%s", environmentCrn, member.MemberCrn) + } + pair := switchoverv1.SwitchoverV1SwitchoverPair{ Spec: &switchoverv1.SwitchoverV1SwitchoverPairSpec{ - DisplayName: switchoverv1.PtrString(displayName), - Members: &members, - ActiveMember: switchoverv1.PtrString(activeMember), - Environment: switchoverv1.PtrString(environmentId), + DisplayName: switchoverv1.PtrString(displayName), + Members: &members, + ActiveMember: switchoverv1.PtrString(activeMember), + EnvironmentCrn: switchoverv1.PtrString(environmentCrn), }, } diff --git a/internal/switchover/pair/command_list.go b/internal/switchover/pair/command_list.go index fbbbae80d9..716110538e 100644 --- a/internal/switchover/pair/command_list.go +++ b/internal/switchover/pair/command_list.go @@ -59,7 +59,7 @@ func (c *command) list(cmd *cobra.Command, _ []string) error { list.Add(&listOut{ Id: pair.GetId(), DisplayName: pair.Spec.GetDisplayName(), - Environment: pair.Spec.GetEnvironment(), + Environment: pair.Spec.GetEnvironmentCrn(), ActiveMember: pair.Spec.GetActiveMember(), FailoverType: pair.Spec.GetFailoverType(), Phase: pair.Status.GetPhase(), diff --git a/internal/switchover/pair/command_trigger_switch.go b/internal/switchover/pair/command_trigger_switch.go index 34171e1e15..07b1ed5bf8 100644 --- a/internal/switchover/pair/command_trigger_switch.go +++ b/internal/switchover/pair/command_trigger_switch.go @@ -55,6 +55,10 @@ func (c *command) triggerSwitch(cmd *cobra.Command, args []string) error { return err } + // The :failover body carries the environment as a CRN (ORC-9794), unlike other operations + // which take it as a query parameter. + environmentCrn := fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s", c.Context.GetCurrentOrganization(), environmentId) + promptMsg := fmt.Sprintf(`This triggers a %s failover on switchover pair "%s", redirecting live traffic between regions. Do you want to proceed?`, failoverType, id) if err := deletion.ConfirmPrompt(cmd, promptMsg); err != nil { return err @@ -62,8 +66,8 @@ func (c *command) triggerSwitch(cmd *cobra.Command, args []string) error { req := switchoverv1.SwitchoverV1SwitchoverPairFailoverRequest{ Spec: switchoverv1.SwitchoverV1SwitchoverPairFailoverRequestSpec{ - Environment: environmentId, - FailoverType: switchoverv1.PtrString(failoverType), + EnvironmentCrn: environmentCrn, + FailoverType: failoverType, }, } if activeMember != "" { From 30ffdada3de8c4122f61b77dfacc9a7e6367110c Mon Sep 17 00:00:00 2001 From: YOUR_FULL_NAME Date: Tue, 18 Aug 2026 20:40:12 -0700 Subject: [PATCH 2/5] switchover: accept member CRN directly on `pair create` (--member crn=...) The `--member` flag took `id=` and the CLI assembled member_crn from the current org + the pair's --environment. That silently assumed every member lives in the pair's environment, so it could not express a pair whose two members are in different environments -- which the backend explicitly supports, because member_crn carries its own environment (ORC-10263). Change --member to take `crn=` directly and pass it through unchanged. The CRN carries the member's own organization and environment, so cross-environment members work. The pair's own environment_crn is still built from --environment (which also drives the ?environment= query parameter). before: --member name=west,id=lkc-111111 after: --member name=west,crn=crn://confluent.cloud/organization=.../environment=env-.../cloud-cluster=lkc-111111 Verified against devel: `id=` is now rejected with a clear error; `crn=` creates the pair with member_crn passed through verbatim. Co-Authored-By: Claude Opus 5 (1M context) --- internal/switchover/pair/command_create.go | 24 ++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/internal/switchover/pair/command_create.go b/internal/switchover/pair/command_create.go index d28ce89fab..140bb9a2a5 100644 --- a/internal/switchover/pair/command_create.go +++ b/internal/switchover/pair/command_create.go @@ -22,13 +22,13 @@ func (c *command) newCreateCommand() *cobra.Command { RunE: c.create, Example: examples.BuildExampleString( examples.Example{ - Text: `Create switchover pair "prod-kafka-dr" between clusters "lkc-111111" (west) and "lkc-222222" (east), active on west.`, - Code: `confluent switchover pair create prod-kafka-dr --member name=west,id=lkc-111111 --member name=east,id=lkc-222222 --active-member west`, + Text: `Create switchover pair "prod-kafka-dr" between two Kafka clusters (west and east), active on west.`, + Code: `confluent switchover pair create prod-kafka-dr --member name=west,crn=crn://confluent.cloud/organization=abc/environment=env-111111/cloud-cluster=lkc-111111 --member name=east,crn=crn://confluent.cloud/organization=abc/environment=env-222222/cloud-cluster=lkc-222222 --active-member west`, }, ), } - cmd.Flags().StringArray("member", nil, `A member of the pair, in the form "name=,id=". Must be specified exactly twice.`) + cmd.Flags().StringArray("member", nil, `A member of the pair, in the form "name=,crn=". The CRN carries the member's own environment, so the two members may live in different environments. Must be specified exactly twice.`) cmd.Flags().String("active-member", "", "The name of the member that starts as active; must match one of the --member names.") pcmd.AddEnvironmentFlag(cmd, c.AuthenticatedCLICommand) pcmd.AddContextFlag(cmd, c.CLICommand) @@ -45,19 +45,19 @@ func parseMemberFlag(raw string) (switchoverv1.SwitchoverV1SwitchoverPairMember, for _, part := range strings.Split(raw, ",") { key, value, ok := strings.Cut(part, "=") if !ok { - return member, fmt.Errorf(`invalid --member value %q: expected "name=,id="`, raw) + return member, fmt.Errorf(`invalid --member value %q: expected "name=,crn="`, raw) } switch key { case "name": member.Name = value - case "id": + case "crn": member.MemberCrn = value default: - return member, fmt.Errorf(`invalid --member key %q: expected "name" or "id"`, key) + return member, fmt.Errorf(`invalid --member key %q: expected "name" or "crn"`, key) } } if member.Name == "" || member.MemberCrn == "" { - return member, fmt.Errorf(`invalid --member value %q: both "name" and "id" are required`, raw) + return member, fmt.Errorf(`invalid --member value %q: both "name" and "crn" are required`, raw) } return member, nil } @@ -92,13 +92,11 @@ func (c *command) create(cmd *cobra.Command, args []string) error { return err } - // The backend addresses every reference by CRN (ORC-9794): spec.environment_crn and each - // member's member_crn. Flags stay ID-based -- the CRNs are assembled here from the current - // organization plus the environment/cluster IDs the user supplied. + // Each member's CRN is supplied directly (crn=...) and carries its own environment, so members + // may live in different environments than the pair. The pair's own environment_crn is built from + // the current organization plus the --environment flag (which also drives the ?environment= + // query parameter). environmentCrn := fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s", c.Context.GetCurrentOrganization(), environmentId) - for i, member := range members { - members[i].MemberCrn = fmt.Sprintf("%s/cloud-cluster=%s", environmentCrn, member.MemberCrn) - } pair := switchoverv1.SwitchoverV1SwitchoverPair{ Spec: &switchoverv1.SwitchoverV1SwitchoverPairSpec{ From 67a0e6253eac0f1737eafdf096df54b1e2a56cb8 Mon Sep 17 00:00:00 2001 From: YOUR_FULL_NAME Date: Wed, 19 Aug 2026 08:23:08 -0700 Subject: [PATCH 3/5] switchover: don't send environment_crn on endpoint create CreateSwitchoverEndpointSpec accepts only display_name, parent_resource_crn, target, and endpoints -- not environment_crn (the endpoint's environment travels inside the parent pair's CRN). The CLI was setting spec.environment_crn, so every `endpoint create` failed with: Bad Request: unknown field "environment_crn" Build parent_resource_crn directly from the org + --environment + --switchover-pair and drop environment_crn from the request body. Found by running `endpoint create` against devel. Co-Authored-By: Claude Opus 5 (1M context) --- internal/switchover/endpoint/command_create.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/switchover/endpoint/command_create.go b/internal/switchover/endpoint/command_create.go index 7efa30b88e..22110927b7 100644 --- a/internal/switchover/endpoint/command_create.go +++ b/internal/switchover/endpoint/command_create.go @@ -98,15 +98,15 @@ func (c *command) create(cmd *cobra.Command, args []string) error { return err } - // Flags stay ID-based; the CRNs the backend expects are assembled here (ORC-9794). - environmentCrn := fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s", c.Context.GetCurrentOrganization(), environmentId) - parentResourceCrn := fmt.Sprintf("%s/switchover-pair=%s", environmentCrn, switchoverPairId) + // The endpoint's environment travels inside parent_resource_crn (the pair CRN); the create body + // does not take a separate environment_crn. The parent CRN is assembled from the current + // organization, the --environment flag, and the --switchover-pair ID. + parentResourceCrn := fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s/switchover-pair=%s", c.Context.GetCurrentOrganization(), environmentId, switchoverPairId) endpoint := switchoverv1.SwitchoverV1SwitchoverEndpoint{ Spec: &switchoverv1.SwitchoverV1SwitchoverEndpointSpec{ DisplayName: switchoverv1.PtrString(displayName), Endpoints: &endpoints, - EnvironmentCrn: switchoverv1.PtrString(environmentCrn), ParentResourceCrn: switchoverv1.PtrString(parentResourceCrn), }, } From 707b7fcf3e310a92b966af7e57d019280b185827 Mon Sep 17 00:00:00 2001 From: YOUR_FULL_NAME Date: Wed, 19 Aug 2026 08:32:06 -0700 Subject: [PATCH 4/5] switchover: assemble network_crn from the --endpoint network= value The --endpoint network= value was passed straight into network_crn, so the CLI put a bare network ID (e.g. "n-abc123") where the backend requires a full CRN (crn://confluent.cloud/organization=.../environment=.../network=) -- the request would fail the network_crn pattern. Assemble the network_crn in create() from the current organization + the endpoint's --environment + the given network id, matching how --member and parent_resource are handled. access-point is left as a full-CRN passthrough: its canonical form carries a gateway segment (.../gateway=/access-point=) that a plain access-point ID does not supply, so it cannot be assembled from an id alone. The flag help now says network= (assembled) vs access-point= (full). Verified against devel: network=n-abc123 is sent as crn://confluent.cloud/organization=.../environment=env-devc7mdr2w/network=n-abc123 and the backend accepts the CRN format (failing later on resolution, not format). Co-Authored-By: Claude Opus 5 (1M context) --- internal/switchover/endpoint/command_create.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/switchover/endpoint/command_create.go b/internal/switchover/endpoint/command_create.go index 22110927b7..9b10c3ccf3 100644 --- a/internal/switchover/endpoint/command_create.go +++ b/internal/switchover/endpoint/command_create.go @@ -29,7 +29,7 @@ func (c *command) newCreateCommand() *cobra.Command { } 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=,type=[,network=][,access-point=]". Must be specified exactly twice.`) + cmd.Flags().StringArray("endpoint", nil, `An endpoint side, in the form "name=,type=[,network=][,access-point=]". A private endpoint sets exactly one of network or access-point. network takes a network ID (the CLI builds its CRN); access-point takes a full CRN, since its canonical form includes a gateway segment. Must be specified exactly twice.`) pcmd.AddEnvironmentFlag(cmd, c.AuthenticatedCLICommand) pcmd.AddContextFlag(cmd, c.CLICommand) pcmd.AddOutputFlag(cmd) @@ -98,10 +98,22 @@ func (c *command) create(cmd *cobra.Command, args []string) error { return err } + organizationId := c.Context.GetCurrentOrganization() + // The endpoint's environment travels inside parent_resource_crn (the pair CRN); the create body // does not take a separate environment_crn. The parent CRN is assembled from the current // organization, the --environment flag, and the --switchover-pair ID. - parentResourceCrn := fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s/switchover-pair=%s", c.Context.GetCurrentOrganization(), environmentId, switchoverPairId) + parentResourceCrn := fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s/switchover-pair=%s", organizationId, environmentId, switchoverPairId) + + // network= is given as a plain network ID; assemble its network_crn here (org + the + // endpoint's environment + the id). access_point_crn is passed through as a full CRN because its + // canonical form carries a gateway segment the --endpoint flag does not supply. + for i := range endpoints { + if networkId := endpoints[i].EndpointFilter.GetNetworkCrn(); networkId != "" { + endpoints[i].EndpointFilter.NetworkCrn = switchoverv1.PtrString( + fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s/network=%s", organizationId, environmentId, networkId)) + } + } endpoint := switchoverv1.SwitchoverV1SwitchoverEndpoint{ Spec: &switchoverv1.SwitchoverV1SwitchoverEndpointSpec{ From f9a50d36546c815235ff8c031e581b9a780c7819 Mon Sep 17 00:00:00 2001 From: YOUR_FULL_NAME Date: Fri, 21 Aug 2026 08:33:35 -0700 Subject: [PATCH 5/5] switchover: update failover-type terminology to PLANNED/UNPLANNED (SDK sync) - trigger-switch --failover-type default CLEAN -> PLANNED; help text updated to PLANNED, UNPLANNED, or RESTORE (RESTORE unchanged), per api#2669. - Bump switchover SDK pin to pick up the updated phases/failover-type spec. Co-Authored-By: Claude Opus 4.8 --- go.mod | 2 +- go.sum | 4 ++-- internal/switchover/pair/command_trigger_switch.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index c3758d1845..5631adcbd8 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +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-20260818200130-b17ef6e8f63f + github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260821005945-1f1c05b15138 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 diff --git a/go.sum b/go.sum index a25e96b11f..5bf9cb5085 100644 --- a/go.sum +++ b/go.sum @@ -172,8 +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-20260818200130-b17ef6e8f63f h1:0K5J5Ke+qB9csUiyuNco+wJdxdklXoqFUoK9SZ2n1mo= -github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260818200130-b17ef6e8f63f/go.mod h1:yv1VtjqyqD7G5gpdrzR2hGyIAfEOuLA3aTQW0TZ6Iek= +github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260821005945-1f1c05b15138 h1:diX6mwK1Z71xyrH9sotG6/E/XFswd1QaxfKJxOv4StE= +github.com/confluentinc/ccloud-sdk-go-v2-internal/switchover v0.0.0-20260821005945-1f1c05b15138/go.mod h1:yv1VtjqyqD7G5gpdrzR2hGyIAfEOuLA3aTQW0TZ6Iek= 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= diff --git a/internal/switchover/pair/command_trigger_switch.go b/internal/switchover/pair/command_trigger_switch.go index 07b1ed5bf8..8cec4576a8 100644 --- a/internal/switchover/pair/command_trigger_switch.go +++ b/internal/switchover/pair/command_trigger_switch.go @@ -28,7 +28,7 @@ func (c *command) newTriggerSwitchCommand() *cobra.Command { } cmd.Flags().String("active-member", "", "The name of the member to promote to active. If omitted, the other member is promoted.") - cmd.Flags().String("failover-type", "CLEAN", "The failover semantics to apply: CLEAN, UNCLEAN, or RESTORE.") + cmd.Flags().String("failover-type", "PLANNED", "The failover semantics to apply: PLANNED, UNPLANNED, or RESTORE.") cmd.Flags().Bool("force", false, "Skip the confirmation prompt.") pcmd.AddEnvironmentFlag(cmd, c.AuthenticatedCLICommand) pcmd.AddContextFlag(cmd, c.CLICommand)