Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/data-sources/confluent_connect_artifact.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The following arguments are supported:
* `id` - (Required String) The ID of the Connect Artifact.
* `environment` - (Required Block) The Environment that the Connect Artifact belongs to, for example, `env-abc123`. It supports the following:
* `id` - (Required String) The ID of the Environment that the Connect Artifact belongs to.
* `cloud` - (Required String) Cloud provider where the Connect Artifact archive is uploaded. Accepted values are: `AWS`, `AZURE`.
* `cloud` - (Required String) Cloud provider where the Connect Artifact archive is uploaded. Accepted values are: `AWS`, `AZURE`, `GCP`.

## Attributes Reference

Expand Down
2 changes: 1 addition & 1 deletion docs/resources/confluent_connect_artifact.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ resource "confluent_connect_artifact" "example" {
The following arguments are supported:

* `display_name` - (Required String) The unique name of the Connect Artifact per cloud, environment scope.
* `cloud` - (Required String) Cloud provider where the Connect Artifact archive is uploaded. Accepted values are: `AWS`, `AZURE`.
* `cloud` - (Required String) Cloud provider where the Connect Artifact archive is uploaded. Accepted values are: `AWS`, `AZURE`, `GCP`.
* `environment` - (Required Block) The Environment that the Connect Artifact belongs to, for example, `env-abc123`. It supports the following:
* `id` - (Required String) The ID of the Environment that the Connect Artifact belongs to.
* `content_format` - (Required String) Archive format of the Connect Artifact. Supported formats are `JAR` and `ZIP`.
Expand Down
199 changes: 199 additions & 0 deletions internal/provider/resource_connect_artifact_gcp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package provider

import (
"context"
"fmt"
"github.com/walkerus/go-wiremock"
"net/http"
"os"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

const (
connectArtifactCloudGCP = "GCP"
connectArtifactCloudGCPAPIResponse = "gcp"
)

func TestAccConnectArtifactGCP(t *testing.T) {
ctx := context.Background()

wiremockContainer, err := setupWiremock(ctx)
if err != nil {
t.Fatal(err)
}
defer wiremockContainer.Terminate(ctx)

mockServerUrl := wiremockContainer.URI
wiremockClient := wiremock.NewClient(mockServerUrl)
// nolint:errcheck
defer wiremockClient.Reset()

// nolint:errcheck
defer wiremockClient.ResetAllScenarios()

createArtifactPresignedUrlResponse, _ := os.ReadFile("../testdata/connect_artifact/read_presigned_url_gcp.json")
createArtifactPresignedUrlStub := wiremock.Post(wiremock.URLPathEqualTo("/cam/v1/presigned-upload-url")).
InScenario(connectArtifactScenarioName).
WhenScenarioStateIs(wiremock.ScenarioStateStarted).
WillSetStateTo(scenarioConnectArtifactPresignedUrlHasBeenCreated).
WillReturn(
string(createArtifactPresignedUrlResponse),
contentTypeJSONHeader,
http.StatusCreated,
)
_ = wiremockClient.StubFor(createArtifactPresignedUrlStub)

createArtifactResponse, _ := os.ReadFile("../testdata/connect_artifact/create_artifact_gcp.json")
createArtifactStub := wiremock.Post(wiremock.URLPathEqualTo("/cam/v1/connect-artifacts")).
InScenario(connectArtifactScenarioName).
WhenScenarioStateIs(scenarioConnectArtifactPresignedUrlHasBeenCreated).
WillSetStateTo(scenarioStateConnectArtifactIsProvisioning).
WillReturn(
string(createArtifactResponse),
contentTypeJSONHeader,
http.StatusCreated,
)
_ = wiremockClient.StubFor(createArtifactStub)

// Add a stub for the provisioning state
provisioningArtifactResponse, _ := os.ReadFile("../testdata/connect_artifact/create_artifact_gcp.json")
_ = wiremockClient.StubFor(wiremock.Get(wiremock.URLPathEqualTo(connectArtifactsUrlPath)).
InScenario(connectArtifactScenarioName).
WithQueryParam("spec.cloud", wiremock.Matching("(?i)^"+regexp.QuoteMeta(connectArtifactCloudGCPAPIResponse)+"$")).
WithQueryParam("environment", wiremock.EqualTo(connectArtifactEnvironmentId)).
WhenScenarioStateIs(scenarioStateConnectArtifactIsProvisioning).
WillSetStateTo(scenarioStateConnectArtifactHasBeenCreated).
WillReturn(
string(provisioningArtifactResponse),
contentTypeJSONHeader,
http.StatusOK,
))

readCreatedArtifactResponse, _ := os.ReadFile("../testdata/connect_artifact/read_created_artifact_gcp.json")
_ = wiremockClient.StubFor(wiremock.Get(wiremock.URLPathEqualTo(connectArtifactsUrlPath)).
InScenario(connectArtifactScenarioName).
WithQueryParam("spec.cloud", wiremock.Matching("(?i)^"+regexp.QuoteMeta(connectArtifactCloudGCP)+"$")).
WithQueryParam("environment", wiremock.EqualTo(connectArtifactEnvironmentId)).
WhenScenarioStateIs(scenarioStateConnectArtifactHasBeenCreated).
WillReturn(
string(readCreatedArtifactResponse),
contentTypeJSONHeader,
http.StatusOK,
))

deleteArtifactStub := wiremock.Delete(wiremock.URLPathEqualTo(connectArtifactsUrlPath)).
InScenario(connectArtifactScenarioName).
WithQueryParam("spec.cloud", wiremock.Matching("(?i)^"+regexp.QuoteMeta(connectArtifactCloudGCP)+"$")).
WhenScenarioStateIs(scenarioStateConnectArtifactHasBeenCreated).
WillSetStateTo(scenarioStateConnectArtifactHasBeenDeleted).
WillReturn(
"",
contentTypeJSONHeader,
http.StatusNoContent,
)
_ = wiremockClient.StubFor(deleteArtifactStub)

readDeletedArtifactResponse, _ := os.ReadFile("../testdata/connect_artifact/read_deleted_artifact.json")
_ = wiremockClient.StubFor(wiremock.Get(wiremock.URLPathEqualTo(connectArtifactsUrlPath)).
InScenario(connectArtifactScenarioName).
WithQueryParam("spec.cloud", wiremock.Matching("(?i)^"+regexp.QuoteMeta(connectArtifactCloudGCP)+"$")).
WithQueryParam("environment", wiremock.EqualTo(connectArtifactEnvironmentId)).
WhenScenarioStateIs(scenarioStateConnectArtifactHasBeenDeleted).
WillReturn(
string(readDeletedArtifactResponse),
contentTypeJSONHeader,
http.StatusNotFound,
))

connectArtifactResourceLabel := "test_gcp"
fullConnectArtifactResourceLabel := fmt.Sprintf("confluent_connect_artifact.%s", connectArtifactResourceLabel)

_ = os.Setenv("IMPORT_ARTIFACT_FILENAME", "abc.jar")
defer func() {
_ = os.Unsetenv("IMPORT_ARTIFACT_FILENAME")
}()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckConnectArtifactDestroyGCP,
Steps: []resource.TestStep{
{
Config: testAccCheckConnectArtifactGCPConfig(mockServerUrl, connectArtifactResourceLabel),
Check: resource.ComposeTestCheckFunc(
testAccCheckConnectArtifactExists(fullConnectArtifactResourceLabel),
resource.TestCheckResourceAttr(fullConnectArtifactResourceLabel, paramId, connectArtifactId),
resource.TestCheckResourceAttr(fullConnectArtifactResourceLabel, paramDisplayName, connectArtifactUniqueName),
resource.TestCheckResourceAttr(fullConnectArtifactResourceLabel, paramCloud, connectArtifactCloudGCPAPIResponse),
resource.TestCheckResourceAttr(fullConnectArtifactResourceLabel, fmt.Sprintf("%s.#", paramEnvironment), "1"),
resource.TestCheckResourceAttr(fullConnectArtifactResourceLabel, fmt.Sprintf("%s.0.%s", paramEnvironment, paramId), connectArtifactEnvironmentId),
resource.TestCheckResourceAttr(fullConnectArtifactResourceLabel, paramArtifactFile, "abc.jar"),
resource.TestCheckResourceAttr(fullConnectArtifactResourceLabel, paramContentFormat, connectArtifactContentFormat),
resource.TestCheckResourceAttr(fullConnectArtifactResourceLabel, paramDescription, connectArtifactDescription),
),
},
{
ResourceName: fullConnectArtifactResourceLabel,
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: func(state *terraform.State) (string, error) {
resources := state.RootModule().Resources
connectArtifactId := resources[fullConnectArtifactResourceLabel].Primary.ID
cloud := resources[fullConnectArtifactResourceLabel].Primary.Attributes["cloud"]
environment := resources[fullConnectArtifactResourceLabel].Primary.Attributes["environment.0.id"]
return environment + "/" + cloud + "/" + connectArtifactId, nil
},
},
},
})

checkStubCount(t, wiremockClient, createArtifactStub, fmt.Sprintf("POST %s", "/cam/v1/connect-artifacts"), expectedCountOne)
checkStubCount(t, wiremockClient, deleteArtifactStub, fmt.Sprintf("DELETE %s", connectArtifactsUrlPath), expectedCountOne)
}

