diff --git a/cmd/lint/main.go b/cmd/lint/main.go index ef64ceb9e1..d7f89a67c9 100644 --- a/cmd/lint/main.go +++ b/cmd/lint/main.go @@ -96,6 +96,7 @@ var flagRules = []linter.FlagRule{ "message-send-max-retries", "request-required-acks", "schema-registry-cluster-id", + "schema-registry-subjects", "skip-message-on-error", "source-bootstrap-server", ), @@ -105,6 +106,7 @@ var flagRules = []linter.FlagRule{ linter.ExcludeFlag( "aws-account-id", "azure-subscription-id", + "gcp-project-id", "ca-cert-path", "client-cert-path", "client-key-path", @@ -129,6 +131,7 @@ var flagRules = []linter.FlagRule{ "request-timeout-ms", "retry-backoff-ms", "schema-registry-cluster-id", + "schema-registry-subjects", "skip-message-on-error", "socket-buffer-size", "source-api-key", @@ -308,7 +311,7 @@ func main() { for _, cfg := range configs { cfg.IsTest = true cfg.Version = new(pversion.Version) - + cmd := pcmd.NewConfluentCommand(cfg) if err := l.Lint(cmd); err != nil { fmt.Printf(`For context "%s", %v`, cfg.CurrentContext, err) diff --git a/go.mod b/go.mod index 4318965703..d8faa69c0f 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/confluentinc/cc-structs/kafka/util v0.1096.0 github.com/confluentinc/ccloud-sdk-go-v1 v0.0.108 github.com/confluentinc/ccloud-sdk-go-v2/apikeys v0.4.0 - github.com/confluentinc/ccloud-sdk-go-v2/cdx v0.0.2 + github.com/confluentinc/ccloud-sdk-go-v2/cdx v0.0.4 github.com/confluentinc/ccloud-sdk-go-v2/cli v0.1.0 github.com/confluentinc/ccloud-sdk-go-v2/cmk v0.6.0 github.com/confluentinc/ccloud-sdk-go-v2/connect v0.3.0 diff --git a/go.sum b/go.sum index fea19b5c42..99bf9deab8 100644 --- a/go.sum +++ b/go.sum @@ -637,8 +637,8 @@ github.com/confluentinc/ccloud-sdk-go-v2-internal/networking v0.0.5/go.mod h1:b0 github.com/confluentinc/ccloud-sdk-go-v2-internal/networking v0.0.7/go.mod h1:b0fKj4FlWseVcd8CsjXUdvvDtAGHu8nDUJjPykqWInQ= github.com/confluentinc/ccloud-sdk-go-v2/apikeys v0.4.0 h1:8fWyLwMuy8ec0MVF5Avd54UvbIxhDFhZzanHBVwgxdw= github.com/confluentinc/ccloud-sdk-go-v2/apikeys v0.4.0/go.mod h1:wNa9Qg2e2v/+PQsUyKh+qB22hhLkPR6Ahy6rP+1jmGI= -github.com/confluentinc/ccloud-sdk-go-v2/cdx v0.0.2 h1:i+wiKAGc7rF3bnwukPyxS0sfpuwtaoT9vBJ04cSCeuU= -github.com/confluentinc/ccloud-sdk-go-v2/cdx v0.0.2/go.mod h1:L8U9xs2duASJnjIYkwGrSbZNpApsbh+vlxsJlZMHJPA= +github.com/confluentinc/ccloud-sdk-go-v2/cdx v0.0.4 h1:9eTUPKELQQP81WBI1WKnb24U7DM6ludy81KnUGSZwMc= +github.com/confluentinc/ccloud-sdk-go-v2/cdx v0.0.4/go.mod h1:L8U9xs2duASJnjIYkwGrSbZNpApsbh+vlxsJlZMHJPA= github.com/confluentinc/ccloud-sdk-go-v2/cli v0.1.0 h1:YQFYJXWU4J10F2v4LhJm5OFSDe2PfZIHTHkzsbl1JuU= github.com/confluentinc/ccloud-sdk-go-v2/cli v0.1.0/go.mod h1:y9C4nB+n7+vy50G83aY19h/1iKr+z3MvhW1YPVdf+t4= github.com/confluentinc/ccloud-sdk-go-v2/cmk v0.6.0 h1:qOBunYd2bWo/IikZ60xMDcp/KfZ/ZahCOoeTjxXOoRU= diff --git a/internal/cmd/stream-share/command_consumer_redeem.go b/internal/cmd/stream-share/command_consumer_redeem.go index bd57e92fca..5de4b13e17 100644 --- a/internal/cmd/stream-share/command_consumer_redeem.go +++ b/internal/cmd/stream-share/command_consumer_redeem.go @@ -2,38 +2,79 @@ package streamshare import ( "fmt" + "strings" "github.com/spf13/cobra" + cdxv1 "github.com/confluentinc/ccloud-sdk-go-v2/cdx/v1" pcmd "github.com/confluentinc/cli/internal/pkg/cmd" "github.com/confluentinc/cli/internal/pkg/examples" "github.com/confluentinc/cli/internal/pkg/output" ) var ( - redeemTokenFields = []string{"Id", "ApiKey", "Secret", "KafkaBootstrapUrl", "Resources"} + redeemTokenFields = []string{"Id", "ApiKey", "ApiSecret", "KafkaBootstrapUrl", "SchemaRegistryApiKey", "SchemaRegistrySecret", "SchemaRegistryUrl", "Resources"} + redeemTokenPrivateLinkFields = []string{"NetworkDnsDomain", "NetworkZones", "NetworkZonalSubdomains", "NetworkKind", + "NetworkPrivateLinkDataType", "NetworkPrivateLinkData"} redeemTokenHumanLabelMap = map[string]string{ - "Id": "ID", - "ApiKey": "API Key", - "Secret": "Secret", - "KafkaBootstrapUrl": "Kafka Bootstrap URL", - "Resources": "Resources", + "Id": "ID", + "ApiKey": "API Key", + "ApiSecret": "API Secret", + "KafkaBootstrapUrl": "Kafka Bootstrap URL", + "SchemaRegistryApiKey": "Schema Registry API Key", + "SchemaRegistrySecret": "Schema Registry Secret", + "SchemaRegistryUrl": "Schema Registry URL", + "Resources": "Resources", + } + redeemTokenPrivateLinkHumanLabelMap = map[string]string{ + "NetworkDnsDomain": "Network DNS Domain", + "NetworkZones": "Network Zones", + "NetworkZonalSubdomains": "Network Zonal Subdomains", + "NetworkKind": "Network Kind", + "NetworkPrivateLinkDataType": "Network Private Link Data Type", + "NetworkPrivateLinkData": "Network Private Link Data", } redeemTokenStructuredLabelMap = map[string]string{ - "Id": "id", - "ApiKey": "api_key", - "Secret": "secret", - "KafkaBootstrapUrl": "kafka_bootstrap_url", - "Resources": "resources", + "Id": "id", + "ApiKey": "api_key", + "ApiSecret": "secret", + "KafkaBootstrapUrl": "kafka_bootstrap_url", + "SchemaRegistryApiKey": "schema_registry_api_key", + "SchemaRegistrySecret": "schema_registry_secret", + "SchemaRegistryUrl": "schema_registry_url", + "Resources": "resources", + } + redeemTokenPrivateLinkStructuredLabelMap = map[string]string{ + "NetworkDnsDomain": "network_dns_domain", + "NetworkZones": "network_zones", + "NetworkZonalSubdomains": "network_zonal_subdomains", + "NetworkKind": "network_kind", + "NetworkPrivateLinkDataType": "network_private_link_data_type", + "NetworkPrivateLinkData": "network_private_link_data", } ) +type privateLinkNetworkDetails struct { + networkKind string + privateLinkDataType string + privateLinkData interface{} +} + type redeemToken struct { - Id string - ApiKey string - Secret string - KafkaBootstrapUrl string - Resources []string + Id string + ApiKey string + ApiSecret string + KafkaBootstrapUrl string + SchemaRegistryApiKey string + SchemaRegistrySecret string + SchemaRegistryUrl string + Resources []string + NetworkDnsDomain string + NetworkZones string + NetworkZonalSubdomains []string + NetworkKind string + NetworkPrivateLinkDataType string + NetworkPrivateLinkData interface{} } func (c *command) newRedeemCommand() *cobra.Command { @@ -50,8 +91,9 @@ func (c *command) newRedeemCommand() *cobra.Command { ), } - cmd.Flags().String("aws-account-id", "", "The AWS account ID for the consumer network.") - cmd.Flags().String("azure-subscription-id", "", "The Azure subscription ID for the consumer network.") + cmd.Flags().String("aws-account-id", "", "Consumer's AWS account ID for PrivateLink access.") + cmd.Flags().String("azure-subscription-id", "", "Consumer's Azure subscription ID for PrivateLink access.") + cmd.Flags().String("gcp-project-id", "", "Consumer's GCP project ID for Private Service Connect access.") pcmd.AddOutputFlag(cmd) @@ -71,7 +113,12 @@ func (c *command) redeemShare(cmd *cobra.Command, args []string) error { return err } - redeemResponse, err := c.V2Client.RedeemSharedToken(token, awsAccountId, azureSubscriptionId) + gcpProjectId, err := cmd.Flags().GetString("gcp-project-id") + if err != nil { + return err + } + + redeemResponse, err := c.V2Client.RedeemSharedToken(token, awsAccountId, azureSubscriptionId, gcpProjectId) if err != nil { return err } @@ -79,21 +126,57 @@ func (c *command) redeemShare(cmd *cobra.Command, args []string) error { var resources []string for _, resource := range redeemResponse.GetResources() { if resource.CdxV1SharedTopic != nil { - resources = append(resources, fmt.Sprintf("%s:%s", resource.CdxV1SharedTopic.GetKind(), resource.CdxV1SharedTopic.GetTopic())) + resources = append(resources, fmt.Sprintf(`%s="%s"`, resource.CdxV1SharedTopic.GetKind(), resource.CdxV1SharedTopic.GetTopic())) } if resource.CdxV1SharedGroup != nil { - resources = append(resources, fmt.Sprintf("%s:%s", resource.CdxV1SharedGroup.GetKind(), resource.CdxV1SharedGroup.GetGroupPrefix())) + resources = append(resources, fmt.Sprintf(`Group_Prefix="%s"`, resource.CdxV1SharedGroup.GetGroupPrefix())) } } tokenObj := &redeemToken{ - Id: redeemResponse.GetId(), - ApiKey: redeemResponse.GetApikey(), - Secret: redeemResponse.GetSecret(), - KafkaBootstrapUrl: redeemResponse.GetKafkaBootstrapUrl(), - Resources: resources, + Id: redeemResponse.GetId(), + ApiKey: redeemResponse.GetApiKey(), + ApiSecret: redeemResponse.GetSecret(), + KafkaBootstrapUrl: redeemResponse.GetKafkaBootstrapUrl(), + SchemaRegistryApiKey: redeemResponse.GetSchemaRegistryApiKey(), + SchemaRegistrySecret: redeemResponse.GetSchemaRegistrySecret(), + SchemaRegistryUrl: redeemResponse.GetSchemaRegistryUrl(), + Resources: resources, + } + + // non private link cluster + if awsAccountId == "" && azureSubscriptionId == "" && gcpProjectId == "" { + return output.DescribeObject(cmd, tokenObj, redeemTokenFields, redeemTokenHumanLabelMap, redeemTokenStructuredLabelMap) + } + + return c.handlePrivateLinkClusterRedeem(cmd, redeemResponse, tokenObj) +} + +func (c *command) handlePrivateLinkClusterRedeem(cmd *cobra.Command, redeemResponse cdxv1.CdxV1RedeemTokenResponse, tokenObj *redeemToken) error { + consumerSharedResources, err := c.V2Client.ListConsumerSharedResources(redeemResponse.GetId()) + if err != nil { + return err + } + + var network cdxv1.CdxV1Network + if len(consumerSharedResources) != 0 { + privateNetwork, err := c.V2Client.GetPrivateLinkNetworkConfig(consumerSharedResources[0].GetId()) + if err != nil { + return err + } + network = privateNetwork } - return output.DescribeObject(cmd, tokenObj, redeemTokenFields, redeemTokenHumanLabelMap, redeemTokenStructuredLabelMap) + networkDetails := getPrivateLinkNetworkDetails(network) + tokenObj.NetworkDnsDomain = network.GetDnsDomain() + tokenObj.NetworkZones = strings.Join(network.GetZones(), ",") + tokenObj.NetworkZonalSubdomains = mapSubdomainsToList(network.GetZonalSubdomains()) + tokenObj.NetworkKind = networkDetails.networkKind + tokenObj.NetworkPrivateLinkDataType = networkDetails.privateLinkDataType + tokenObj.NetworkPrivateLinkData = networkDetails.privateLinkData + + return output.DescribeObject(cmd, tokenObj, append(redeemTokenFields, redeemTokenPrivateLinkFields...), + combineMaps(redeemTokenHumanLabelMap, redeemTokenPrivateLinkHumanLabelMap), + combineMaps(redeemTokenStructuredLabelMap, redeemTokenPrivateLinkStructuredLabelMap)) } diff --git a/internal/cmd/stream-share/command_consumer_share.go b/internal/cmd/stream-share/command_consumer_share.go index c120b3b509..28fcb5fee8 100644 --- a/internal/cmd/stream-share/command_consumer_share.go +++ b/internal/cmd/stream-share/command_consumer_share.go @@ -9,34 +9,40 @@ import ( ) var ( - consumerShareListFields = []string{"Id", "ProviderName", "Status", "SharedResourceId", "InviteExpiresAt"} - consumerShareListHumanLabels = []string{"ID", "Provider Name", "Status", "Shared Resource ID", "Invite Expires At"} - consumerShareListStructuredLabels = []string{"id", "provider_name", "status", "shared_resource_id", "invite_expires_at"} + consumerShareListFields = []string{"Id", "ProviderName", "ProviderOrganizationName", "Status", "InviteExpiresAt"} + consumerShareListHumanLabels = []string{"ID", "Provider Name", "Provider Organization Name", "Status", "Invite Expires At"} + consumerShareListStructuredLabels = []string{"id", "provider_user_name", "provider_organization_name", "status", "invite_expires_at"} ) var ( consumerHumanLabelMap = map[string]string{ - "Id": "ID", - "ProviderName": "Provider Name", - "Status": "Status", - "SharedResourceId": "Shared Resource ID", - "InviteExpiresAt": "Invite Expires At", + "Id": "ID", + "ProviderName": "Provider Name", + "ProviderOrganizationName": "Provider Organization Name", + "Status": "Status", + "InviteExpiresAt": "Invite Expires At", } consumerStructuredLabelMap = map[string]string{ - "Id": "id", - "ProviderName": "provider_name", - "Status": "status", - "SharedResourceId": "shared_resource_id", - "InviteExpiresAt": "invite_expires_at", + "Id": "id", + "ProviderName": "provider_user_name", + "ProviderOrganizationName": "provider_organization_name", + "Status": "status", + "InviteExpiresAt": "invite_expires_at", } ) type consumerShare struct { - Id string - ProviderName string - Status string - SharedResourceId string - InviteExpiresAt time.Time + Id string + ProviderName string + ProviderOrganizationName string + Status string + InviteExpiresAt time.Time + NetworkDnsDomain string + NetworkZones string + NetworkZonalSubdomains []string + NetworkKind string + NetworkPrivateLinkDataType string + NetworkPrivateLinkData interface{} } func (c *command) newConsumerShareCommand() *cobra.Command { @@ -78,12 +84,12 @@ func (c *command) autocompleteConsumerShares() []string { } func (c *command) buildConsumerShare(share cdxv1.CdxV1ConsumerShare) *consumerShare { - sharedResource := share.GetSharedResource() + status := share.GetStatus() return &consumerShare{ - Id: share.GetId(), - ProviderName: share.GetProviderUserName(), - Status: share.GetStatus(), - SharedResourceId: sharedResource.GetId(), - InviteExpiresAt: share.GetInviteExpiresAt(), + Id: share.GetId(), + ProviderName: share.GetProviderUserName(), + ProviderOrganizationName: share.GetProviderOrganizationName(), + Status: status.GetPhase(), + InviteExpiresAt: share.GetInviteExpiresAt(), } } diff --git a/internal/cmd/stream-share/command_consumer_share_describe.go b/internal/cmd/stream-share/command_consumer_share_describe.go index 982a577166..79820a1bb2 100644 --- a/internal/cmd/stream-share/command_consumer_share_describe.go +++ b/internal/cmd/stream-share/command_consumer_share_describe.go @@ -1,8 +1,11 @@ package streamshare import ( + "strings" + "github.com/spf13/cobra" + cdxv1 "github.com/confluentinc/ccloud-sdk-go-v2/cdx/v1" pcmd "github.com/confluentinc/cli/internal/pkg/cmd" "github.com/confluentinc/cli/internal/pkg/examples" "github.com/confluentinc/cli/internal/pkg/output" @@ -36,5 +39,39 @@ func (c *command) describeConsumerShare(cmd *cobra.Command, args []string) error return err } - return output.DescribeObject(cmd, c.buildConsumerShare(consumerShare), consumerShareListFields, consumerHumanLabelMap, consumerStructuredLabelMap) + consumerSharedResources, err := c.V2Client.ListConsumerSharedResources(shareId) + if err != nil { + return err + } + + var network cdxv1.CdxV1Network + if len(consumerSharedResources) > 0 { + privateNetwork, err := c.V2Client.GetPrivateLinkNetworkConfig(consumerSharedResources[0].GetId()) + if err != nil { + return err + } + network = privateNetwork + } + + consumerShareObj := c.buildConsumerShare(consumerShare) + cloud := network.GetCloud() + if cloud.CdxV1AwsNetwork == nil && cloud.CdxV1AzureNetwork == nil && cloud.CdxV1GcpNetwork == nil { + return output.DescribeObject(cmd, consumerShareObj, consumerShareListFields, consumerHumanLabelMap, consumerStructuredLabelMap) + } + + return c.handlePrivateLinkClusterConsumerShare(cmd, network, consumerShareObj) +} + +func (c *command) handlePrivateLinkClusterConsumerShare(cmd *cobra.Command, network cdxv1.CdxV1Network, consumerShareObj *consumerShare) error { + networkDetails := getPrivateLinkNetworkDetails(network) + consumerShareObj.NetworkDnsDomain = network.GetDnsDomain() + consumerShareObj.NetworkZones = strings.Join(network.GetZones(), ",") + consumerShareObj.NetworkZonalSubdomains = mapSubdomainsToList(network.GetZonalSubdomains()) + consumerShareObj.NetworkKind = networkDetails.networkKind + consumerShareObj.NetworkPrivateLinkDataType = networkDetails.privateLinkDataType + consumerShareObj.NetworkPrivateLinkData = networkDetails.privateLinkData + + return output.DescribeObject(cmd, consumerShareObj, append(consumerShareListFields, redeemTokenPrivateLinkFields...), + combineMaps(consumerHumanLabelMap, redeemTokenPrivateLinkHumanLabelMap), + combineMaps(consumerStructuredLabelMap, redeemTokenPrivateLinkStructuredLabelMap)) } diff --git a/internal/cmd/stream-share/command_provider.go b/internal/cmd/stream-share/command_provider.go index 36b56ea3a8..d096a87bf0 100644 --- a/internal/cmd/stream-share/command_provider.go +++ b/internal/cmd/stream-share/command_provider.go @@ -7,23 +7,20 @@ import ( ) var ( - providerShareListFields = []string{"Id", "ConsumerName", "ConsumerOrganizationName", "ProviderName", - "Status", "DeliveryMethod", "ServiceAccountId", "SharedResourceId", "InvitedAt", "RedeemedAt", "InviteExpiresAt"} - providerShareListHumanLabels = []string{"ID", "Consumer Name", "Consumer Organization Name", "Provider Name", - "Status", "Delivery Method", "Service Account ID", "Shared Resource ID", "Invited At", "Redeemed At", "Invite Expires At"} - providerShareListStructuredLabels = []string{"id", "consumer_name", "consumer_organization_name", "provider_name", - "status", "delivery_method", "service_account_id", "shared_resource_id", "invited_at", "redeemed_at", "invite_expires_at"} + providerShareListFields = []string{"Id", "ConsumerName", "ConsumerOrganizationName", + "Status", "DeliveryMethod", "InvitedAt", "RedeemedAt", "InviteExpiresAt"} + providerShareListHumanLabels = []string{"ID", "Consumer Name", "Consumer Organization Name", + "Status", "Delivery Method", "Invited At", "Redeemed At", "Invite Expires At"} + providerShareListStructuredLabels = []string{"id", "consumer_name", "consumer_organization_name", + "status", "delivery_method", "invited_at", "redeemed_at", "invite_expires_at"} ) type providerShare struct { Id string ConsumerName string ConsumerOrganizationName string - ProviderName string Status string DeliveryMethod string - ServiceAccountId string - SharedResourceId string RedeemedAt string InvitedAt time.Time InviteExpiresAt time.Time @@ -34,24 +31,18 @@ var ( "Id": "ID", "ConsumerName": "Consumer Name", "ConsumerOrganizationName": "Consumer Organization Name", - "ProviderName": "Provider Name", "Status": "Status", "DeliveryMethod": "Delivery Method", - "ServiceAccount": "Service Account", - "SharedResource": "Shared Resource", "RedeemedAt": "Redeemed At", "InvitedAt": "Invited At", "InviteExpiresAt": "Invite Expires At", } providerStructuredLabelMap = map[string]string{ "Id": "id", - "ConsumerName": "consumer_name", + "ConsumerName": "consumer_user_name", "ConsumerOrganizationName": "consumer_organization_name", - "ProviderName": "provider_name", "Status": "status", "DeliveryMethod": "delivery_method", - "ServiceAccountId": "service_account_id", - "SharedResourceId": "shared_resource_id", "RedeemedAt": "redeemed_at", "InvitedAt": "invited_at", "InviteExpiresAt": "invite_expires_at", @@ -65,6 +56,8 @@ func (c *command) newProviderCommand() *cobra.Command { } cmd.AddCommand(c.newInviteCommand()) + cmd.AddCommand(c.newOptInCommand()) + cmd.AddCommand(c.newOptOutCommand()) cmd.AddCommand(c.newProviderShareCommand()) return cmd diff --git a/internal/cmd/stream-share/command_provider_invite_create.go b/internal/cmd/stream-share/command_provider_invite_create.go index 60811bb0a2..9de3dc1ef8 100644 --- a/internal/cmd/stream-share/command_provider_invite_create.go +++ b/internal/cmd/stream-share/command_provider_invite_create.go @@ -26,6 +26,7 @@ func (c *command) newCreateEmailInviteCommand() *cobra.Command { cmd.Flags().String("topic", "", "Topic to be shared.") pcmd.AddEnvironmentFlag(cmd, c.AuthenticatedCLICommand) pcmd.AddClusterFlag(cmd, c.AuthenticatedCLICommand) + cmd.Flags().StringSlice("schema-registry-subjects", []string{}, "List of Schema Registry subjects (comma-separated).") pcmd.AddOutputFlag(cmd) _ = cmd.MarkFlagRequired("email") @@ -57,7 +58,18 @@ func (c *command) createEmailInvite(cmd *cobra.Command, _ []string) error { return err } - invite, err := c.V2Client.CreateInvite(environment, kafkaCluster, topic, email) + schemaRegistrySubjects, err := cmd.Flags().GetStringSlice("schema-registry-subjects") + if err != nil { + return err + } + + srCluster, err := c.Context.FetchSchemaRegistryByAccountId(cmd.Context(), c.EnvironmentId()) + if err != nil { + return err + } + + invite, err := c.V2Client.CreateProviderInvite(environment, kafkaCluster, topic, email, srCluster.Id, + c.Config.GetLastUsedOrgId(), schemaRegistrySubjects) if err != nil { return err } diff --git a/internal/cmd/stream-share/command_provider_opt_in.go b/internal/cmd/stream-share/command_provider_opt_in.go new file mode 100644 index 0000000000..46fb8184c9 --- /dev/null +++ b/internal/cmd/stream-share/command_provider_opt_in.go @@ -0,0 +1,26 @@ +package streamshare + +import ( + "github.com/spf13/cobra" + + "github.com/confluentinc/cli/internal/pkg/errors" + "github.com/confluentinc/cli/internal/pkg/utils" +) + +func (c *command) newOptInCommand() *cobra.Command { + return &cobra.Command{ + Use: "opt-in", + Short: "Opt in to stream sharing.", + RunE: c.optIn, + } +} + +func (c *command) optIn(cmd *cobra.Command, _ []string) error { + _, err := c.V2Client.StreamShareOptInOrOut(true) + if err != nil { + return err + } + + utils.Print(cmd, errors.OptInMsg) + return nil +} diff --git a/internal/cmd/stream-share/command_provider_opt_out.go b/internal/cmd/stream-share/command_provider_opt_out.go new file mode 100644 index 0000000000..849c6452b1 --- /dev/null +++ b/internal/cmd/stream-share/command_provider_opt_out.go @@ -0,0 +1,36 @@ +package streamshare + +import ( + "github.com/spf13/cobra" + + "github.com/confluentinc/cli/internal/pkg/errors" + "github.com/confluentinc/cli/internal/pkg/utils" +) + +func (c *command) newOptOutCommand() *cobra.Command { + return &cobra.Command{ + Use: "opt-out", + Short: "Opt out of stream sharing.", + RunE: c.optOut, + } +} + +func (c *command) optOut(cmd *cobra.Command, _ []string) error { + isDeleteConfirmed, err := confirmOptOut(cmd) + if err != nil { + return err + } + + if !isDeleteConfirmed { + utils.Println(cmd, "Operation terminated.") + return nil + } + + _, err = c.V2Client.StreamShareOptInOrOut(false) + if err != nil { + return err + } + + utils.Print(cmd, errors.OptOutMsg) + return nil +} diff --git a/internal/cmd/stream-share/command_provider_share.go b/internal/cmd/stream-share/command_provider_share.go index 989f8cc1ae..e412b6f253 100644 --- a/internal/cmd/stream-share/command_provider_share.go +++ b/internal/cmd/stream-share/command_provider_share.go @@ -45,17 +45,13 @@ func (c *command) autocompleteProviderShares() []string { } func (c *command) buildProviderShare(share cdxv1.CdxV1ProviderShare) *providerShare { - serviceAccount := share.GetServiceAccount() - sharedResource := share.GetSharedResource() + status := share.GetStatus() element := &providerShare{ Id: share.GetId(), ConsumerName: share.GetConsumerUserName(), ConsumerOrganizationName: share.GetConsumerOrganizationName(), - ProviderName: share.GetProviderUserName(), - Status: share.GetStatus(), + Status: status.GetPhase(), DeliveryMethod: share.GetDeliveryMethod(), - ServiceAccountId: serviceAccount.GetId(), - SharedResourceId: sharedResource.GetId(), InvitedAt: share.GetInvitedAt(), InviteExpiresAt: share.GetInviteExpiresAt(), } diff --git a/internal/cmd/stream-share/utils.go b/internal/cmd/stream-share/utils.go new file mode 100644 index 0000000000..d8a065ed17 --- /dev/null +++ b/internal/cmd/stream-share/utils.go @@ -0,0 +1,61 @@ +package streamshare + +import ( + "fmt" + "os" + + cdxv1 "github.com/confluentinc/ccloud-sdk-go-v2/cdx/v1" + "github.com/confluentinc/cli/internal/pkg/errors" + "github.com/confluentinc/cli/internal/pkg/form" + "github.com/spf13/cobra" +) + +func getPrivateLinkNetworkDetails(network cdxv1.CdxV1Network) *privateLinkNetworkDetails { + cloud := network.GetCloud() + var details privateLinkNetworkDetails + if cloud.CdxV1AwsNetwork != nil { + details.networkKind = cloud.CdxV1AwsNetwork.Kind + details.privateLinkDataType = "Private Link Endpoint Service" + details.privateLinkData = cloud.CdxV1AwsNetwork.GetPrivateLinkEndpointService() + } else if cloud.CdxV1AzureNetwork != nil { + details.networkKind = cloud.CdxV1AzureNetwork.Kind + details.privateLinkDataType = "Private Link Service Aliases" + details.privateLinkData = cloud.CdxV1AzureNetwork.GetPrivateLinkServiceAliases() + } else if cloud.CdxV1GcpNetwork != nil { + details.networkKind = cloud.CdxV1GcpNetwork.Kind + details.privateLinkDataType = "Private Service Connect Service Attachments" + details.privateLinkData = cloud.CdxV1GcpNetwork.GetPrivateServiceConnectServiceAttachments() + } + return &details +} + +func combineMaps(m1, m2 map[string]string) map[string]string { + for k, v := range m2 { + m1[k] = v + } + return m1 +} + +func mapSubdomainsToList(m map[string]string) []string { + var subdomains []string + for k, v := range m { + subdomains = append(subdomains, fmt.Sprintf(`%s="%s"`, k, v)) + } + + return subdomains +} + +func confirmOptOut(cmd *cobra.Command) (bool, error) { + f := form.New( + form.Field{ + ID: "confirmation", + Prompt: "Are you sure you want to disable Stream Sharing for your organization? " + + "Existing shares in your organization will not be accessible if Stream Sharing is disabled.", + IsYesOrNo: true, + }, + ) + if err := f.Prompt(cmd, form.NewPrompt(os.Stdin)); err != nil { + return false, errors.New(errors.FailedToReadOptOutConfirmationErrorMsg) + } + return f.Responses["confirmation"].(bool), nil +} diff --git a/internal/pkg/ccloudv2/cdx.go b/internal/pkg/ccloudv2/cdx.go index 4a190ea006..cbb189da74 100644 --- a/internal/pkg/ccloudv2/cdx.go +++ b/internal/pkg/ccloudv2/cdx.go @@ -6,7 +6,6 @@ import ( "net/http" cdxv1 "github.com/confluentinc/ccloud-sdk-go-v2/cdx/v1" - "github.com/confluentinc/cli/internal/pkg/errors" ) @@ -54,20 +53,26 @@ func (c *Client) DescribeConsumerShare(shareId string) (cdxv1.CdxV1ConsumerShare return resp, errors.CatchCCloudV2Error(err, httpResp) } -func (c *Client) CreateInvite(environment, kafkaCluster, topic, email string) (cdxv1.CdxV1ProviderShare, error) { +func (c *Client) CreateProviderInvite(environment, kafkaCluster, topic, email, srClusterId, orgId string, subjects []string) (cdxv1.CdxV1ProviderShare, error) { deliveryMethod := "Email" + + resources := []string{ + fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s/kafka=%s/topic=%s", orgId, environment, kafkaCluster, topic), + } + for _, subject := range subjects { + resources = append(resources, fmt.Sprintf("crn://confluent.cloud/organization=%s/environment=%s/schema-registry=%s/subject=%s", orgId, environment, srClusterId, subject)) + } + req := c.CdxClient.ProviderSharesCdxV1Api.CreateCdxV1ProviderShare(c.cdxApiContext()). - CdxV1CreateShareRequest(cdxv1.CdxV1CreateShareRequest{ - Environment: &environment, - KafkaCluster: &kafkaCluster, - ConsumerRestriction: &cdxv1.CdxV1CreateShareRequestConsumerRestrictionOneOf{ + CdxV1CreateProviderShareRequest(cdxv1.CdxV1CreateProviderShareRequest{ + ConsumerRestriction: &cdxv1.CdxV1CreateProviderShareRequestConsumerRestrictionOneOf{ CdxV1EmailConsumerRestriction: &cdxv1.CdxV1EmailConsumerRestriction{ Kind: deliveryMethod, Email: email, }, }, DeliveryMethod: &deliveryMethod, - Resources: &[]string{fmt.Sprintf("crn://confluent.cloud/kafka=%s/topic=%s", kafkaCluster, topic)}, + Resources: &resources, }) resp, httpResp, err := c.CdxClient.ProviderSharesCdxV1Api.CreateCdxV1ProviderShareExecute(req) return resp, errors.CatchCCloudV2Error(err, httpResp) @@ -100,7 +105,28 @@ func (c *Client) ListConsumerShares(sharedResource string) ([]cdxv1.CdxV1Consume done := false pageToken := "" for !done { - page, httpResp, err := c.executeListConsumerShares(sharedResource, pageToken) + page, r, err := c.executeListConsumerShares(sharedResource, pageToken) + if err != nil { + return nil, errors.CatchCCloudV2Error(err, r) + } + list = append(list, page.GetData()...) + + pageToken, done, err = extractCdxNextPageToken(page.GetMetadata().Next) + if err != nil { + return nil, err + } + } + + return list, nil +} + +func (c *Client) ListConsumerSharedResources(streamShareId string) ([]cdxv1.CdxV1ConsumerSharedResource, error) { + var list []cdxv1.CdxV1ConsumerSharedResource + + done := false + pageToken := "" + for !done { + page, httpResp, err := c.executeListConsumerSharedResources(streamShareId, pageToken) if err != nil { return nil, errors.CatchCCloudV2Error(err, httpResp) } @@ -115,6 +141,15 @@ func (c *Client) ListConsumerShares(sharedResource string) ([]cdxv1.CdxV1Consume return list, nil } +func (c *Client) executeListConsumerSharedResources(streamShareId, pageToken string) (cdxv1.CdxV1ConsumerSharedResourceList, *http.Response, error) { + req := c.CdxClient.ConsumerSharedResourcesCdxV1Api.ListCdxV1ConsumerSharedResources(c.cdxApiContext()). + StreamShare(streamShareId).PageSize(ccloudV2ListPageSize) + if pageToken != "" { + req = req.PageToken(pageToken) + } + return c.CdxClient.ConsumerSharedResourcesCdxV1Api.ListCdxV1ConsumerSharedResourcesExecute(req) +} + func (c *Client) executeListConsumerShares(sharedResource, pageToken string) (cdxv1.CdxV1ConsumerShareList, *http.Response, error) { req := c.CdxClient.ConsumerSharesCdxV1Api.ListCdxV1ConsumerShares(c.cdxApiContext()). SharedResource(sharedResource).PageSize(ccloudV2ListPageSize) @@ -141,13 +176,28 @@ func extractCdxNextPageToken(nextPageUrlStringNullable cdxv1.NullableString) (st return pageToken, false, err } -func (c *Client) RedeemSharedToken(token, awsAccountId, azureSubscriptionId string) (cdxv1.CdxV1RedeemTokenResponse, error) { +func (c *Client) RedeemSharedToken(token, awsAccountId, azureSubscriptionId, gcpProjectId string) (cdxv1.CdxV1RedeemTokenResponse, error) { redeemTokenRequest := cdxv1.CdxV1RedeemTokenRequest{ Token: &token, AwsAccount: &awsAccountId, AzureSubscription: &azureSubscriptionId, + GcpProject: &gcpProjectId, } req := c.CdxClient.SharedTokensCdxV1Api.RedeemCdxV1SharedToken(c.cdxApiContext()).CdxV1RedeemTokenRequest(redeemTokenRequest) resp, httpResp, err := c.CdxClient.SharedTokensCdxV1Api.RedeemCdxV1SharedTokenExecute(req) return resp, errors.CatchCCloudV2Error(err, httpResp) } + +func (c *Client) GetPrivateLinkNetworkConfig(sharedResourceId string) (cdxv1.CdxV1Network, error) { + req := c.CdxClient.ConsumerSharedResourcesCdxV1Api.NetworkCdxV1ConsumerSharedResource(c.cdxApiContext(), sharedResourceId) + resp, httpResp, err := c.CdxClient.ConsumerSharedResourcesCdxV1Api.NetworkCdxV1ConsumerSharedResourceExecute(req) + return resp, errors.CatchCCloudV2Error(err, httpResp) +} + +func (c *Client) StreamShareOptInOrOut(status bool) (cdxv1.CdxV1OptIn, error) { + req := c.CdxClient.OptInsCdxV1Api.UpdateCdxV1OptIn(c.cdxApiContext()).CdxV1OptIn(cdxv1.CdxV1OptIn{ + StreamShareEnabled: &status, + }) + resp, httpResp, err := c.CdxClient.OptInsCdxV1Api.UpdateCdxV1OptInExecute(req) + return resp, errors.CatchCCloudV2Error(err, httpResp) +} diff --git a/internal/pkg/ccloudv2/utils.go b/internal/pkg/ccloudv2/utils.go index 8b9eca9c66..ce65d9dc73 100644 --- a/internal/pkg/ccloudv2/utils.go +++ b/internal/pkg/ccloudv2/utils.go @@ -1,6 +1,7 @@ package ccloudv2 import ( + "context" "fmt" "net/http" "net/url" @@ -35,6 +36,9 @@ func IsCCloudURL(url string, isTest bool) bool { func newRetryableHttpClient(unsafeTrace bool) *http.Client { client := retryablehttp.NewClient() client.Logger = plog.NewLeveledLogger(unsafeTrace) + client.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) { + return resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500, err + } return client.StandardClient() } diff --git a/internal/pkg/errors/error_message.go b/internal/pkg/errors/error_message.go index b662e1495b..a6dbc38a31 100644 --- a/internal/pkg/errors/error_message.go +++ b/internal/pkg/errors/error_message.go @@ -455,6 +455,7 @@ const ( "To create an API key, use `confluent api-key create --resource %s`.\n" + "To store an existing API key, use `confluent api-key store --resource %s`." FailedToReadDeletionConfirmationErrorMsg = "failed to read your deletion confirmation" + FailedToReadOptOutConfirmationErrorMsg = "failed to read your opt out confirmation" // Flag parsing errors EnvironmentFlagWithApiLoginErrorMsg = `"environment" flag should not be passed for API key context` diff --git a/internal/pkg/errors/strings.go b/internal/pkg/errors/strings.go index 09085eb131..7521886917 100644 --- a/internal/pkg/errors/strings.go +++ b/internal/pkg/errors/strings.go @@ -146,5 +146,7 @@ const ( UpdateSuccessMsg = "Updated the %s of %s \"%s\" to \"%s\".\n" // Stream Sharing commands - ResendInviteMsg = `Sent invitation for "%s".` + ResendInviteMsg = "Sent invitation for \"%s\".\n" + OptInMsg = "Successfully opted in to Stream Sharing.\n" + OptOutMsg = "Successfully opted out of Stream Sharing.\n" ) diff --git a/test/fixtures/output/stream-share/create-invite.golden b/test/fixtures/output/stream-share/create-invite.golden index 0fd5e32f5a..a17b5f9f01 100644 --- a/test/fixtures/output/stream-share/create-invite.golden +++ b/test/fixtures/output/stream-share/create-invite.golden @@ -2,11 +2,8 @@ | ID | ss-12345 | | Consumer Name | consumer | | Consumer Organization Name | consumer org | -| Provider Name | provider | | Status | active | | Delivery Method | email | -| ServiceAccountId | sa-123456 | -| SharedResourceId | sr-12345 | | Invited At | 2022-07-20 22:08:41 +0000 UTC | | Redeemed At | 2022-07-21 22:08:41 +0000 UTC | | Invite Expires At | 2022-07-22 22:08:41 +0000 UTC | diff --git a/test/fixtures/output/stream-share/describe-consumer-share.golden b/test/fixtures/output/stream-share/describe-consumer-share.golden index a2e50eb9b0..928b78a8d2 100644 --- a/test/fixtures/output/stream-share/describe-consumer-share.golden +++ b/test/fixtures/output/stream-share/describe-consumer-share.golden @@ -1,7 +1,13 @@ -+--------------------+-------------------------------+ -| ID | ss-12345 | -| Provider Name | provider | -| Status | active | -| Shared Resource ID | sr-12345 | -| Invite Expires At | 2022-07-22 22:08:41 +0000 UTC | -+--------------------+-------------------------------+ ++--------------------------------+-------------------------------------------------------------+ +| ID | ss-12345 | +| Provider Name | provider | +| Provider Organization Name | provider org | +| Status | active | +| Invite Expires At | 2022-07-22 22:08:41 +0000 UTC | +| Network DNS Domain | abc123.us-west-2.aws.stag.cpdev.cloud | +| Network Zones | usw2-az1,usw2-az3,usw2-az2 | +| Network Zonal Subdomains | [usw2-az2="usw2-az2.abc123.us-west-2.aws.stag.cpdev.cloud"] | +| Network Kind | AwsNetwork | +| Network Private Link Data Type | Private Link Endpoint Service | +| Network Private Link Data | com.amazonaws.vpce.us-west-2.vpce-svc-0000000000 | ++--------------------------------+-------------------------------------------------------------+ diff --git a/test/fixtures/output/stream-share/describe-provider-share.golden b/test/fixtures/output/stream-share/describe-provider-share.golden index 0fd5e32f5a..a17b5f9f01 100644 --- a/test/fixtures/output/stream-share/describe-provider-share.golden +++ b/test/fixtures/output/stream-share/describe-provider-share.golden @@ -2,11 +2,8 @@ | ID | ss-12345 | | Consumer Name | consumer | | Consumer Organization Name | consumer org | -| Provider Name | provider | | Status | active | | Delivery Method | email | -| ServiceAccountId | sa-123456 | -| SharedResourceId | sr-12345 | | Invited At | 2022-07-20 22:08:41 +0000 UTC | | Redeemed At | 2022-07-21 22:08:41 +0000 UTC | | Invite Expires At | 2022-07-22 22:08:41 +0000 UTC | diff --git a/test/fixtures/output/stream-share/list-consumer-shares.golden b/test/fixtures/output/stream-share/list-consumer-shares.golden index 7b5c57cac1..110d82375d 100644 --- a/test/fixtures/output/stream-share/list-consumer-shares.golden +++ b/test/fixtures/output/stream-share/list-consumer-shares.golden @@ -1,3 +1,3 @@ - ID | Provider Name | Status | Shared Resource ID | Invite Expires At ------------+---------------+--------+--------------------+-------------------------------- - ss-12345 | provider | active | sr-12345 | 2022-07-22 22:08:41 +0000 UTC + ID | Provider Name | Provider Organization Name | Status | Invite Expires At +-----------+---------------+----------------------------+--------+-------------------------------- + ss-12345 | provider | provider org | active | 2022-07-22 22:08:41 +0000 UTC diff --git a/test/fixtures/output/stream-share/list-provider-shares.golden b/test/fixtures/output/stream-share/list-provider-shares.golden index c8f520641e..a3f51c3e64 100644 --- a/test/fixtures/output/stream-share/list-provider-shares.golden +++ b/test/fixtures/output/stream-share/list-provider-shares.golden @@ -1,3 +1,3 @@ - ID | Consumer Name | Consumer Organization Name | Provider Name | Status | Delivery Method | Service Account ID | Shared Resource ID | Invited At | Redeemed At | Invite Expires At ------------+---------------+----------------------------+---------------+--------+-----------------+--------------------+--------------------+-------------------------------+-------------------------------+-------------------------------- - ss-12345 | consumer | consumer org | provider | active | email | sa-123456 | sr-12345 | 2022-07-20 22:08:41 +0000 UTC | 2022-07-21 22:08:41 +0000 UTC | 2022-07-22 22:08:41 +0000 UTC + ID | Consumer Name | Consumer Organization Name | Status | Delivery Method | Invited At | Redeemed At | Invite Expires At +-----------+---------------+----------------------------+--------+-----------------+-------------------------------+-------------------------------+-------------------------------- + ss-12345 | consumer | consumer org | active | email | 2022-07-20 22:08:41 +0000 UTC | 2022-07-21 22:08:41 +0000 UTC | 2022-07-22 22:08:41 +0000 UTC diff --git a/test/fixtures/output/stream-share/opt-in.golden b/test/fixtures/output/stream-share/opt-in.golden new file mode 100644 index 0000000000..cdcc5a8e51 --- /dev/null +++ b/test/fixtures/output/stream-share/opt-in.golden @@ -0,0 +1 @@ +Successfully opted in to Stream Sharing. diff --git a/test/fixtures/output/stream-share/opt-out-accept.golden b/test/fixtures/output/stream-share/opt-out-accept.golden new file mode 100644 index 0000000000..3de07e8477 --- /dev/null +++ b/test/fixtures/output/stream-share/opt-out-accept.golden @@ -0,0 +1 @@ +Are you sure you want to disable Stream Sharing for your organization? Existing shares in your organization will not be accessible if Stream Sharing is disabled. (y/n): Successfully opted out of Stream Sharing. diff --git a/test/fixtures/output/stream-share/opt-out-decline.golden b/test/fixtures/output/stream-share/opt-out-decline.golden new file mode 100644 index 0000000000..e14a40ae5b --- /dev/null +++ b/test/fixtures/output/stream-share/opt-out-decline.golden @@ -0,0 +1 @@ +Are you sure you want to disable Stream Sharing for your organization? Existing shares in your organization will not be accessible if Stream Sharing is disabled. (y/n): Operation terminated. diff --git a/test/fixtures/output/stream-share/redeem-share-private-link.golden b/test/fixtures/output/stream-share/redeem-share-private-link.golden new file mode 100644 index 0000000000..a4bf3b1a57 --- /dev/null +++ b/test/fixtures/output/stream-share/redeem-share-private-link.golden @@ -0,0 +1,17 @@ ++--------------------------------+-------------------------------------------------------------+ +| ID | ss-12345 | +| API Key | 00000000000000000000 | +| API Secret | 00000000000000000000 | +| Kafka Bootstrap URL | pkc-00000.us-east1.gcp.confluent.cloud:9092 | +| Schema Registry API Key | 00000000000000000000 | +| Schema Registry Secret | 00000000000000000000 | +| Schema Registry URL | https://psrc-xyz123.us-west-2.aws.cpdev.cloud | +| Resources | [Topic="topic-12345" | +| | Group_Prefix="stream-share.ss-12345"] | +| Network DNS Domain | abc123.us-west-2.aws.stag.cpdev.cloud | +| Network Zones | usw2-az1,usw2-az3,usw2-az2 | +| Network Zonal Subdomains | [usw2-az2="usw2-az2.abc123.us-west-2.aws.stag.cpdev.cloud"] | +| Network Kind | AwsNetwork | +| Network Private Link Data Type | Private Link Endpoint Service | +| Network Private Link Data | com.amazonaws.vpce.us-west-2.vpce-svc-0000000000 | ++--------------------------------+-------------------------------------------------------------+ diff --git a/test/fixtures/output/stream-share/redeem-share.golden b/test/fixtures/output/stream-share/redeem-share.golden index 6b47a19ca5..b28533e320 100644 --- a/test/fixtures/output/stream-share/redeem-share.golden +++ b/test/fixtures/output/stream-share/redeem-share.golden @@ -1,8 +1,11 @@ -+---------------------+---------------------------------------------+ -| ID | ss-12345 | -| API Key | 00000000000000000000 | -| Secret | 00000000000000000000 | -| Kafka Bootstrap URL | pkc-00000.us-east1.gcp.confluent.cloud:9092 | -| Resources | [Topic:topic-12345 | -| | Group:stream-share.ss-12345] | -+---------------------+---------------------------------------------+ ++-------------------------+-----------------------------------------------+ +| ID | ss-12345 | +| API Key | 00000000000000000000 | +| API Secret | 00000000000000000000 | +| Kafka Bootstrap URL | pkc-00000.us-east1.gcp.confluent.cloud:9092 | +| Schema Registry API Key | 00000000000000000000 | +| Schema Registry Secret | 00000000000000000000 | +| Schema Registry URL | https://psrc-xyz123.us-west-2.aws.cpdev.cloud | +| Resources | [Topic="topic-12345" | +| | Group_Prefix="stream-share.ss-12345"] | ++-------------------------+-----------------------------------------------+ diff --git a/test/fixtures/output/stream-share/resend-invite.golden b/test/fixtures/output/stream-share/resend-invite.golden index 6f231807a7..dd3bbf0ec8 100644 --- a/test/fixtures/output/stream-share/resend-invite.golden +++ b/test/fixtures/output/stream-share/resend-invite.golden @@ -1 +1 @@ -Sent invitation for "ss-12345". \ No newline at end of file +Sent invitation for "ss-12345". diff --git a/test/streamshare_test.go b/test/streamshare_test.go index 023b91318a..14a9050642 100644 --- a/test/streamshare_test.go +++ b/test/streamshare_test.go @@ -1,17 +1,38 @@ package test +import ( + "strings" + + "github.com/confluentinc/bincover" +) + func (s *CLITestSuite) TestStreamShare() { tests := []CLITest{ {args: "stream-share provider share list --shared-resource sr-12345", fixture: "stream-share/list-provider-shares.golden"}, {args: "stream-share provider share describe ss-12345", fixture: "stream-share/describe-provider-share.golden"}, {args: "stream-share provider share delete ss-12345", fixture: "stream-share/delete-provider-share.golden"}, - {args: "stream-share provider invite create --email user@example.com --topic topic-12345 --environment env-12345 --cluster lkc-12345", fixture: "stream-share/create-invite.golden"}, + + {args: "stream-share provider invite create --email user@example.com --topic topic-12345 --environment env-12345 --cluster lkc-12345 --schema-registry-subjects sub1,sub2,sub3", fixture: "stream-share/create-invite.golden"}, {args: "stream-share provider invite resend ss-12345", fixture: "stream-share/resend-invite.golden"}, + {args: "stream-share provider opt-in", fixture: "stream-share/opt-in.golden"}, + { + preCmdFuncs: []bincover.PreCmdFunc{stdinPipeFunc(strings.NewReader("y\n"))}, + args: "stream-share provider opt-out", + fixture: "stream-share/opt-out-accept.golden", + }, + { + preCmdFuncs: []bincover.PreCmdFunc{stdinPipeFunc(strings.NewReader("n\n"))}, + args: "stream-share provider opt-out", + fixture: "stream-share/opt-out-decline.golden", + }, + {args: "stream-share consumer share list --shared-resource sr-12345", fixture: "stream-share/list-consumer-shares.golden"}, {args: "stream-share consumer share delete ss-12345", fixture: "stream-share/delete-consumer-share.golden"}, {args: "stream-share consumer share describe ss-12345", fixture: "stream-share/describe-consumer-share.golden"}, + {args: "stream-share consumer redeem stream-share-token", fixture: "stream-share/redeem-share.golden"}, + {args: "stream-share consumer redeem stream-share-token --aws-account-id 111111111111", fixture: "stream-share/redeem-share-private-link.golden"}, } for _, tt := range tests { diff --git a/test/test-server/ccloudv2_router.go b/test/test-server/ccloudv2_router.go index 2da79a4ff1..55d372613d 100644 --- a/test/test-server/ccloudv2_router.go +++ b/test/test-server/ccloudv2_router.go @@ -8,14 +8,17 @@ import ( ) var ccloudv2Handlers = map[string]func(*testing.T) http.HandlerFunc{ - "/cdx/v1/consumer-shares": handleStreamSharingConsumerShares, - "/cdx/v1/consumer-shares/{id}": handleStreamSharingConsumerShare, - "/cdx/v1/provider-shares": handleStreamSharingProviderShares, - "/cdx/v1/provider-shares/{id}": handleStreamSharingProviderShare, - "/cdx/v1/provider-shares/{id}:resend": handleStreamSharingResendInvite, - "/cdx/v1/shared-tokens:redeem": handleStreamSharingRedeemToken, - "/cmk/v2/clusters": handleCmkClusters, - "/cmk/v2/clusters/{id}": handleCmkCluster, + "/cdx/v1/consumer-shared-resources": handleConsumerSharedResources, + "/cdx/v1/consumer-shared-resources/{id}:network": handlePrivateLinkNetworkConfig, + "/cdx/v1/consumer-shares": handleStreamSharingConsumerShares, + "/cdx/v1/consumer-shares/{id}": handleStreamSharingConsumerShare, + "/cdx/v1/opt-in": handleOptInOptOut, + "/cdx/v1/provider-shares": handleStreamSharingProviderShares, + "/cdx/v1/provider-shares/{id}": handleStreamSharingProviderShare, + "/cdx/v1/provider-shares/{id}:resend": handleStreamSharingResendInvite, + "/cdx/v1/shared-tokens:redeem": handleStreamSharingRedeemToken, + "/cmk/v2/clusters": handleCmkClusters, + "/cmk/v2/clusters/{id}": handleCmkCluster, "/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/streamsharing_handler.go b/test/test-server/streamsharing_handler.go index dd1be73f21..e1714c8f3a 100644 --- a/test/test-server/streamsharing_handler.go +++ b/test/test-server/streamsharing_handler.go @@ -15,11 +15,24 @@ import ( func getTestConsumerShare() cdxv1.CdxV1ConsumerShare { expiresAt, _ := time.Parse(time.RFC3339, "2022-07-22T22:08:41+00:00") return cdxv1.CdxV1ConsumerShare{ - Id: stringToPtr("ss-12345"), - ProviderUserName: stringToPtr("provider"), - Status: stringToPtr("active"), - SharedResource: &cdxv1.ObjectReference{Id: "sr-12345"}, - InviteExpiresAt: &expiresAt, + Id: stringToPtr("ss-12345"), + ProviderUserName: stringToPtr("provider"), + ProviderOrganizationName: stringPtr("provider org"), + Status: &cdxv1.CdxV1ConsumerShareStatus{Phase: "active"}, + InviteExpiresAt: &expiresAt, + } +} + +func getTestConsumerSharedResource() cdxv1.CdxV1ConsumerSharedResource { + return cdxv1.CdxV1ConsumerSharedResource{ + Id: stringToPtr("sr-12345"), + } +} + +func getTestAWSNetwork() *cdxv1.CdxV1AwsNetwork { + return &cdxv1.CdxV1AwsNetwork{ + Kind: "AwsNetwork", + PrivateLinkEndpointService: stringToPtr("com.amazonaws.vpce.us-west-2.vpce-svc-0000000000"), } } @@ -31,11 +44,8 @@ func getTestProviderShare() cdxv1.CdxV1ProviderShare { Id: stringToPtr("ss-12345"), ConsumerUserName: stringToPtr("consumer"), ConsumerOrganizationName: stringToPtr("consumer org"), - ProviderUserName: stringToPtr("provider"), - Status: stringToPtr("active"), + Status: &cdxv1.CdxV1ProviderShareStatus{Phase: "active"}, DeliveryMethod: stringToPtr("email"), - ServiceAccount: &cdxv1.ObjectReference{Id: "sa-123456"}, - SharedResource: &cdxv1.ObjectReference{Id: "sr-12345"}, RedeemedAt: &redeemedAt, InvitedAt: &invitedAt, InviteExpiresAt: &expiresAt, @@ -123,10 +133,13 @@ func handleStreamSharingRedeemToken(t *testing.T) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") response := cdxv1.CdxV1RedeemTokenResponse{ - Id: stringPtr("ss-12345"), - Apikey: stringPtr("00000000000000000000"), - Secret: stringPtr("00000000000000000000"), - KafkaBootstrapUrl: stringPtr("pkc-00000.us-east1.gcp.confluent.cloud:9092"), + Id: stringPtr("ss-12345"), + ApiKey: stringPtr("00000000000000000000"), + Secret: stringPtr("00000000000000000000"), + KafkaBootstrapUrl: stringPtr("pkc-00000.us-east1.gcp.confluent.cloud:9092"), + SchemaRegistryUrl: stringToPtr("https://psrc-xyz123.us-west-2.aws.cpdev.cloud"), + SchemaRegistryApiKey: stringPtr("00000000000000000000"), + SchemaRegistrySecret: stringPtr("00000000000000000000"), Resources: &[]cdxv1.CdxV1RedeemTokenResponseResourcesOneOf{ { CdxV1SharedTopic: &cdxv1.CdxV1SharedTopic{ @@ -148,3 +161,55 @@ func handleStreamSharingRedeemToken(t *testing.T) http.HandlerFunc { require.NoError(t, err) } } + +// Handler for: "/cdx/v1/consumer-shared-resources" +func handleConsumerSharedResources(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + list := cdxv1.CdxV1ConsumerSharedResourceList{ + Data: []cdxv1.CdxV1ConsumerSharedResource{getTestConsumerSharedResource()}, + } + b, err := json.Marshal(&list) + require.NoError(t, err) + _, err = io.WriteString(w, string(b)) + require.NoError(t, err) + } +} + +// Handler for: "/cdx/v1/consumer-shared-resources/{id}:network" +func handlePrivateLinkNetworkConfig(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + network := cdxv1.CdxV1Network{ + DnsDomain: stringToPtr("abc123.us-west-2.aws.stag.cpdev.cloud"), + Zones: &[]string{"usw2-az1", "usw2-az3", "usw2-az2"}, + ZonalSubdomains: &map[string]string{ + "usw2-az2": "usw2-az2.abc123.us-west-2.aws.stag.cpdev.cloud", + }, + Cloud: &cdxv1.CdxV1NetworkCloudOneOf{ + CdxV1AwsNetwork: getTestAWSNetwork(), + }, + } + b, err := json.Marshal(&network) + require.NoError(t, err) + _, err = io.WriteString(w, string(b)) + require.NoError(t, err) + } +} + +// Handler for: "/cdx/v1/opt-in" +func handleOptInOptOut(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + body, _ := io.ReadAll(r.Body) + var reqBody cdxv1.CdxV1OptIn + _ = json.Unmarshal(body, &reqBody) + + network := &cdxv1.CdxV1OptIn{StreamShareEnabled: reqBody.StreamShareEnabled} + b, err := json.Marshal(&network) + require.NoError(t, err) + _, err = io.WriteString(w, string(b)) + require.NoError(t, err) + } +}