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
23 changes: 23 additions & 0 deletions docs/resources/confluent_api_key.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,29 @@ resource "confluent_api_key" "env-manager-tableflow-api-key" {
}
```

### Example Global API Key
```terraform
resource "confluent_api_key" "env-manager-global-api-key" {
display_name = "env-manager-global-api-key"
description = "Global API Key that is owned by 'env-manager' service account"
owner {
id = confluent_service_account.env-manager.id
api_version = confluent_service_account.env-manager.api_version
kind = confluent_service_account.env-manager.kind
}

managed_resource {
id = "global"
api_version = "global/v1"
kind = "Global"
}

lifecycle {
prevent_destroy = true
}
}
```

### Example Cloud API Key
```terraform
resource "confluent_api_key" "env-manager-cloud-api-key" {
Expand Down
18 changes: 16 additions & 2 deletions internal/provider/resource_api_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const (
cloudKindInLowercase = "cloud"
tableflowKind = "Tableflow"
tableflowKindInLowercase = "tableflow"
globalKind = "Global"
globalKindInLowercase = "global"

iamApiVersion = "iam/v2"
cmkApiVersion = "cmk/v2"
Expand All @@ -53,13 +55,14 @@ const (
ksqldbcmApiVersion = "ksqldbcm/v2"
fcpmApiVersion = "fcpm/v2"
tableflowApiVersion = "tableflow/v1"
globalApiVersion = "global/v1"
)

var acceptedOwnerKinds = []string{serviceAccountKind, userKind}
var acceptedResourceKinds = []string{clusterKind, regionKind, tableflowKind}
var acceptedResourceKinds = []string{clusterKind, regionKind, tableflowKind, globalKind}

var acceptedOwnerApiVersions = []string{iamApiVersion}
var acceptedResourceApiVersions = []string{cmkApiVersion, srcmV2ApiVersion, srcmV3ApiVersion, ksqldbcmApiVersion, fcpmApiVersion, tableflowApiVersion}
var acceptedResourceApiVersions = []string{cmkApiVersion, srcmV2ApiVersion, srcmV3ApiVersion, ksqldbcmApiVersion, fcpmApiVersion, tableflowApiVersion, globalApiVersion}

func apiKeyResource() *schema.Resource {
return &schema.Resource{
Expand Down Expand Up @@ -140,6 +143,9 @@ func apiKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{})
if apiVersion == tableflowApiVersion {
spec.Resource.SetApiVersion(tableflowApiVersion)
}
if apiVersion == globalApiVersion {
spec.Resource.SetApiVersion(globalApiVersion)
}

if isFlinkApiKey(apikeys.IamV2ApiKey{Spec: spec}) {
spec.Resource.SetId(resourceId)
Expand Down Expand Up @@ -508,6 +514,10 @@ func isTableflowApiKey(apiKey apikeys.IamV2ApiKey) bool {
return apiKey.Spec.Resource.GetKind() == tableflowKind && apiKey.Spec.Resource.GetId() == tableflowKindInLowercase
}

func isGlobalApiKey(apiKey apikeys.IamV2ApiKey) bool {

Choose a reason for hiding this comment

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

Is the backend service suppose to return the lower case global as the Id?

Choose a reason for hiding this comment

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

yes, it will return lower case global.

return apiKey.Spec.Resource.GetKind() == globalKind && apiKey.Spec.Resource.GetId() == globalKindInLowercase
}

func waitForApiKeyToSync(ctx context.Context, c *Client, createdApiKey apikeys.IamV2ApiKey, isResourceSpecificApiKey bool, environmentId string) error {
// For Kafka API Key use Kafka REST API's List Topics request and wait for http.StatusOK
// For Cloud API Key use Org API's List Environments request and wait for http.StatusOK
Expand Down Expand Up @@ -536,6 +546,10 @@ func waitForApiKeyToSync(ctx context.Context, c *Client, createdApiKey apikeys.I
if err := waitForCreatedTableflowApiKeyToSync(ctx, tableflowRestClient, c.isAcceptanceTestMode); err != nil {
return fmt.Errorf("error waiting for Tableflow API Key %q to sync: %s", createdApiKey.GetId(), createDescriptiveError(err))
}
} else if isGlobalApiKey(createdApiKey) {

Choose a reason for hiding this comment

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

Is the global API key supposed to be resource specific? Or Global is considered as a special resource?

Choose a reason for hiding this comment

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

Yes we use global as the associated resource id for global API key.

if err := waitForCreatedGlobalApiKeyToSync(ctx, c, createdApiKey.GetId(), createdApiKey.Spec.GetSecret()); err != nil {
return fmt.Errorf("error waiting for Global API Key %q to sync: %s", createdApiKey.GetId(), createDescriptiveError(err))
}
} else {
resourceJson, err := json.Marshal(createdApiKey.Spec.GetResource())
if err != nil {
Expand Down
185 changes: 185 additions & 0 deletions internal/provider/resource_api_key_live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,191 @@ func testAccCheckApiKeyUpdateLiveConfig(endpoint, serviceAccountResourceLabel, s
`, endpoint, apiKey, apiSecret, serviceAccountResourceLabel, serviceAccountDisplayName, apiKeyResourceLabel, apiKeyDisplayName, serviceAccountResourceLabel, serviceAccountResourceLabel, serviceAccountResourceLabel)
}

func TestAccGlobalApiKeyLive(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)
serviceAccountDisplayName := fmt.Sprintf("tf-live-sa-global-%d", randomSuffix)
apiKeyDisplayName := fmt.Sprintf("tf-live-global-api-key-%d", randomSuffix)
serviceAccountResourceLabel := "test_live_service_account_global"
apiKeyResourceLabel := "test_live_global_api_key"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckApiKeyLiveDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckGlobalApiKeyLiveConfig(endpoint, serviceAccountResourceLabel, serviceAccountDisplayName, apiKeyResourceLabel, apiKeyDisplayName, apiKey, apiSecret),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceAccountLiveExists(fmt.Sprintf("confluent_service_account.%s", serviceAccountResourceLabel)),
testAccCheckApiKeyLiveExists(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel)),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "display_name", apiKeyDisplayName),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "description", "Test global API key for live testing"),
resource.TestCheckResourceAttrSet(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "id"),
resource.TestCheckResourceAttrSet(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "secret"),
// Check that owner is set correctly
resource.TestCheckResourceAttrSet(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "owner.0.id"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "owner.0.api_version", "iam/v2"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "owner.0.kind", "ServiceAccount"),
// Check that managed_resource is set correctly for Global API Key
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "managed_resource.0.api_version", "global/v1"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "managed_resource.0.id", "global"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "managed_resource.0.kind", "Global"),
),
},
},
})
}

func TestAccGlobalApiKeyUpdateLive(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)
serviceAccountDisplayName := fmt.Sprintf("tf-live-sa-global-update-%d", randomSuffix)
apiKeyDisplayName := fmt.Sprintf("tf-live-global-api-key-update-%d", randomSuffix)
serviceAccountResourceLabel := "test_live_service_account_global_update"
apiKeyResourceLabel := "test_live_global_api_key_update"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckApiKeyLiveDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckGlobalApiKeyLiveConfig(endpoint, serviceAccountResourceLabel, serviceAccountDisplayName, apiKeyResourceLabel, apiKeyDisplayName, apiKey, apiSecret),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceAccountLiveExists(fmt.Sprintf("confluent_service_account.%s", serviceAccountResourceLabel)),
testAccCheckApiKeyLiveExists(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel)),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "display_name", apiKeyDisplayName),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "description", "Test global API key for live testing"),
// Check that managed_resource is set correctly for Global API Key
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "managed_resource.0.api_version", "global/v1"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "managed_resource.0.id", "global"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "managed_resource.0.kind", "Global"),
),
},
{
Config: testAccCheckGlobalApiKeyUpdateLiveConfig(endpoint, serviceAccountResourceLabel, serviceAccountDisplayName, apiKeyResourceLabel, apiKeyDisplayName+"-updated", apiKey, apiSecret),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceAccountLiveExists(fmt.Sprintf("confluent_service_account.%s", serviceAccountResourceLabel)),
testAccCheckApiKeyLiveExists(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel)),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "display_name", apiKeyDisplayName+"-updated"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "description", "Updated test global API key for live testing"),
// Check that managed_resource is unchanged after update
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "managed_resource.0.api_version", "global/v1"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "managed_resource.0.id", "global"),
resource.TestCheckResourceAttr(fmt.Sprintf("confluent_api_key.%s", apiKeyResourceLabel), "managed_resource.0.kind", "Global"),
),
},
},
})
}

func testAccCheckGlobalApiKeyLiveConfig(endpoint, serviceAccountResourceLabel, serviceAccountDisplayName, apiKeyResourceLabel, apiKeyDisplayName, apiKey, apiSecret string) string {
return fmt.Sprintf(`
provider "confluent" {
endpoint = "%s"
cloud_api_key = "%s"
cloud_api_secret = "%s"
}

resource "confluent_service_account" "%s" {
display_name = "%s"
description = "Test service account for global API key live testing"
}

resource "confluent_api_key" "%s" {
display_name = "%s"
description = "Test global API key for live testing"

owner {
id = confluent_service_account.%s.id
api_version = confluent_service_account.%s.api_version
kind = confluent_service_account.%s.kind
}

managed_resource {
api_version = "global/v1"
id = "global"
kind = "Global"
}
}
`, endpoint, apiKey, apiSecret, serviceAccountResourceLabel, serviceAccountDisplayName, apiKeyResourceLabel, apiKeyDisplayName, serviceAccountResourceLabel, serviceAccountResourceLabel, serviceAccountResourceLabel)
}

func testAccCheckGlobalApiKeyUpdateLiveConfig(endpoint, serviceAccountResourceLabel, serviceAccountDisplayName, apiKeyResourceLabel, apiKeyDisplayName, apiKey, apiSecret string) string {
return fmt.Sprintf(`
provider "confluent" {
endpoint = "%s"
cloud_api_key = "%s"
cloud_api_secret = "%s"
}

resource "confluent_service_account" "%s" {
display_name = "%s"
description = "Test service account for global API key live testing"
}

resource "confluent_api_key" "%s" {
display_name = "%s"
description = "Updated test global API key for live testing"

owner {
id = confluent_service_account.%s.id
api_version = confluent_service_account.%s.api_version
kind = confluent_service_account.%s.kind
}

managed_resource {
api_version = "global/v1"
id = "global"
kind = "Global"
}
}
`, endpoint, apiKey, apiSecret, serviceAccountResourceLabel, serviceAccountDisplayName, apiKeyResourceLabel, apiKeyDisplayName, serviceAccountResourceLabel, serviceAccountResourceLabel, serviceAccountResourceLabel)
}

func testAccCheckApiKeyLiveExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
Expand Down
Loading