diff --git a/Makefile b/Makefile index 7abd7c4d59..ea439f55e1 100644 --- a/Makefile +++ b/Makefile @@ -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 && \ diff --git a/go.mod b/go.mod index 77a4f6c6a7..a2e6356af0 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 diff --git a/go.sum b/go.sum index e761d8c46f..b775aa2806 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/internal/command.go b/internal/command.go index 0c8d38d04d..1028d86267 100644 --- a/internal/command.go +++ b/internal/command.go @@ -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" @@ -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)) diff --git a/internal/endpoint/command.go b/internal/endpoint/command.go new file mode 100644 index 0000000000..3a31efa778 --- /dev/null +++ b/internal/endpoint/command.go @@ -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}, + } + + c := &command{pcmd.NewAuthenticatedCLICommand(cmd, prerunner)} + + cmd.AddCommand(c.newListCommand()) + + return cmd +} diff --git a/internal/endpoint/command_list.go b/internal/endpoint/command_list.go new file mode 100644 index 0000000000..5727af3677 --- /dev/null +++ b/internal/endpoint/command_list.go @@ -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.") + + // 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() +} diff --git a/pkg/ccloudv2/client.go b/pkg/ccloudv2/client.go index 3ff7e6cf73..310fca3ab2 100644 --- a/pkg/ccloudv2/client.go +++ b/pkg/ccloudv2/client.go @@ -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" @@ -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 @@ -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), diff --git a/pkg/ccloudv2/endpoint.go b/pkg/ccloudv2/endpoint.go new file mode 100644 index 0000000000..24fe2f8e0a --- /dev/null +++ b/pkg/ccloudv2/endpoint.go @@ -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) +} diff --git a/test/endpoint_test.go b/test/endpoint_test.go new file mode 100644 index 0000000000..40dfba1e00 --- /dev/null +++ b/test/endpoint_test.go @@ -0,0 +1,77 @@ +package test + +func (s *CLITestSuite) TestEndpointList() { + tests := []CLITest{ + // Basic list with required flags + { + args: "endpoint list --service KAFKA --environment env-00000", + fixture: "endpoint/list.golden", + }, + + // List with cloud filter + { + args: "endpoint list --service KAFKA --environment env-00000 --cloud AWS", + fixture: "endpoint/list-cloud-filter.golden", + }, + + // List with region filter + { + args: "endpoint list --service KAFKA --environment env-00000 --region us-west-2", + fixture: "endpoint/list-region-filter.golden", + }, + + // List private endpoints only + { + args: "endpoint list --service KAFKA --environment env-00000 --is-private=true", + fixture: "endpoint/list-private.golden", + }, + + // List public endpoints only + { + args: "endpoint list --service KAFKA --environment env-00000 --is-private=false", + fixture: "endpoint/list-public.golden", + }, + + // List with resource filter + { + args: "endpoint list --service KAFKA --environment env-00000 --resource lkc-abc123", + fixture: "endpoint/list-resource-filter.golden", + }, + + // List with multiple filters + { + args: "endpoint list --service KAFKA --environment env-00000 --cloud AWS --region us-west-2 --is-private=true", + fixture: "endpoint/list-multiple-filters.golden", + }, + + // List Schema Registry endpoints + { + args: "endpoint list --service SCHEMA_REGISTRY --environment env-00000", + fixture: "endpoint/list-schema-registry.golden", + }, + + // JSON output + { + args: "endpoint list --service KAFKA --environment env-00000 --output json", + fixture: "endpoint/list-json.golden", + }, + + // YAML output + { + args: "endpoint list --service KAFKA --environment env-00000 --output yaml", + fixture: "endpoint/list-yaml.golden", + }, + + // Missing required service flag + { + args: "endpoint list --environment env-00000", + fixture: "endpoint/list-missing-service.golden", + exitCode: 1, + }, + } + + for _, test := range tests { + test.login = "cloud" + s.runIntegrationTest(test) + } +} diff --git a/test/fixtures/output/endpoint/help.golden b/test/fixtures/output/endpoint/help.golden new file mode 100644 index 0000000000..bae4c959f6 --- /dev/null +++ b/test/fixtures/output/endpoint/help.golden @@ -0,0 +1,14 @@ +Manage Confluent Cloud endpoints. + +Usage: + confluent endpoint [command] + +Available Commands: + list List endpoints. + +Global Flags: + -h, --help Show help for this command. + --unsafe-trace Equivalent to -vvvv, but also log HTTP requests and responses which might contain plaintext secrets. + -v, --verbose count Increase verbosity (-v for warn, -vv for info, -vvv for debug, -vvvv for trace). + +Use "confluent endpoint [command] --help" for more information about a command. diff --git a/test/fixtures/output/endpoint/list-cloud-filter.golden b/test/fixtures/output/endpoint/list-cloud-filter.golden new file mode 100644 index 0000000000..320526a583 --- /dev/null +++ b/test/fixtures/output/endpoint/list-cloud-filter.golden @@ -0,0 +1,4 @@ + ID | Cloud | Region | Service | Is Private | Connection Type | Endpoint URL | Endpoint Type | Environment | Resource | Gateway | Access Point +----------+-------+-----------+---------+------------+-----------------+------------------------------------------------------------------+---------------+-------------+------------+----------+--------------- + e-67890 | AWS | us-east-1 | KAFKA | false | PUBLIC | https://pkc-xyz789.us-east-1.aws.confluent.cloud:443 | REST | env-00000 | lkc-xyz789 | | + e-12345 | AWS | us-west-2 | KAFKA | true | PRIVATE_LINK | https://lkc-abc123-ap12345.us-west-2.aws.glb.confluent.cloud:443 | REST | env-00000 | lkc-abc123 | gw-12345 | ap-12345 diff --git a/test/fixtures/output/endpoint/list-help.golden b/test/fixtures/output/endpoint/list-help.golden new file mode 100644 index 0000000000..809b1b16a0 --- /dev/null +++ b/test/fixtures/output/endpoint/list-help.golden @@ -0,0 +1,19 @@ +List endpoints. + +Usage: + confluent endpoint list [flags] + +Flags: + --service string REQUIRED: Filter by service type (KAFKA, KSQL, SCHEMA_REGISTRY, etc.). + --environment string Environment ID. + --cloud string Filter by cloud provider (AWS, GCP, AZURE). + --region string Filter by region. + --is-private Filter by privacy (true for private, false for public). + --resource string Filter by resource ID. + --context string CLI context name. + -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") + +Global Flags: + -h, --help Show help for this command. + --unsafe-trace Equivalent to -vvvv, but also log HTTP requests and responses which might contain plaintext secrets. + -v, --verbose count Increase verbosity (-v for warn, -vv for info, -vvv for debug, -vvvv for trace). diff --git a/test/fixtures/output/endpoint/list-json.golden b/test/fixtures/output/endpoint/list-json.golden new file mode 100644 index 0000000000..78ffae763c --- /dev/null +++ b/test/fixtures/output/endpoint/list-json.golden @@ -0,0 +1,28 @@ +[ + { + "id": "e-67890", + "cloud": "AWS", + "region": "us-east-1", + "service": "KAFKA", + "is_private": false, + "connection_type": "PUBLIC", + "endpoint": "https://pkc-xyz789.us-east-1.aws.confluent.cloud:443", + "endpoint_type": "REST", + "environment": "env-00000", + "resource": "lkc-xyz789" + }, + { + "id": "e-12345", + "cloud": "AWS", + "region": "us-west-2", + "service": "KAFKA", + "is_private": true, + "connection_type": "PRIVATE_LINK", + "endpoint": "https://lkc-abc123-ap12345.us-west-2.aws.glb.confluent.cloud:443", + "endpoint_type": "REST", + "environment": "env-00000", + "resource": "lkc-abc123", + "gateway": "gw-12345", + "access_point": "ap-12345" + } +] diff --git a/test/fixtures/output/endpoint/list-missing-service.golden b/test/fixtures/output/endpoint/list-missing-service.golden new file mode 100644 index 0000000000..18d7e508c3 --- /dev/null +++ b/test/fixtures/output/endpoint/list-missing-service.golden @@ -0,0 +1,19 @@ +Error: required flag(s) "service" not set +Usage: + confluent endpoint list [flags] + +Flags: + --service string REQUIRED: Filter by service type (KAFKA, KSQL, SCHEMA_REGISTRY, etc.). + --environment string Environment ID. + --cloud string Filter by cloud provider (AWS, GCP, AZURE). + --region string Filter by region. + --is-private Filter by privacy (true for private, false for public). + --resource string Filter by resource ID. + --context string CLI context name. + -o, --output string Specify the output format as "human", "json", or "yaml". (default "human") + +Global Flags: + -h, --help Show help for this command. + --unsafe-trace Equivalent to -vvvv, but also log HTTP requests and responses which might contain plaintext secrets. + -v, --verbose count Increase verbosity (-v for warn, -vv for info, -vvv for debug, -vvvv for trace). + diff --git a/test/fixtures/output/endpoint/list-multiple-filters.golden b/test/fixtures/output/endpoint/list-multiple-filters.golden new file mode 100644 index 0000000000..46c34a3184 --- /dev/null +++ b/test/fixtures/output/endpoint/list-multiple-filters.golden @@ -0,0 +1,3 @@ + ID | Cloud | Region | Service | Is Private | Connection Type | Endpoint URL | Endpoint Type | Environment | Resource | Gateway | Access Point +----------+-------+-----------+---------+------------+-----------------+------------------------------------------------------------------+---------------+-------------+------------+----------+--------------- + e-12345 | AWS | us-west-2 | KAFKA | true | PRIVATE_LINK | https://lkc-abc123-ap12345.us-west-2.aws.glb.confluent.cloud:443 | REST | env-00000 | lkc-abc123 | gw-12345 | ap-12345 diff --git a/test/fixtures/output/endpoint/list-private.golden b/test/fixtures/output/endpoint/list-private.golden new file mode 100644 index 0000000000..46c34a3184 --- /dev/null +++ b/test/fixtures/output/endpoint/list-private.golden @@ -0,0 +1,3 @@ + ID | Cloud | Region | Service | Is Private | Connection Type | Endpoint URL | Endpoint Type | Environment | Resource | Gateway | Access Point +----------+-------+-----------+---------+------------+-----------------+------------------------------------------------------------------+---------------+-------------+------------+----------+--------------- + e-12345 | AWS | us-west-2 | KAFKA | true | PRIVATE_LINK | https://lkc-abc123-ap12345.us-west-2.aws.glb.confluent.cloud:443 | REST | env-00000 | lkc-abc123 | gw-12345 | ap-12345 diff --git a/test/fixtures/output/endpoint/list-public.golden b/test/fixtures/output/endpoint/list-public.golden new file mode 100644 index 0000000000..88dff08367 --- /dev/null +++ b/test/fixtures/output/endpoint/list-public.golden @@ -0,0 +1,3 @@ + ID | Cloud | Region | Service | Is Private | Connection Type | Endpoint URL | Endpoint Type | Environment | Resource | Gateway | Access Point +----------+-------+-----------+---------+------------+-----------------+------------------------------------------------------+---------------+-------------+------------+---------+--------------- + e-67890 | AWS | us-east-1 | KAFKA | false | PUBLIC | https://pkc-xyz789.us-east-1.aws.confluent.cloud:443 | REST | env-00000 | lkc-xyz789 | | diff --git a/test/fixtures/output/endpoint/list-region-filter.golden b/test/fixtures/output/endpoint/list-region-filter.golden new file mode 100644 index 0000000000..46c34a3184 --- /dev/null +++ b/test/fixtures/output/endpoint/list-region-filter.golden @@ -0,0 +1,3 @@ + ID | Cloud | Region | Service | Is Private | Connection Type | Endpoint URL | Endpoint Type | Environment | Resource | Gateway | Access Point +----------+-------+-----------+---------+------------+-----------------+------------------------------------------------------------------+---------------+-------------+------------+----------+--------------- + e-12345 | AWS | us-west-2 | KAFKA | true | PRIVATE_LINK | https://lkc-abc123-ap12345.us-west-2.aws.glb.confluent.cloud:443 | REST | env-00000 | lkc-abc123 | gw-12345 | ap-12345 diff --git a/test/fixtures/output/endpoint/list-resource-filter.golden b/test/fixtures/output/endpoint/list-resource-filter.golden new file mode 100644 index 0000000000..46c34a3184 --- /dev/null +++ b/test/fixtures/output/endpoint/list-resource-filter.golden @@ -0,0 +1,3 @@ + ID | Cloud | Region | Service | Is Private | Connection Type | Endpoint URL | Endpoint Type | Environment | Resource | Gateway | Access Point +----------+-------+-----------+---------+------------+-----------------+------------------------------------------------------------------+---------------+-------------+------------+----------+--------------- + e-12345 | AWS | us-west-2 | KAFKA | true | PRIVATE_LINK | https://lkc-abc123-ap12345.us-west-2.aws.glb.confluent.cloud:443 | REST | env-00000 | lkc-abc123 | gw-12345 | ap-12345 diff --git a/test/fixtures/output/endpoint/list-schema-registry.golden b/test/fixtures/output/endpoint/list-schema-registry.golden new file mode 100644 index 0000000000..d32c0aa96d --- /dev/null +++ b/test/fixtures/output/endpoint/list-schema-registry.golden @@ -0,0 +1,3 @@ + ID | Cloud | Region | Service | Is Private | Connection Type | Endpoint URL | Endpoint Type | Environment | Resource | Gateway | Access Point +-----------+-------+-------------+-----------------+------------+-------------------------+-----------------------------------------------------+---------------+-------------+-------------+----------+--------------- + e-abc456 | GCP | us-central1 | SCHEMA_REGISTRY | true | PRIVATE_SERVICE_CONNECT | https://psrc-abc456.us-central1.gcp.confluent.cloud | REST | env-00000 | lsrc-def789 | gw-67890 | ap-34567 diff --git a/test/fixtures/output/endpoint/list-yaml.golden b/test/fixtures/output/endpoint/list-yaml.golden new file mode 100644 index 0000000000..897b5a9783 --- /dev/null +++ b/test/fixtures/output/endpoint/list-yaml.golden @@ -0,0 +1,22 @@ +- id: e-67890 + cloud: AWS + region: us-east-1 + service: KAFKA + is_private: false + connection_type: PUBLIC + endpoint: https://pkc-xyz789.us-east-1.aws.confluent.cloud:443 + endpoint_type: REST + environment: env-00000 + resource: lkc-xyz789 +- id: e-12345 + cloud: AWS + region: us-west-2 + service: KAFKA + is_private: true + connection_type: PRIVATE_LINK + endpoint: https://lkc-abc123-ap12345.us-west-2.aws.glb.confluent.cloud:443 + endpoint_type: REST + environment: env-00000 + resource: lkc-abc123 + gateway: gw-12345 + access_point: ap-12345 diff --git a/test/fixtures/output/endpoint/list.golden b/test/fixtures/output/endpoint/list.golden new file mode 100644 index 0000000000..320526a583 --- /dev/null +++ b/test/fixtures/output/endpoint/list.golden @@ -0,0 +1,4 @@ + ID | Cloud | Region | Service | Is Private | Connection Type | Endpoint URL | Endpoint Type | Environment | Resource | Gateway | Access Point +----------+-------+-----------+---------+------------+-----------------+------------------------------------------------------------------+---------------+-------------+------------+----------+--------------- + e-67890 | AWS | us-east-1 | KAFKA | false | PUBLIC | https://pkc-xyz789.us-east-1.aws.confluent.cloud:443 | REST | env-00000 | lkc-xyz789 | | + e-12345 | AWS | us-west-2 | KAFKA | true | PRIVATE_LINK | https://lkc-abc123-ap12345.us-west-2.aws.glb.confluent.cloud:443 | REST | env-00000 | lkc-abc123 | gw-12345 | ap-12345 diff --git a/test/fixtures/output/help.golden b/test/fixtures/output/help.golden index 8bccbfc4bf..0c03aa5062 100644 --- a/test/fixtures/output/help.golden +++ b/test/fixtures/output/help.golden @@ -17,6 +17,7 @@ Available Commands: connect Manage Kafka Connect. context Manage CLI configuration contexts. custom-code-logging Manage custom code logging. + endpoint Manage Confluent Cloud endpoints. environment Manage and select Confluent Cloud environments. feedback Submit feedback for the Confluent CLI. flink Manage Apache Flink. diff --git a/test/test-server/ccloudv2_router.go b/test/test-server/ccloudv2_router.go index 862081e8af..1cdb168a41 100644 --- a/test/test-server/ccloudv2_router.go +++ b/test/test-server/ccloudv2_router.go @@ -40,6 +40,7 @@ var ccloudV2Routes = []route{ {"/ccl/v1/custom-code-loggings/{id}", handleCustomCodeLoggingsId}, {"/cmk/v2/clusters", handleCmkClusters}, {"/cmk/v2/clusters/{id}", handleCmkCluster}, + {"/endpoint/v1/endpoints", handleEndpoints}, {"/connect/v1/environments/{env}/clusters/{clusters}/connector-plugins", handlePlugins}, {"/connect/v1/environments/{env}/clusters/{clusters}/connector-plugins/{plugin}/config/validate", handlePluginValidate}, {"/connect/v1/environments/{env}/clusters/{clusters}/connectors", handleConnectors}, diff --git a/test/test-server/endpoint_handler.go b/test/test-server/endpoint_handler.go new file mode 100644 index 0000000000..402e661120 --- /dev/null +++ b/test/test-server/endpoint_handler.go @@ -0,0 +1,174 @@ +package testserver + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + endpointv1 "github.com/confluentinc/ccloud-sdk-go-v2/endpoint/v1" +) + +// Handler for: "/endpoint/v1/endpoints" +func handleEndpoints(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + handleEndpointList(t)(w, r) + } + } +} + +func handleEndpointList(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Parse query parameters + service := r.URL.Query().Get("service") + cloud := r.URL.Query().Get("cloud") + region := r.URL.Query().Get("region") + environment := r.URL.Query().Get("environment") + resource := r.URL.Query().Get("resource") + isPrivate := r.URL.Query().Get("is_private") + + // Build mock response based on filters + endpoints := []endpointv1.EndpointV1Endpoint{} + + // Helper function to check if endpoint should be included based on privacy filter + shouldIncludeByPrivacy := func(endpointIsPrivate bool) bool { + if isPrivate == "" { + return true // No filter, include all + } + if isPrivate == "true" && endpointIsPrivate { + return true // Filter for private, endpoint is private + } + if isPrivate == "false" && !endpointIsPrivate { + return true // Filter for public, endpoint is public + } + return false + } + + // Mock endpoint 1: AWS, private, KAFKA + if (cloud == "" || cloud == "AWS") && + (region == "" || region == "us-west-2") && + (service == "KAFKA") && + (resource == "" || resource == "lkc-abc123") && + shouldIncludeByPrivacy(true) { + endpoint1 := createMockEndpoint( + "e-12345", + "AWS", + "us-west-2", + "KAFKA", + true, + "PRIVATE_LINK", + "https://lkc-abc123-ap12345.us-west-2.aws.glb.confluent.cloud:443", + "REST", + environment, + "lkc-abc123", + "gw-12345", + "ap-12345", + ) + endpoints = append(endpoints, endpoint1) + } + + // Mock endpoint 2: AWS, public, KAFKA + if (cloud == "" || cloud == "AWS") && + (region == "" || region == "us-east-1") && + (service == "KAFKA") && + (resource == "" || resource == "lkc-xyz789") && + shouldIncludeByPrivacy(false) { + endpoint2 := createMockEndpoint( + "e-67890", + "AWS", + "us-east-1", + "KAFKA", + false, + "PUBLIC", + "https://pkc-xyz789.us-east-1.aws.confluent.cloud:443", + "REST", + environment, + "lkc-xyz789", + "", + "", + ) + endpoints = append(endpoints, endpoint2) + } + + // Mock endpoint 3: GCP, private, SCHEMA_REGISTRY + if (cloud == "" || cloud == "GCP") && + (region == "" || region == "us-central1") && + (service == "SCHEMA_REGISTRY") && + shouldIncludeByPrivacy(true) { + endpoint3 := createMockEndpoint( + "e-abc456", + "GCP", + "us-central1", + "SCHEMA_REGISTRY", + true, + "PRIVATE_SERVICE_CONNECT", + "https://psrc-abc456.us-central1.gcp.confluent.cloud", + "REST", + environment, + "lsrc-def789", + "gw-67890", + "ap-34567", + ) + endpoints = append(endpoints, endpoint3) + } + + // Build response + response := endpointv1.EndpointV1EndpointList{ + ApiVersion: "endpoint/v1", + Kind: "EndpointList", + Metadata: endpointv1.ListMeta{ + First: *endpointv1.NewNullableString(endpointv1.PtrString("https://api.confluent.cloud/endpoint/v1/endpoints")), + Last: *endpointv1.NewNullableString(endpointv1.PtrString("https://api.confluent.cloud/endpoint/v1/endpoints")), + }, + Data: endpoints, + } + + err := json.NewEncoder(w).Encode(response) + require.NoError(t, err) + } +} + +func createMockEndpoint(id, cloud, region, service string, isPrivate bool, connectionType, endpoint, endpointType, environment, resource, gateway, accessPoint string) endpointv1.EndpointV1Endpoint { + ep := endpointv1.EndpointV1Endpoint{ + ApiVersion: endpointv1.PtrString("endpoint/v1"), + Kind: endpointv1.PtrString("Endpoint"), + Id: endpointv1.PtrString(id), + Cloud: endpointv1.PtrString(cloud), + Region: endpointv1.PtrString(region), + Service: endpointv1.PtrString(service), + IsPrivate: endpointv1.PtrBool(isPrivate), + Endpoint: endpointv1.PtrString(endpoint), + } + + if connectionType != "" { + ep.ConnectionType = endpointv1.PtrString(connectionType) + } + if endpointType != "" { + ep.EndpointType = endpointv1.PtrString(endpointType) + } + if environment != "" { + ep.Environment = &endpointv1.ObjectReference{ + Id: environment, + } + } + if resource != "" { + ep.Resource = &endpointv1.TypedEnvScopedObjectReference{ + Id: resource, + } + } + if gateway != "" { + ep.Gateway = &endpointv1.ObjectReference{ + Id: gateway, + } + } + if accessPoint != "" { + ep.AccessPoint = &endpointv1.ObjectReference{ + Id: accessPoint, + } + } + + return ep +}