func testAccCheckConnectArtifactDestroyGCP(s *terraform.State) error {
c := testAccProvider.Meta().(*Client)
// Loop through the resources in state, verifying each connect artifact is destroyed
for _, rs := range s.RootModule().Resources {
if rs.Type != "confluent_connect_artifact" {
continue
}
deletedArtifactId := rs.Primary.ID
req := c.camClient.ConnectArtifactsCamV1Api.GetCamV1ConnectArtifact(c.camApiContext(context.Background()), deletedArtifactId).
SpecCloud(connectArtifactCloudGCP).
Environment(connectArtifactEnvironmentId)
deletedArtifact, response, err := req.Execute()
if response != nil && response.StatusCode == http.StatusNotFound {
return nil
} else if err == nil && deletedArtifact.GetId() != "" {
// Otherwise return the error
if deletedArtifact.GetId() == rs.Primary.ID {
return fmt.Errorf("connect artifact (%s) still exists", rs.Primary.ID)
}
}
return err
}
return nil
}

func testAccCheckConnectArtifactGCPConfig(mockServerUrl, resourceLabel string) string {
return fmt.Sprintf(`
provider "confluent" {
endpoint = "%s"
}
resource "confluent_connect_artifact" "%s" {
display_name = "%s"
cloud = "%s"
artifact_file = "abc.jar"
content_format = "%s"
description = "%s"
environment {
id = "%s"
}
}
`, mockServerUrl, resourceLabel, connectArtifactUniqueName, connectArtifactCloudGCP, connectArtifactContentFormat, connectArtifactDescription, connectArtifactEnvironmentId)
}
61 changes: 61 additions & 0 deletions internal/provider/resource_connect_artifact_live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,67 @@ func TestAccConnectArtifactAzureLive(t *testing.T) {
})
}

