Skip to content

Commit

Permalink
Add support for cloudflare_custom_pages resource
Browse files Browse the repository at this point in the history
This introduces support for managing the custom error pages as a Terraform resource.

Depends on cloudflare/cloudflare-go#240 however I will wait for that to land and then propose a PR that updates the vendored library in a separate PR.
  • Loading branch information
jacobbednarz committed Oct 24, 2018
1 parent 6ee7a1a commit 3572050
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 0 deletions.
1 change: 1 addition & 0 deletions cloudflare/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func Provider() terraform.ResourceProvider {
ResourcesMap: map[string]*schema.Resource{
"cloudflare_access_rule": resourceCloudflareAccessRule(),
"cloudflare_account_member": resourceCloudflareAccountMember(),
"cloudflare_custom_pages": resourceCloudflareCustomPages(),
"cloudflare_filter": resourceCloudflareFilter(),
"cloudflare_firewall_rule": resourceCloudflareFirewallRule(),
"cloudflare_load_balancer_monitor": resourceCloudflareLoadBalancerMonitor(),
Expand Down
175 changes: 175 additions & 0 deletions cloudflare/resource_cloudflare_custom_pages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package cloudflare

import (
"fmt"
"log"
"strings"

cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"github.com/pkg/errors"
)

func resourceCloudflareCustomPages() *schema.Resource {
return &schema.Resource{
// Pointing the `Create` at the `Update` method here is intentional.
// Custom pages don't really get "created" as they are always
// present in Cloudflare. We just update and toggle the settings to
// be customised.
Create: resourceCloudflareCustomPagesUpdate,
Read: resourceCloudflareCustomPagesRead,
Update: resourceCloudflareCustomPagesUpdate,
Delete: resourceCloudflareCustomPagesDelete,
Importer: &schema.ResourceImporter{
State: resourceCloudflareCustomPagesImport,
},

Schema: map[string]*schema.Schema{
"zone_id": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"account_id"},
},
"account_id": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"zone_id"},
},
"type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
"basic_challenge",
"waf_challenge",
"waf_block",
"ratelimit_block",
"country_challenge",
"ip_block",
"under_attack",
"500_errors",
"1000_errors",
"always_online",
}, true),
},
"url": {
Type: schema.TypeString,
Required: true,
},
"state": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"default", "customized"}, true),
},
},
}
}

func resourceCloudflareCustomPagesRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)
zoneID := d.Get("zone_id").(string)
accountID := d.Get("account_id").(string)
pageType := d.Get("type").(string)

if accountID == "" && zoneID == "" {
return fmt.Errorf("either `account_id` or `zone_id` must be set")
}

var pageOptions cloudflare.CustomPageOptions

if accountID != "" {
pageOptions = cloudflare.CustomPageOptions{AccountID: accountID}
} else {
pageOptions = cloudflare.CustomPageOptions{ZoneID: zoneID}
}

page, err := client.CustomPage(&pageOptions, pageType)
if err != nil {
return errors.New(err.Error())
}

// If the `page.State` comes back as "default", it's safe to assume we
// don't need to keep the ID managed anymore as it will be relying on
// Cloudflare's default pages.
if page.State == "default" {
log.Printf("[INFO] removing custom page configuration for '%s' as it is marked as being in the default state", pageType)
d.SetId("")
return nil
}

d.Set("state", page.State)
d.Set("url", page.URL)
d.Set("type", page.ID)

return nil
}

func resourceCloudflareCustomPagesUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)
accountID := d.Get("account_id").(string)
zoneID := d.Get("zone_id").(string)

var pageOptions cloudflare.CustomPageOptions
if accountID != "" {
pageOptions = cloudflare.CustomPageOptions{AccountID: accountID}
} else {
pageOptions = cloudflare.CustomPageOptions{ZoneID: zoneID}
}

pageType := d.Get("type").(string)
customPageParameters := cloudflare.CustomPageParameters{
URL: d.Get("url").(*string),
State: "customized",
}
_, err := client.UpdateCustomPage(&pageOptions, pageType, customPageParameters)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to update '%s' custom page", pageType))
}

return resourceCloudflareCustomPagesRead(d, meta)
}

func resourceCloudflareCustomPagesDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)
accountID := d.Get("account_id").(string)
zoneID := d.Get("zoneID").(string)

var pageOptions cloudflare.CustomPageOptions
if accountID != "" {
pageOptions = cloudflare.CustomPageOptions{AccountID: accountID}
} else {
pageOptions = cloudflare.CustomPageOptions{ZoneID: zoneID}
}

