diff --git a/.changelog/2647.txt b/.changelog/2647.txt new file mode 100644 index 0000000000..43d980789d --- /dev/null +++ b/.changelog/2647.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/cloudflare_access_service_token: add support for managing `Duration` +``` diff --git a/internal/sdkv2provider/resource_cloudflare_access_service_tokens.go b/internal/sdkv2provider/resource_cloudflare_access_service_tokens.go index 04c926f20c..f4b3bb22b4 100644 --- a/internal/sdkv2provider/resource_cloudflare_access_service_tokens.go +++ b/internal/sdkv2provider/resource_cloudflare_access_service_tokens.go @@ -79,6 +79,7 @@ func resourceCloudflareAccessServiceTokenRead(ctx context.Context, d *schema.Res d.Set("name", token.Name) d.Set("client_id", token.ClientID) d.Set("expires_at", token.ExpiresAt.Format(time.RFC3339)) + d.Set("duration", token.Duration) } } @@ -93,7 +94,12 @@ func resourceCloudflareAccessServiceTokenCreate(ctx context.Context, d *schema.R return diag.FromErr(err) } - serviceToken, err := client.CreateAccessServiceToken(ctx, identifier, cloudflare.CreateAccessServiceTokenParams{Name: d.Get("name").(string)}) + params := cloudflare.CreateAccessServiceTokenParams{Name: d.Get("name").(string)} + if value, ok := d.GetOk("duration"); ok { + params.Duration = value.(string) + } + + serviceToken, err := client.CreateAccessServiceToken(ctx, identifier, params) if err != nil { return diag.FromErr(fmt.Errorf("error creating access service token: %w", err)) @@ -104,6 +110,7 @@ func resourceCloudflareAccessServiceTokenCreate(ctx context.Context, d *schema.R d.Set("client_id", serviceToken.ClientID) d.Set("client_secret", serviceToken.ClientSecret) d.Set("expires_at", serviceToken.ExpiresAt.Format(time.RFC3339)) + d.Set("duration", serviceToken.Duration) resourceCloudflareAccessServiceTokenRead(ctx, d, meta) @@ -118,10 +125,16 @@ func resourceCloudflareAccessServiceTokenUpdate(ctx context.Context, d *schema.R return diag.FromErr(err) } - serviceToken, err := client.UpdateAccessServiceToken(ctx, identifier, cloudflare.UpdateAccessServiceTokenParams{ + params := cloudflare.UpdateAccessServiceTokenParams{ UUID: d.Id(), Name: d.Get("name").(string), - }) + } + + if d.HasChange("duration") { + params.Duration = d.Get("duration").(string) + } + + serviceToken, err := client.UpdateAccessServiceToken(ctx, identifier, params) if err != nil { return diag.FromErr(fmt.Errorf("error updating access service token: %w", err)) diff --git a/internal/sdkv2provider/resource_cloudflare_access_service_tokens_test.go b/internal/sdkv2provider/resource_cloudflare_access_service_tokens_test.go index cae861e55e..bae613a04e 100644 --- a/internal/sdkv2provider/resource_cloudflare_access_service_tokens_test.go +++ b/internal/sdkv2provider/resource_cloudflare_access_service_tokens_test.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -func TestAccCloudflareAccessServiceTokenCreate(t *testing.T) { +func TestAccCloudflareAccessServiceToken_Basic(t *testing.T) { // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Access // Service Tokens endpoint does not yet support the API tokens and it // results in misleading state error messages. @@ -22,7 +22,7 @@ func TestAccCloudflareAccessServiceTokenCreate(t *testing.T) { } rnd := generateRandomResourceName() - name := fmt.Sprintf("cloudflare_access_service_token.tf-acc-%s", rnd) + name := fmt.Sprintf("cloudflare_access_service_token.%s", rnd) resourceName := strings.Split(name, ".")[1] resource.Test(t, resource.TestCase{ @@ -37,58 +37,18 @@ func TestAccCloudflareAccessServiceTokenCreate(t *testing.T) { resource.TestCheckResourceAttrSet(name, "client_id"), resource.TestCheckResourceAttrSet(name, "client_secret"), resource.TestCheckResourceAttrSet(name, "expires_at"), + resource.TestCheckResourceAttr(name, "duration", "8760h"), ), }, - }, - }) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ { - Config: testCloudflareAccessServiceTokenBasicConfig(resourceName, resourceName, cloudflare.ZoneIdentifier(zoneID)), + Config: testCloudflareAccessServiceTokenBasicConfig(resourceName, resourceName+"-updated", cloudflare.AccountIdentifier(accountID)), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(name, consts.ZoneIDSchemaKey, zoneID), - resource.TestCheckResourceAttr(name, "name", resourceName), + resource.TestCheckResourceAttr(name, consts.AccountIDSchemaKey, accountID), + resource.TestCheckResourceAttr(name, "name", resourceName+"-updated"), resource.TestCheckResourceAttrSet(name, "client_id"), resource.TestCheckResourceAttrSet(name, "client_secret"), resource.TestCheckResourceAttrSet(name, "expires_at"), - ), - }, - }, - }) -} - -func TestAccCloudflareAccessServiceTokenUpdate(t *testing.T) { - // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Access - // Service Tokens endpoint does not yet support the API tokens and it - // results in misleading state error messages. - if os.Getenv("CLOUDFLARE_API_TOKEN") != "" { - t.Setenv("CLOUDFLARE_API_TOKEN", "") - } - - rnd := generateRandomResourceName() - name := fmt.Sprintf("cloudflare_access_service_token.tf-acc-%s", rnd) - resourceName := strings.Split(name, ".")[1] - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccPreCheckAccount(t) - }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ - { - Config: testCloudflareAccessServiceTokenBasicConfig(resourceName, resourceName, cloudflare.AccountIdentifier(accountID)), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(name, "name", resourceName), - ), - }, - { - Config: testCloudflareAccessServiceTokenBasicConfig(resourceName, resourceName+"-updated", cloudflare.AccountIdentifier(accountID)), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(name, "name", resourceName+"-updated"), + resource.TestCheckResourceAttr(name, "duration", "8760h"), ), }, }, @@ -101,13 +61,23 @@ func TestAccCloudflareAccessServiceTokenUpdate(t *testing.T) { { Config: testCloudflareAccessServiceTokenBasicConfig(resourceName, resourceName, cloudflare.ZoneIdentifier(zoneID)), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, consts.ZoneIDSchemaKey, zoneID), resource.TestCheckResourceAttr(name, "name", resourceName), + resource.TestCheckResourceAttrSet(name, "client_id"), + resource.TestCheckResourceAttrSet(name, "client_secret"), + resource.TestCheckResourceAttrSet(name, "expires_at"), + resource.TestCheckResourceAttr(name, "duration", "8760h"), ), }, { Config: testCloudflareAccessServiceTokenBasicConfig(resourceName, resourceName+"-updated", cloudflare.ZoneIdentifier(zoneID)), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, consts.ZoneIDSchemaKey, zoneID), resource.TestCheckResourceAttr(name, "name", resourceName+"-updated"), + resource.TestCheckResourceAttrSet(name, "client_id"), + resource.TestCheckResourceAttrSet(name, "client_secret"), + resource.TestCheckResourceAttrSet(name, "expires_at"), + resource.TestCheckResourceAttr(name, "duration", "8760h"), ), }, }, @@ -128,7 +98,7 @@ func TestAccCloudflareAccessServiceTokenUpdate(t *testing.T) { // rnd := generateRandomResourceName() // var initialState terraform.ResourceState -// name := fmt.Sprintf("cloudflare_access_service_token.tf-acc-%s", rnd) +// name := fmt.Sprintf("cloudflare_access_service_token.%s", rnd) // resourceName := strings.Split(name, ".")[1] // expirationTime := 365 @@ -195,7 +165,7 @@ func testAccCheckCloudflareAccessServiceTokenRenewed(n string, oldResourceState } } -func TestAccCloudflareAccessServiceTokenDelete(t *testing.T) { +func TestAccCloudflareAccessServiceToken_Delete(t *testing.T) { // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Access // Service Tokens endpoint does not yet support the API tokens and it // results in misleading state error messages. @@ -204,7 +174,7 @@ func TestAccCloudflareAccessServiceTokenDelete(t *testing.T) { } rnd := generateRandomResourceName() - name := fmt.Sprintf("cloudflare_access_service_token.tf-acc-%s", rnd) + name := fmt.Sprintf("cloudflare_access_service_token.%s", rnd) resourceName := strings.Split(name, ".")[1] resource.Test(t, resource.TestCase{ @@ -223,6 +193,7 @@ func TestAccCloudflareAccessServiceTokenDelete(t *testing.T) { resource.TestCheckResourceAttrSet(name, "client_id"), resource.TestCheckResourceAttrSet(name, "client_secret"), resource.TestCheckResourceAttrSet(name, "expires_at"), + resource.TestCheckResourceAttr(name, "duration", "8760h"), ), }, }, @@ -241,6 +212,83 @@ func TestAccCloudflareAccessServiceTokenDelete(t *testing.T) { resource.TestCheckResourceAttrSet(name, "client_id"), resource.TestCheckResourceAttrSet(name, "client_secret"), resource.TestCheckResourceAttrSet(name, "expires_at"), + resource.TestCheckResourceAttr(name, "duration", "8760h"), + ), + }, + }, + }) +} + +func TestAccCloudflareAccessServiceToken_WithDuration(t *testing.T) { + // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Access + // Service Tokens endpoint does not yet support the API tokens and it + // results in misleading state error messages. + if os.Getenv("CLOUDFLARE_API_TOKEN") != "" { + t.Setenv("CLOUDFLARE_API_TOKEN", "") + } + + rnd := generateRandomResourceName() + name := fmt.Sprintf("cloudflare_access_service_token.%s", rnd) + resourceName := strings.Split(name, ".")[1] + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAccount(t) + }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckCloudflareAccessServiceTokenDestroy, + Steps: []resource.TestStep{ + { + Config: testCloudflareAccessServiceTokenBasicConfigWithDuration(resourceName, resourceName, cloudflare.AccountIdentifier(accountID), "forever"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, consts.AccountIDSchemaKey, accountID), + resource.TestCheckResourceAttr(name, "name", resourceName), + resource.TestCheckResourceAttrSet(name, "client_id"), + resource.TestCheckResourceAttrSet(name, "client_secret"), + resource.TestCheckResourceAttrSet(name, "expires_at"), + resource.TestCheckResourceAttr(name, "duration", "forever"), + ), + }, + { + Config: testCloudflareAccessServiceTokenBasicConfigWithDuration(resourceName, resourceName, cloudflare.AccountIdentifier(accountID), "8760h"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, consts.AccountIDSchemaKey, accountID), + resource.TestCheckResourceAttr(name, "name", resourceName), + resource.TestCheckResourceAttrSet(name, "client_id"), + resource.TestCheckResourceAttrSet(name, "client_secret"), + resource.TestCheckResourceAttrSet(name, "expires_at"), + resource.TestCheckResourceAttr(name, "duration", "8760h"), + ), + }, + }, + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckCloudflareAccessServiceTokenDestroy, + Steps: []resource.TestStep{ + { + Config: testCloudflareAccessServiceTokenBasicConfigWithDuration(resourceName, resourceName, cloudflare.ZoneIdentifier(zoneID), "forever"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, consts.ZoneIDSchemaKey, zoneID), + resource.TestCheckResourceAttr(name, "name", resourceName), + resource.TestCheckResourceAttrSet(name, "client_id"), + resource.TestCheckResourceAttrSet(name, "client_secret"), + resource.TestCheckResourceAttrSet(name, "expires_at"), + resource.TestCheckResourceAttr(name, "duration", "forever"), + ), + }, + { + Config: testCloudflareAccessServiceTokenBasicConfigWithDuration(resourceName, resourceName, cloudflare.ZoneIdentifier(zoneID), "8760h"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, consts.ZoneIDSchemaKey, zoneID), + resource.TestCheckResourceAttr(name, "name", resourceName), + resource.TestCheckResourceAttrSet(name, "client_id"), + resource.TestCheckResourceAttrSet(name, "client_secret"), + resource.TestCheckResourceAttrSet(name, "expires_at"), + resource.TestCheckResourceAttr(name, "duration", "8760h"), ), }, }, @@ -256,6 +304,16 @@ resource "cloudflare_access_service_token" "%[1]s" { }`, resourceName, tokenName, identifier.Type, identifier.Identifier) } +func testCloudflareAccessServiceTokenBasicConfigWithDuration(resourceName string, tokenName string, identifier *cloudflare.ResourceContainer, duration string) string { + return fmt.Sprintf(` +resource "cloudflare_access_service_token" "%[1]s" { + %[3]s_id = "%[4]s" + name = "%[2]s" + min_days_for_renewal = "0" + duration = "%[5]s" +}`, resourceName, tokenName, identifier.Type, identifier.Identifier, duration) +} + func testAccCheckCloudflareAccessServiceTokenDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*cloudflare.API) diff --git a/internal/sdkv2provider/schema_cloudflare_access_service_tokens.go b/internal/sdkv2provider/schema_cloudflare_access_service_tokens.go index f7c813bc96..389ceced5b 100644 --- a/internal/sdkv2provider/schema_cloudflare_access_service_tokens.go +++ b/internal/sdkv2provider/schema_cloudflare_access_service_tokens.go @@ -1,8 +1,11 @@ package sdkv2provider import ( + "fmt" + "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) func resourceCloudflareAccessServiceTokenSchema() map[string]*schema.Schema { @@ -48,5 +51,12 @@ func resourceCloudflareAccessServiceTokenSchema() map[string]*schema.Schema { Default: 0, Description: "Refresh the token if terraform is run within the specified amount of days before expiration", }, + "duration": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{"8760h", "17520h", "43800h", "87600h", "forever"}, false), + Description: fmt.Sprintf("Length of time the service token is valid for. %s", renderAvailableDocumentationValuesStringSlice([]string{"8760h", "17520h", "43800h", "87600h", "forever"})), + }, } }