diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ae0b109..da25990f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Notes + +- The `private_endpoint_connection` resource can now be used to create private + endpoint connections on every supported cloud-provider and cluster type, + except Serverless clusters on Azure as that configuration is not yet + available. + ### Fixed - Renamed example files to the correct name so they are automatically included diff --git a/docs/resources/private_endpoint_connection.md b/docs/resources/private_endpoint_connection.md index 5527757f..7238d571 100644 --- a/docs/resources/private_endpoint_connection.md +++ b/docs/resources/private_endpoint_connection.md @@ -3,12 +3,12 @@ page_title: "cockroach_private_endpoint_connection Resource - terraform-provider-cockroach" subcategory: "" description: |- - AWS PrivateLink Endpoint Connection. + Private Endpoint Connection. --- # cockroach_private_endpoint_connection (Resource) -AWS PrivateLink Endpoint Connection. +Private Endpoint Connection. @@ -18,13 +18,13 @@ AWS PrivateLink Endpoint Connection. ### Required - `cluster_id` (String) -- `endpoint_id` (String) Client side ID of the PrivateLink connection. +- `endpoint_id` (String) Client side ID of the Private Endpoint Connection. ### Read-Only - `cloud_provider` (String) Cloud provider associated with this connection. - `id` (String) Used with `terraform import`. Format is ":". - `region_name` (String) Cloud provider region code associated with this connection. -- `service_id` (String) Server side ID of the PrivateLink connection. +- `service_id` (String) Server side ID of the Private Endpoint Connection. diff --git a/examples/resources/cockroach_aws_private_endpoint_connection/resource.tf b/examples/resources/cockroach_aws_private_endpoint_connection/resource.tf index 04b182da..7d177b2e 100644 --- a/examples/resources/cockroach_aws_private_endpoint_connection/resource.tf +++ b/examples/resources/cockroach_aws_private_endpoint_connection/resource.tf @@ -1,10 +1,9 @@ variable "cluster_id" { - type = string + type = string + description = "the id for the CockroachDB Cloud cluster" } resource "cockroach_private_endpoint_connection" "cockroach" { cluster_id = var.cluster_id - endpoint_id = "endpoint id assigned by consumer AWS" - cloud_provider = "AWS" - region_name = "AWS region in which the endpoint was created" + endpoint_id = "the endpoint id assigned by cloud provider to the client-side of the connection" } diff --git a/internal/provider/private_endpoint_connection_resource.go b/internal/provider/private_endpoint_connection_resource.go index 630bcc7f..f134bfd1 100644 --- a/internal/provider/private_endpoint_connection_resource.go +++ b/internal/provider/private_endpoint_connection_resource.go @@ -48,7 +48,7 @@ func (r *privateEndpointConnectionResource) Schema( _ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse, ) { resp.Schema = schema.Schema{ - MarkdownDescription: "AWS PrivateLink Endpoint Connection.", + MarkdownDescription: "Private Endpoint Connection.", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, @@ -76,14 +76,14 @@ func (r *privateEndpointConnectionResource) Schema( PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, - Description: "Client side ID of the PrivateLink connection.", + Description: "Client side ID of the Private Endpoint Connection.", }, "service_id": schema.StringAttribute{ Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, - Description: "Server side ID of the PrivateLink connection.", + Description: "Server side ID of the Private Endpoint Connection.", }, "cluster_id": schema.StringAttribute{ Required: true, @@ -129,7 +129,8 @@ func (r *privateEndpointConnectionResource) Create( return } - cluster, _, err := r.provider.service.GetCluster(ctx, plan.ClusterID.ValueString()) + svc := r.provider.service + cluster, _, err := svc.GetCluster(ctx, plan.ClusterID.ValueString()) if err != nil { resp.Diagnostics.AddError( "Error getting cluster", @@ -138,30 +139,22 @@ func (r *privateEndpointConnectionResource) Create( return } - if cluster.CloudProvider != client.CLOUDPROVIDERTYPE_AWS { - resp.Diagnostics.AddError( - "Incompatible cluster cloud provider", - "Private endpoint services are only available for AWS clusters", - ) - return - } - - connectionStateRequest := client.SetAwsEndpointConnectionStateRequest{ - Status: client.SETAWSENDPOINTCONNECTIONSTATUSTYPE_AVAILABLE, + addRequest := client.AddPrivateEndpointConnectionRequest{ + EndpointId: plan.EndpointID.ValueString(), } - _, _, err = r.provider.service.SetAwsEndpointConnectionState(ctx, plan.ClusterID.ValueString(), plan.EndpointID.ValueString(), &connectionStateRequest) + _, _, err = svc.AddPrivateEndpointConnection(ctx, cluster.Id, &addRequest) if err != nil { resp.Diagnostics.AddError( - "Error establishing AWS Endpoint Connection", - fmt.Sprintf("Could not establish AWS Endpoint Connection: %s", formatAPIErrorMessage(err)), + "Error establishing Private Endpoint Connection", + fmt.Sprintf("Could not establish Private Endpoint Connection: %s", formatAPIErrorMessage(err)), ) return } - var connection client.AwsEndpointConnection + var connection client.PrivateEndpointConnection err = sdk_resource.RetryContext(ctx, endpointConnectionCreateTimeout, - waitForEndpointConnectionCreatedFunc(ctx, cluster.Id, plan.EndpointID.ValueString(), r.provider.service, &connection)) + waitForEndpointConnectionCreatedFunc(ctx, cluster.Id, plan.EndpointID.ValueString(), svc, &connection)) if err != nil { resp.Diagnostics.AddError( "Error accepting private endpoint connection", @@ -192,7 +185,7 @@ func (r *privateEndpointConnectionResource) Read( return } - connections, _, err := r.provider.service.ListAwsEndpointConnections(ctx, state.ClusterID.ValueString()) + connections, _, err := r.provider.service.ListPrivateEndpointConnections(ctx, state.ClusterID.ValueString()) if err != nil { diags.AddError("Unable to get endpoint connection status", fmt.Sprintf("Unexpected error retrieving endpoint status: %s", formatAPIErrorMessage(err))) @@ -212,14 +205,14 @@ func (r *privateEndpointConnectionResource) Read( } func loadEndpointConnectionIntoTerraformState( - apiConnection *client.AwsEndpointConnection, state *PrivateEndpointConnection, + apiConnection *client.PrivateEndpointConnection, state *PrivateEndpointConnection, ) { state.EndpointID = types.StringValue(apiConnection.GetEndpointId()) state.ID = types.StringValue(fmt.Sprintf( privateEndpointConnectionIDFmt, state.ClusterID.ValueString(), apiConnection.GetEndpointId())) - state.ServiceID = types.StringValue(apiConnection.GetServiceId()) + state.ServiceID = types.StringValue(apiConnection.GetEndpointServiceId()) state.CloudProvider = types.StringValue(string(apiConnection.GetCloudProvider())) state.RegionName = types.StringValue(apiConnection.GetRegionName()) } @@ -240,13 +233,11 @@ func (r *privateEndpointConnectionResource) Delete( return } - _, httpResp, err := r.provider.service.SetAwsEndpointConnectionState( + httpResp, err := r.provider.service.DeletePrivateEndpointConnection( ctx, state.ClusterID.ValueString(), state.EndpointID.ValueString(), - &client.SetAwsEndpointConnectionStateRequest{ - Status: client.SETAWSENDPOINTCONNECTIONSTATUSTYPE_REJECTED, - }) + ) if err != nil && httpResp != nil && httpResp.StatusCode != http.StatusNotFound { diags.AddError("Couldn't delete connection", fmt.Sprintf("Unexpected error occurred while setting connection status: %s", formatAPIErrorMessage(err))) @@ -284,10 +275,10 @@ func waitForEndpointConnectionCreatedFunc( ctx context.Context, clusterID, endpointID string, cl client.Service, - connection *client.AwsEndpointConnection, + connection *client.PrivateEndpointConnection, ) sdk_resource.RetryFunc { return func() *sdk_resource.RetryError { - connections, httpResp, err := cl.ListAwsEndpointConnections(ctx, clusterID) + connections, httpResp, err := cl.ListPrivateEndpointConnections(ctx, clusterID) if err != nil { if httpResp != nil && httpResp.StatusCode < http.StatusInternalServerError { return sdk_resource.NonRetryableError(fmt.Errorf("error getting endpoint connections: %s", formatAPIErrorMessage(err))) @@ -299,10 +290,16 @@ func waitForEndpointConnectionCreatedFunc( for _, *connection = range connections.GetConnections() { if connection.GetEndpointId() == endpointID { switch status := connection.GetStatus(); status { - case client.AWSENDPOINTCONNECTIONSTATUSTYPE_AVAILABLE: + case client.PRIVATEENDPOINTCONNECTIONSTATUS_AVAILABLE: return nil - case client.AWSENDPOINTCONNECTIONSTATUSTYPE_PENDING, - client.AWSENDPOINTCONNECTIONSTATUSTYPE_PENDING_ACCEPTANCE: + case client.PRIVATEENDPOINTCONNECTIONSTATUS_PENDING, + client.PRIVATEENDPOINTCONNECTIONSTATUS_PENDING_ACCEPTANCE, + client.PRIVATEENDPOINTCONNECTIONSTATUS_REJECTED: + // Note: A REJECTED state means the user previously called + // DeletePrivateEndpointConnection() on an existing + // connection. A user can re-attach a rejected connection + // by calling AddPrivateEndpointConnection() with the same + // endpointId. return sdk_resource.RetryableError(fmt.Errorf("endpoint connection is not ready yet")) default: return sdk_resource.NonRetryableError(fmt.Errorf("endpoint connection failed with state: %s", status)) diff --git a/internal/provider/private_endpoint_connection_resource_test.go b/internal/provider/private_endpoint_connection_resource_test.go index 2e836688..66d978ba 100644 --- a/internal/provider/private_endpoint_connection_resource_test.go +++ b/internal/provider/private_endpoint_connection_resource_test.go @@ -38,17 +38,18 @@ func TestAccDedicatedPrivateEndpointConnectionResource(t *testing.T) { } func TestAccServerlessPrivateEndpointConnectionResource(t *testing.T) { - t.Skip("Skipping until we can either integrate the AWS provider " + - "or import a permanent test fixture.") + // This test relies on a pre-created private endpoint created via the AWS + // console. The endpoint id is vpce-0011573c1fa6afa3d and lives in the + // staging AWS serverless cluster account. t.Parallel() clusterName := fmt.Sprintf("aws-connection-%s", GenerateRandomString(5)) testPrivateEndpointConnectionResource(t, clusterName, false /* useMock */, true /* isServerless */) } func TestIntegrationPrivateEndpointConnectionResource(t *testing.T) { - clusterName := fmt.Sprintf("aws-connection-%s", GenerateRandomString(5)) + clusterName := fmt.Sprintf("private-connection-%s", GenerateRandomString(5)) clusterID := uuid.Nil.String() - endpointID := "endpoint-id" + endpointID := "vpce-0011573c1fa6afa3d" if os.Getenv(CockroachAPIKey) == "" { os.Setenv(CockroachAPIKey, "fake") } @@ -70,15 +71,15 @@ func TestIntegrationPrivateEndpointConnectionResource(t *testing.T) { }, }, } - connection := client.AwsEndpointConnection{ - RegionName: "us-east-1", - CloudProvider: "AWS", - Status: client.AWSENDPOINTCONNECTIONSTATUSTYPE_AVAILABLE, - EndpointId: endpointID, - ServiceId: "service-id", + connection := client.PrivateEndpointConnection{ + RegionName: &services.Services[0].RegionName, + CloudProvider: "AWS", + Status: client.PRIVATEENDPOINTCONNECTIONSTATUS_AVAILABLE, + EndpointId: endpointID, + EndpointServiceId: "service-id", } - connections := &client.AwsEndpointConnections{ - Connections: []client.AwsEndpointConnection{connection}, + connections := &client.PrivateEndpointConnections{ + Connections: []client.PrivateEndpointConnection{connection}, } zeroSpendLimit := int32(0) @@ -151,27 +152,19 @@ func TestIntegrationPrivateEndpointConnectionResource(t *testing.T) { s.EXPECT().ListPrivateEndpointServices(gomock.Any(), clusterID). Return(services, nil, nil). Times(2) - available := client.SETAWSENDPOINTCONNECTIONSTATUSTYPE_AVAILABLE - s.EXPECT().SetAwsEndpointConnectionState( + s.EXPECT().AddPrivateEndpointConnection( gomock.Any(), clusterID, - endpointID, - &client.SetAwsEndpointConnectionStateRequest{ - Status: available, - }). + &client.AddPrivateEndpointConnectionRequest{EndpointId: endpointID}). Return(&connection, nil, nil) - s.EXPECT().ListAwsEndpointConnections(gomock.Any(), clusterID). + s.EXPECT().ListPrivateEndpointConnections(gomock.Any(), clusterID). Return(connections, nil, nil). Times(3) - rejected := client.SETAWSENDPOINTCONNECTIONSTATUSTYPE_REJECTED - s.EXPECT().SetAwsEndpointConnectionState( + s.EXPECT().DeletePrivateEndpointConnection( gomock.Any(), clusterID, - endpointID, - &client.SetAwsEndpointConnectionStateRequest{ - Status: rejected, - }). - Return(&connection, nil, nil) + endpointID). + Return(nil, nil) s.EXPECT().DeleteCluster(gomock.Any(), clusterID) testPrivateEndpointConnectionResource( @@ -202,11 +195,11 @@ func testPrivateEndpointConnectionResource( { Config: privateEndpointConnectionResourceConfigFn(clusterName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "endpoint_id", "endpoint-id"), - resource.TestCheckResourceAttr(resourceName, "service_id", "service-id"), - resource.TestCheckResourceAttr(resourceName, "cluster_id", uuid.Nil.String()), + resource.TestCheckResourceAttr(resourceName, "endpoint_id", "vpce-0011573c1fa6afa3d"), resource.TestCheckResourceAttr(resourceName, "region_name", "us-east-1"), resource.TestCheckResourceAttr(resourceName, "cloud_provider", "AWS"), + resource.TestCheckResourceAttrSet(resourceName, "service_id"), + resource.TestCheckResourceAttrSet(resourceName, "cluster_id"), ), }, { @@ -238,7 +231,7 @@ resource "cockroach_private_endpoint_services" "services" { resource "cockroach_private_endpoint_connection" "connection" { cluster_id = cockroach_cluster.dedicated.id - endpoint_id = "endpoint-id" + endpoint_id = "vpce-0011573c1fa6afa3d" } `, clusterName) } @@ -261,7 +254,7 @@ resource "cockroach_private_endpoint_services" "services" { resource "cockroach_private_endpoint_connection" "connection" { cluster_id = cockroach_cluster.serverless.id - endpoint_id = "endpoint-id" + endpoint_id = "vpce-0011573c1fa6afa3d" } `, clusterName) }