pageType := d.Get("type").(string)
customPageParameters := cloudflare.CustomPageParameters{
URL: nil,
State: "default",
}
_, err := client.UpdateCustomPage(&pageOptions, pageType, customPageParameters)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to update '%s' custom page", pageType))
}

return resourceCloudflareCustomPagesRead(d, meta)
}

func resourceCloudflareCustomPagesImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
attributes := strings.SplitN(d.Id(), "/", 3)
if len(attributes) != 3 {
return nil, fmt.Errorf("invalid id (\"%s\") specified, should be in format \"requestType/ID/pageType\"", d.Id())
}
requestType, identifier, pageType := attributes[0], attributes[1], attributes[2]

d.Set("type", pageType)

if requestType == "account" {
d.Set("account_id", identifier)
} else {
d.Set("zone_id", identifier)
}

resourceCloudflareCustomPagesRead(d, meta)

return []*schema.ResourceData{d}, nil
}
12 changes: 12 additions & 0 deletions cloudflare/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cloudflare
import (
"fmt"
"net"
"net/url"
"strings"

"github.com/hashicorp/terraform/helper/schema"
Expand Down Expand Up @@ -86,3 +87,14 @@ func validateIntInSlice(valid []int) schema.SchemaValidateFunc {
return nil, es
}
}

// validateURL provides a method to test whether the provided string
// is a valid URL. Relying on `url.ParseRequestURI` isn't the most
// robust solution it will catch majority of the issues we're looking to
// handle here but there _could_ be edge cases.
func validateURL(v interface{}, k string) (s []string, errors []error) {
if _, err := url.ParseRequestURI(v.(string)); err != nil {
errors = append(errors, fmt.Errorf("%q: %s", k, err))
}
return
}
3 changes: 3 additions & 0 deletions website/cloudflare.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
<li<%= sidebar_current("docs-cloudflare-resource-access-rule") %>>
<a href="/docs/providers/cloudflare/r/access_rule.html">cloudflare_access_rule</a>
</li>
<li<%= sidebar_current("docs-cloudflare-resource-custom-pages") %>>
<a href="/docs/providers/cloudflare/r/custom_pages.html">cloudflare_custom_pages</a>
</li>
<li<%= sidebar_current("docs-cloudflare-resource-filter") %>>
<a href="/docs/providers/cloudflare/r/filter.html">cloudflare_filter</a>
</li>
Expand Down
60 changes: 60 additions & 0 deletions website/docs/r/custom_pages.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
layout: "cloudflare"
page_title: "Cloudflare: cloudflare_custom_pages"
sidebar_current: "docs-cloudflare-resource-custom-pages"
description: |-
Provides a resource which manages Cloudflare custom pages.
---

# cloudflare_custom_pages

Provides a resource which manages Cloudflare custom error pages.

## Example Usage

```hcl
resource "cloudflare_custom_pages" "basic_challenge" {
zone_id = "d41d8cd98f00b204e9800998ecf8427e"
type = "basic_challenge"
url = "https://example.com/challenge.html"
state = "customized"
}
```

## Argument Reference

The following arguments are supported:

* `zone_id` - (Optional) The zone ID where the custom pages should be
updated. Either `zone_id` or `account_id` must be provided.
* `account_id` - (Optional) The account ID where the custom pages should be
updated. Either `account_id` or `zone_id` must be provided. If
`account_id` is present, it will override the zone setting.
* `type` - (Required) The type of custom page you wish to update. Must
be one of `basic_challenge`, `waf_challenge`, `waf_block`,
`ratelimit_block`, `country_challenge`, `ip_block`, `under_attack`,
`500_errors`, `1000_errors`, `always_online`.
* `url` - (Required) URL of where the custom page source is located.
* `state` - (Required) Managed state of the custom page. Must be one of
`default`, `customised`. If the value is `default` it will be removed
from the Terraform state management.

## Import

Custom pages can be imported using a composite ID formed of:

* `customPageLevel` - Either `account` or `zone`.
* `identifier` - The ID of the account or zone you intend to manage.
* `pageType` - The value from the `type` argument.

Example for a zone:

```
$ terraform import cloudflare_custom_pages.basic_challenge zone/d41d8cd98f00b204e9800998ecf8427e/basic_challenge
```

Example for an account:

```
$ terraform import cloudflare_custom_pages.basic_challenge account/e268443e43d93dab7ebef303bbe9642f/basic_challenge
```

0 comments on commit 3572050

Please sign in to comment.