Skip to content
Open
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
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ else
endif

.PHONY: integration-test
integration-test:
integration-test: build-for-integration-test
ifdef CI
go install gotest.tools/gotestsum@v1.13.0 && \
export GOCOVERDIR=test/coverage && \
rm -rf $${GOCOVERDIR} && mkdir $${GOCOVERDIR} && \
gotestsum --junitfile integration-test-report.xml -- -timeout 0 -v -race $$(go list ./... | grep github.com/confluentinc/cli/v4/test) && \
gotestsum --junitfile integration-test-report.xml -- -timeout 0 -v -race $$(go list ./... | grep github.com/confluentinc/cli/v4/test) $(INTEGRATION_TEST_ARGS) && \
go tool covdata textfmt -i $${GOCOVERDIR} -o coverage.integration.out
else
export GOCOVERDIR=test/coverage && \
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ require (
github.com/confluentinc/ccloud-sdk-go-v2/cmk v0.25.0
github.com/confluentinc/ccloud-sdk-go-v2/connect v0.7.0
github.com/confluentinc/ccloud-sdk-go-v2/connect-custom-plugin v0.0.9
github.com/confluentinc/ccloud-sdk-go-v2/endpoint v0.4.0
github.com/confluentinc/ccloud-sdk-go-v2/flink v0.9.0
github.com/confluentinc/ccloud-sdk-go-v2/flink-artifact v0.3.0
github.com/confluentinc/ccloud-sdk-go-v2/flink-gateway v0.17.0
Expand Down Expand Up @@ -111,7 +112,7 @@ require (
go.uber.org/mock v0.4.0
golang.org/x/crypto v0.46.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/oauth2 v0.27.0
golang.org/x/oauth2 v0.34.0
golang.org/x/term v0.38.0
golang.org/x/text v0.32.0
google.golang.org/protobuf v1.34.2
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ github.com/confluentinc/ccloud-sdk-go-v2/connect v0.7.0 h1:ISrVOX9qJ2Sxiu/fGBqqH
github.com/confluentinc/ccloud-sdk-go-v2/connect v0.7.0/go.mod h1:zHG/3DzsnoHC81B1AY9K/8bMX3mxbIp5/nHHdypa//w=
github.com/confluentinc/ccloud-sdk-go-v2/connect-custom-plugin v0.0.9 h1:o1zKZlKbnN9uv+Y8TxwesBRryUl3lEU6lnfndEJigxQ=
github.com/confluentinc/ccloud-sdk-go-v2/connect-custom-plugin v0.0.9/go.mod h1:TtTcSfm+/JvnfqEKglOZ32LIcsRbdtrQdI+TtcP7fiU=
github.com/confluentinc/ccloud-sdk-go-v2/endpoint v0.4.0 h1:Pd4oCibpSNfjyBNt9hOQv2EOHjKql1xc7hnU/qs7lvk=
github.com/confluentinc/ccloud-sdk-go-v2/endpoint v0.4.0/go.mod h1:xhkW77SQ3Dahj7/x05b8U5rhoI3sznz+oaZorAikCY4=
github.com/confluentinc/ccloud-sdk-go-v2/flink v0.9.0 h1:QqtIFEB5E3CIyGMJd7NQBEtc/k3K11PX7f4Fj7sPFdo=
github.com/confluentinc/ccloud-sdk-go-v2/flink v0.9.0/go.mod h1:GPj4sfR85OyiFQUMNEq1DtPOjYVAuE222Z6Mcapwa48=
github.com/confluentinc/ccloud-sdk-go-v2/flink-artifact v0.3.0 h1:DVWL3Y4b5azgCADubtyp3EhGZuyJkleINaTy2V3iius=
Expand Down Expand Up @@ -1024,8 +1026,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
2 changes: 2 additions & 0 deletions internal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/confluentinc/cli/v4/internal/connect"
"github.com/confluentinc/cli/v4/internal/context"
ccl "github.com/confluentinc/cli/v4/internal/custom-code-logging"
"github.com/confluentinc/cli/v4/internal/endpoint"
"github.com/confluentinc/cli/v4/internal/environment"
"github.com/confluentinc/cli/v4/internal/feedback"
"github.com/confluentinc/cli/v4/internal/flink"
Expand Down Expand Up @@ -117,6 +118,7 @@ func NewConfluentCommand(cfg *config.Config) *cobra.Command {
cmd.AddCommand(context.New(prerunner))
cmd.AddCommand(connect.New(cfg, prerunner))
cmd.AddCommand(ccl.New(cfg, prerunner))
cmd.AddCommand(endpoint.New(prerunner))
cmd.AddCommand(environment.New(prerunner))
cmd.AddCommand(feedback.New(prerunner))
cmd.AddCommand(flink.New(cfg, prerunner))
Expand Down
40 changes: 40 additions & 0 deletions internal/endpoint/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package endpoint

import (
"github.com/spf13/cobra"

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

type out struct {
Id string `human:"ID" serialized:"id"`
Cloud string `human:"Cloud" serialized:"cloud"`
Region string `human:"Region" serialized:"region"`
Service string `human:"Service" serialized:"service"`
IsPrivate bool `human:"Is Private" serialized:"is_private"`
ConnectionType string `human:"Connection Type,omitempty" serialized:"connection_type,omitempty"`
Endpoint string `human:"Endpoint URL" serialized:"endpoint"`
EndpointType string `human:"Endpoint Type,omitempty" serialized:"endpoint_type,omitempty"`
Environment string `human:"Environment" serialized:"environment"`
Resource string `human:"Resource,omitempty" serialized:"resource,omitempty"`
Gateway string `human:"Gateway,omitempty" serialized:"gateway,omitempty"`
AccessPoint string `human:"Access Point,omitempty" serialized:"access_point,omitempty"`
}

type command struct {
*pcmd.AuthenticatedCLICommand
}

func New(prerunner pcmd.PreRunner) *cobra.Command {
cmd := &cobra.Command{
Use: "endpoint",
Short: "Manage Confluent Cloud endpoints.",
Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin},
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The top-level endpoint command is gated by RequireCloudLogin, which allows API-key based cloud contexts. This command uses V2Client/ListEndpoints which authenticates with an OAuth access token from the context; API-key contexts generally won't have that token and the request will fail. Use RequireNonAPIKeyCloudLogin (as done by other V2 Cloud resource commands) or otherwise implement API-key auth for the Endpoint API client.

Suggested change
Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireCloudLogin},
Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin},

Copilot uses AI. Check for mistakes.
}

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

cmd.AddCommand(c.newListCommand())

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

import (
"sort"
"strings"

"github.com/spf13/cobra"

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

func (c *command) newListCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List endpoints.",
Args: cobra.NoArgs,
RunE: c.list,
}

// Required flags
cmd.Flags().String("service", "", "Filter by service type (KAFKA, KSQL, SCHEMA_REGISTRY, etc.).")
cobra.CheckErr(cmd.MarkFlagRequired("service"))
pcmd.AddEnvironmentFlag(cmd, c.AuthenticatedCLICommand)

// Optional filter flags
cmd.Flags().String("cloud", "", "Filter by cloud provider (AWS, GCP, AZURE).")
cmd.Flags().String("region", "", "Filter by region.")
cmd.Flags().Bool("is-private", false, "Filter by privacy (true for private, false for public).")
cmd.Flags().String("resource", "", "Filter by resource ID.")
Comment on lines +26 to +30
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This command defines string flags named "cloud" and "region". The CLI prerunner parses these flag names into the global context (used for Flink settings) via Context.ParseFlagsIntoContext, so running confluent endpoint list --cloud/--region will mutate the user's Flink cloud/region selection as a side effect. Consider renaming these flags (e.g., "endpoint-cloud"/"endpoint-region") or updating ParseFlagsIntoContext to only apply these flags for Flink-related commands.

Copilot uses AI. Check for mistakes.

// Standard flags
pcmd.AddContextFlag(cmd, c.CLICommand)
pcmd.AddOutputFlag(cmd)

return cmd
}

func (c *command) list(cmd *cobra.Command, _ []string) error {
// Get environment ID
environmentId, err := c.Context.EnvironmentId()
if err != nil {
return err
}

// Get required service flag
service, err := cmd.Flags().GetString("service")
if err != nil {
return err
}

// Get optional filter flags
cloud, err := cmd.Flags().GetString("cloud")
if err != nil {
return err
}

region, err := cmd.Flags().GetString("region")
if err != nil {
return err
}

resource, err := cmd.Flags().GetString("resource")
if err != nil {
return err
}

// Handle is-private flag (optional boolean)
// nil means no filter, &true means filter for private, &false means filter for public
var isPrivate *bool
if cmd.Flags().Changed("is-private") {
val, err := cmd.Flags().GetBool("is-private")
if err != nil {
return err
}
isPrivate = &val
}

// Convert to uppercase for API consistency
service = strings.ToUpper(service)
if cloud != "" {
cloud = strings.ToUpper(cloud)
}

// Call API via client wrapper
endpoints, err := c.V2Client.ListEndpoints(environmentId, cloud, region, service, isPrivate, resource)
if err != nil {
return err
}

// Sort by Cloud, then Region, then ID for deterministic output
sort.Slice(endpoints, func(i, j int) bool {
if endpoints[i].GetCloud() != endpoints[j].GetCloud() {
return endpoints[i].GetCloud() < endpoints[j].GetCloud()
}
if endpoints[i].GetRegion() != endpoints[j].GetRegion() {
return endpoints[i].GetRegion() < endpoints[j].GetRegion()
}
return endpoints[i].GetId() < endpoints[j].GetId()
})

// Build output
list := output.NewList(cmd)
for _, endpoint := range endpoints {
out := &out{
Id: endpoint.GetId(),
Cloud: endpoint.GetCloud(),
Region: endpoint.GetRegion(),
Service: endpoint.GetService(),
IsPrivate: endpoint.GetIsPrivate(),
Endpoint: endpoint.GetEndpoint(),
}

// Add environment if present
if endpoint.Environment != nil {
out.Environment = endpoint.Environment.GetId()
}

// Add optional fields if present
if endpoint.ConnectionType != nil {
out.ConnectionType = endpoint.GetConnectionType()
}
if endpoint.EndpointType != nil {
out.EndpointType = endpoint.GetEndpointType()
}
if endpoint.Resource != nil {
out.Resource = endpoint.Resource.Id
}
if endpoint.Gateway != nil {
out.Gateway = endpoint.Gateway.Id
}
if endpoint.AccessPoint != nil {
out.AccessPoint = endpoint.AccessPoint.Id
}

list.Add(out)
}

// Sort and filter fields for display
list.Sort(false)
list.Filter([]string{"Id", "Cloud", "Region", "Service", "IsPrivate", "ConnectionType", "Endpoint", "EndpointType", "Environment", "Resource", "Gateway", "AccessPoint"})

return list.Print()
}
3 changes: 3 additions & 0 deletions pkg/ccloudv2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
cmkv2 "github.com/confluentinc/ccloud-sdk-go-v2/cmk/v2"
connectcustompluginv1 "github.com/confluentinc/ccloud-sdk-go-v2/connect-custom-plugin/v1"
connectv1 "github.com/confluentinc/ccloud-sdk-go-v2/connect/v1"
endpointv1 "github.com/confluentinc/ccloud-sdk-go-v2/endpoint/v1"
flinkartifactv1 "github.com/confluentinc/ccloud-sdk-go-v2/flink-artifact/v1"
flinkv2 "github.com/confluentinc/ccloud-sdk-go-v2/flink/v2"
iamIpFilter "github.com/confluentinc/ccloud-sdk-go-v2/iam-ip-filtering/v2"
Expand Down Expand Up @@ -57,6 +58,7 @@ type Client struct {
ConnectArtifactClient *camv1.APIClient
ConnectCustomPluginClient *connectcustompluginv1.APIClient
Cclv1Client *cclv1.APIClient
EndpointClient *endpointv1.APIClient
FlinkArtifactClient *flinkartifactv1.APIClient
FlinkClient *flinkv2.APIClient
IamClient *iamv2.APIClient
Expand Down Expand Up @@ -107,6 +109,7 @@ func NewClient(cfg *config.Config, unsafeTrace bool) *Client {
ConnectCustomPluginClient: newConnectCustomPluginClient(httpClient, url, userAgent, unsafeTrace),
ConnectArtifactClient: newConnectArtifactClient(httpClient, url, userAgent, unsafeTrace),
Cclv1Client: newCclClient(httpClient, url, userAgent, unsafeTrace),
EndpointClient: newEndpointClient(httpClient, url, userAgent, unsafeTrace),
FlinkArtifactClient: newFlinkArtifactClient(httpClient, url, userAgent, unsafeTrace),
FlinkClient: newFlinkClient(httpClient, url, userAgent, unsafeTrace),
IamClient: newIamClient(httpClient, url, userAgent, unsafeTrace),
Expand Down
73 changes: 73 additions & 0 deletions pkg/ccloudv2/endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package ccloudv2

import (
"context"
"net/http"

endpointv1 "github.com/confluentinc/ccloud-sdk-go-v2/endpoint/v1"

"github.com/confluentinc/cli/v4/pkg/errors"
)

func newEndpointClient(httpClient *http.Client, url, userAgent string, unsafeTrace bool) *endpointv1.APIClient {
cfg := endpointv1.NewConfiguration()
cfg.Debug = unsafeTrace
cfg.HTTPClient = httpClient
cfg.Servers = endpointv1.ServerConfigurations{{URL: url}}
cfg.UserAgent = userAgent

return endpointv1.NewAPIClient(cfg)
}

func (c *Client) endpointApiContext() context.Context {
return context.WithValue(context.Background(), endpointv1.ContextAccessToken, c.cfg.Context().GetAuthToken())
}

func (c *Client) ListEndpoints(environment, cloud, region, service string, isPrivate *bool, resource string) ([]endpointv1.EndpointV1Endpoint, error) {
var list []endpointv1.EndpointV1Endpoint

done := false
pageToken := ""
for !done {
page, err := c.executeListEndpoints(environment, pageToken, cloud, region, service, isPrivate, resource)
if err != nil {
return nil, err
}
list = append(list, page.GetData()...)

pageToken, done, err = extractNextPageToken(page.GetMetadata().Next)
if err != nil {
return nil, err
}
}
return list, nil
}

func (c *Client) executeListEndpoints(environment, pageToken, cloud, region, service string, isPrivate *bool, resource string) (endpointv1.EndpointV1EndpointList, error) {
req := c.EndpointClient.EndpointsEndpointV1Api.ListEndpointV1Endpoints(c.endpointApiContext()).
Environment(environment).
PageSize(ccloudV2ListPageSize)

// Add optional filters
if cloud != "" {
req = req.Cloud(cloud)
}
if region != "" {
req = req.Region(region)
}
if service != "" {
req = req.Service(service)
}
if isPrivate != nil {
req = req.IsPrivate(*isPrivate)
}
if resource != "" {
req = req.Resource(resource)
}
if pageToken != "" {
req = req.PageToken(pageToken)
}

resp, httpResp, err := req.Execute()
return resp, errors.CatchCCloudV2Error(err, httpResp)
}
Loading