func TestAccConnectArtifactGCPLive(t *testing.T) {
// Enable parallel execution for I/O bound operations
t.Parallel()

// Skip this test unless explicitly enabled
if os.Getenv("TF_ACC_PROD") == "" {
t.Skip("Skipping live test. Set TF_ACC_PROD=1 to run this test.")
}

// Read credentials and configuration from environment variables (populated by Vault)
apiKey := os.Getenv("CONFLUENT_CLOUD_API_KEY")
apiSecret := os.Getenv("CONFLUENT_CLOUD_API_SECRET")
endpoint := os.Getenv("CONFLUENT_CLOUD_ENDPOINT")
if endpoint == "" {
endpoint = "https://api.confluent.cloud" // Use default endpoint if not set
}

// Validate required environment variables are present
if apiKey == "" || apiSecret == "" {
t.Fatal("CONFLUENT_CLOUD_API_KEY and CONFLUENT_CLOUD_API_SECRET must be set for live tests")
}

// Generate unique names for test resources to avoid conflicts
randomSuffix := rand.Intn(100000)
artifactDisplayName := fmt.Sprintf("tf-live-artifact-gcp-%d", randomSuffix)
artifactResourceLabel := "test_live_connect_artifact_gcp"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckConnectArtifactLiveDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckConnectArtifactLiveConfig(endpoint, artifactResourceLabel, artifactDisplayName, "GCP", apiKey, apiSecret),
Check: resource.ComposeTestCheckFunc(
testAccCheckConnectArtifactLiveExists(fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel)),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel), "display_name", artifactDisplayName),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel), "cloud", "gcp"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel), "content_format", "JAR"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel), "description", "A test connect artifact for live testing"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel), "environment.0.id", "env-zyg27z"),
resource.TestCheckResourceAttrSet(fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel), "id"),
),
},
{
ResourceName: fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel),
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"artifact_file"},
ImportStateIdFunc: func(state *terraform.State) (string, error) {
resources := state.RootModule().Resources
artifactId := resources[fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel)].Primary.ID
environmentId := resources[fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel)].Primary.Attributes["environment.0.id"]
cloud := resources[fmt.Sprintf("confluent_connect_artifact.%s", artifactResourceLabel)].Primary.Attributes["cloud"]
return environmentId + "/" + cloud + "/" + artifactId, nil
},
},
},
})
}

func testAccCheckConnectArtifactLiveDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "confluent_connect_artifact" {
Expand Down
23 changes: 23 additions & 0 deletions internal/testdata/connect_artifact/create_artifact_gcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"api_version": "cam/v1",
"id": "cca-xryjrg",
"kind": "ConnectArtifact",
"metadata": {
"created_at": "2025-08-14T21:58:48.97731Z",
"resource_name": "crn://confluent.cloud/organization=1111aaaa-11aa-11aa-11aa-111111aaaaaa/environment=env-gz903/connect-artifact=cca-xryjrg",
"self": "https://confluent.cloud/cam/v1/connect-artifacts/cca-xryjrg",
"updated_at": "2025-08-14T21:58:48.97731Z"
},
"spec": {
"cloud": "gcp",
"content_format": "JAR",
"description": "test-description",
"display_name": "connect_artifact_0",
"environment": "env-gz903",
"plugins": null,
"usages": []
},
"status": {
"phase": "PROCESSING"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"api_version": "cam/v1",
"id": "cca-xryjrg",
"kind": "ConnectArtifact",
"metadata": {
"created_at": "2025-08-14T21:58:48.97731Z",
"resource_name": "crn://confluent.cloud/organization=1111aaaa-11aa-11aa-11aa-111111aaaaaa/environment=env-gz903/connect-artifact=cca-xryjrg",
"self": "https://confluent.cloud/cam/v1/connect-artifacts/cca-xryjrg",
"updated_at": "2025-08-14T21:58:48.97731Z"
},
"spec": {
"cloud": "gcp",
"content_format": "JAR",
"description": "test-description",
"display_name": "connect_artifact_0",
"environment": "env-gz903",
"plugins": [],
"usages": []
},
"status": {
"phase": "READY"
}
}
12 changes: 12 additions & 0 deletions internal/testdata/connect_artifact/read_presigned_url_gcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"api_version": "cam/v1",
"cloud": "GCP",
"content_format": "JAR",
"kind": "PresignedUrl",
"upload_form_data": {
"bucket": "confluent-custom-connectors-stag-us-central1",
"object_path": "staging/ccp/v1/2f37f0b6-f8da-4e8b-bc5f-282ebb0511be/connect-e53bb2e8-8de3-49fa-9fb1-4e3fd9a16b66/plugin.jar"
},
"upload_id": "e53bb2e8-8de3-49fa-9fb1-4e3fd9a16b66",
"upload_url": "TF_TEST_URL"
}