Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for account resource #1902

Merged
merged 23 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d756a2b
functional cloudflare_account resource
mlhnono68 Sep 9, 2022
4b34348
adding basic test for cloudflare_account resource
mlhnono68 Sep 9, 2022
82ef4cd
adding changelog
mlhnono68 Sep 9, 2022
e0e4273
verifying UpdateAccount return value
mlhnono68 Sep 9, 2022
26f36dc
Update internal/provider/resource_cloudflare_account.go
mlhnono68 Sep 14, 2022
50c8a98
validate account type by schema validation only
mlhnono68 Sep 14, 2022
9f6a575
removing redundant getCloudflareAccontFromId
mlhnono68 Sep 14, 2022
4778e72
cleaning useless error debug finding accountID
mlhnono68 Sep 14, 2022
eb17af3
replacing GetOkExists by GetOk for account properties
mlhnono68 Sep 14, 2022
3dc566a
adding support for error 1001 to be ignored
mlhnono68 Sep 14, 2022
69d4f39
tflog instead of printf
mlhnono68 Sep 14, 2022
f628d5a
simpler if
mlhnono68 Sep 14, 2022
9cfa418
adding example
mlhnono68 Sep 14, 2022
15dc2c5
after make docs
mlhnono68 Sep 14, 2022
732a819
additional account.md
mlhnono68 Sep 14, 2022
b8011c7
using errors.As
mlhnono68 Sep 14, 2022
bdaf421
omitting Account.Type on update to prevent 1001 error
mlhnono68 Sep 15, 2022
3c48170
Merge branch 'master' into feat-accounts-resource
jacobbednarz Sep 21, 2022
1632568
clean up examples
jacobbednarz Sep 21, 2022
879670f
fix test cases and building updated accounts
jacobbednarz Sep 21, 2022
d321272
sort resources alphabetically
jacobbednarz Sep 21, 2022
6359a83
fix schema definition
jacobbednarz Sep 21, 2022
4ebc365
fix `email_routing_catch_all docs`
jacobbednarz Sep 21, 2022
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
3 changes: 3 additions & 0 deletions .changelog/1902.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
cloudflare_account
```
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ func New(version string) func() *schema.Provider {
"cloudflare_zone_lockdown": resourceCloudflareZoneLockdown(),
"cloudflare_zone_settings_override": resourceCloudflareZoneSettingsOverride(),
"cloudflare_zone": resourceCloudflareZone(),
"cloudflare_account": resourceCloudflareAccount(),
},
}

Expand Down
150 changes: 150 additions & 0 deletions internal/provider/resource_cloudflare_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package provider

import (
"context"
"errors"
"fmt"
"log"

"github.com/MakeNowJust/heredoc/v2"
cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
accountTypeStandard = "standard"
accountTypeEnterprise = "enterprise"
)

func resourceCloudflareAccount() *schema.Resource {
return &schema.Resource{
Schema: resourceCloudflareAccountSchema(),
CreateContext: resourceCloudflareAccountCreate,
ReadContext: resourceCloudflareAccountRead,
UpdateContext: resourceCloudflareAccountUpdate,
DeleteContext: resourceCloudflareAccountDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: heredoc.Doc(`
Provides a Cloudflare Account resource. Account is the basic resource for
working with Cloudflare zones, teams and users. Requires the tenant entitlement
mlhnono68 marked this conversation as resolved.
Show resolved Hide resolved
`),
}
}

func resourceCloudflareAccountCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
accountName := d.Get("name").(string)
var accountType string
if d.Get("type").(string) == "" {
mlhnono68 marked this conversation as resolved.
Show resolved Hide resolved
accountType = accountTypeStandard
} else if d.Get("type").(string) == accountTypeStandard || d.Get("type").(string) == accountTypeEnterprise {
accountType = d.Get("type").(string)
} else {
return diag.FromErr(fmt.Errorf("invalid account type %s", d.Get("type").(string)))
}

tflog.Info(ctx, fmt.Sprintf("Creating Cloudflare Account: name %s", accountName))

account := cloudflare.Account{
Name: accountName,
Type: accountType,
}
acc, err := client.CreateAccount(ctx, account)

if err != nil {
return diag.FromErr(fmt.Errorf("error creating account %q: %w", accountName, err))
}

d.SetId(acc.ID)

return resourceCloudflareAccountRead(ctx, d, meta)
}

func getCloudflareAccontFromId(accountID string, client *cloudflare.API, ctx context.Context) (cloudflare.Account, error) {
mlhnono68 marked this conversation as resolved.
Show resolved Hide resolved
accs, _, err := client.Accounts(ctx, cloudflare.AccountsListParams{})
if err != nil {
return cloudflare.Account{}, err
} else {
for _, acc := range accs {
if acc.ID == accountID {
return acc, nil
}
}

return cloudflare.Account{}, fmt.Errorf("Account %s does not exist", accountID)
}
}

func resourceCloudflareAccountRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
accountID := d.Id()

foundAcc, err := getCloudflareAccontFromId(accountID, client, ctx)
tflog.Debug(ctx, fmt.Sprintf("AccountDetails error: %#v", err))
mlhnono68 marked this conversation as resolved.
Show resolved Hide resolved

if err != nil || foundAcc.ID == "" {
var notFoundError *cloudflare.NotFoundError
if errors.As(err, &notFoundError) {
tflog.Info(ctx, fmt.Sprintf("Account %s no longer exists", d.Id()))
d.SetId("")
return nil
}
return diag.FromErr(fmt.Errorf("error finding Account %q: %w", d.Id(), err))
}

tflog.Debug(ctx, fmt.Sprintf("AccountDetails: %#v", foundAcc))

d.Set("name", foundAcc.Name)
d.Set("type", foundAcc.Type)
d.Set("enforce_twofactor", foundAcc.Settings.EnforceTwoFactor)

return nil
}

func resourceCloudflareAccountUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
accountID := d.Id()
foundAcc, err := getCloudflareAccontFromId(accountID, client, ctx)

tflog.Debug(ctx, fmt.Sprintf("AccountDetails error: %#v", err))
mlhnono68 marked this conversation as resolved.
Show resolved Hide resolved

log.Printf("[INFO] Updating Cloudflare Account: id %s", accountID)
mlhnono68 marked this conversation as resolved.
Show resolved Hide resolved

if accountName, ok := d.GetOkExists("name"); ok && d.HasChange("name") {
mlhnono68 marked this conversation as resolved.
Show resolved Hide resolved
foundAcc.Name = accountName.(string)
}

if accountType, ok := d.GetOkExists("type"); ok && d.HasChange("type") {
foundAcc.Type = accountType.(string)
}

if enforce_twofactor, ok := d.GetOkExists("enforce_twofactor"); ok && d.HasChange("enforce_twofactor") {
foundAcc.Settings.EnforceTwoFactor = enforce_twofactor.(bool)
}

_, err = client.UpdateAccount(ctx, accountID, foundAcc)
if err != nil {
return diag.FromErr(fmt.Errorf("error updating Account %q: %w", d.Id(), err))
} else {
return resourceCloudflareAccountRead(ctx, d, meta)
}
mlhnono68 marked this conversation as resolved.
Show resolved Hide resolved
}

func resourceCloudflareAccountDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
accountID := d.Id()

log.Printf("[INFO] Deleting Cloudflare Account: id %s", accountID)
mlhnono68 marked this conversation as resolved.
Show resolved Hide resolved

err := client.DeleteAccount(ctx, accountID)

if err != nil {
return diag.FromErr(fmt.Errorf("error deleting Cloudflare Account: %w", err))
}

return nil
}
45 changes: 45 additions & 0 deletions internal/provider/resource_cloudflare_account_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package provider

import (
"fmt"
"testing"

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

func TestAccCloudflareAccount(t *testing.T) {
t.Parallel()

rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_account.%s", rnd)

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareAccountName(fmt.Sprintf("%s old", rnd)),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
name, "name", fmt.Sprintf("%s old", rnd)),
),
},
{
Config: testAccCheckCloudflareAccountName(fmt.Sprintf("%s new", rnd)),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
name, "name", fmt.Sprintf("%s new", rnd)),
),
},
},
})
}

func testAccCheckCloudflareAccountName(name string) string {
return fmt.Sprintf(`
resource "cloudflare_account" "%[1]s" {
name = "%[1]s"
}`, name)
}
28 changes: 28 additions & 0 deletions internal/provider/schema_cloudflare_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package provider
jacobbednarz marked this conversation as resolved.
Show resolved Hide resolved

import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"

func resourceCloudflareAccountSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "The name of the account that is displayed in the Cloudflare dashboard.",
},
"type": {
Type: schema.TypeString,
Optional: true,
Description: "Valid values are standard (default) and enterprise. For self-serve customers, use standard. For enterprise customers, use enterprise.",
Default: accountTypeStandard,
ValidateFunc: validation.StringInSlice([]string{accountTypeEnterprise, accountTypeStandard}, false),
ForceNew: true,
},
"enforce_twofactor": {
Description: "Whether 2FA is enforced on the account.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
}
}