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 Worker KV Namespace Bindings #544

Merged
merged 3 commits into from
Dec 3, 2019
Merged
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
4 changes: 2 additions & 2 deletions cloudflare/import_cloudflare_worker_script_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ func TestAccCloudflareWorkerScript_Import(t *testing.T) {
{
Config: testAccCheckCloudflareWorkerScriptConfigMultiScriptInitial(rnd),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWorkerScriptExists(name, &script),
testAccCheckCloudflareWorkerScriptExists(name, &script, nil),
),
},
{
ResourceName: name,
ImportState: true,
ImportStateVerify: true,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWorkerScriptExists(name, &script),
testAccCheckCloudflareWorkerScriptExists(name, &script, nil),
),
},
},
Expand Down
108 changes: 99 additions & 9 deletions cloudflare/resource_cloudflare_worker_script.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/pkg/errors"
)
Expand All @@ -31,10 +32,33 @@ func resourceCloudflareWorkerScript() *schema.Resource {
Type: schema.TypeString,
Required: true,
},
"kv_namespace_binding": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"namespace_id": {
Type: schema.TypeString,
Required: true,
},
},
},
Set: resourceCloudflareWorkerScriptKvNamespaceBindingHash,
},
},
}
}

func resourceCloudflareWorkerScriptKvNamespaceBindingHash(v interface{}) int {
m := v.(map[string]interface{})

return hashcode.String(fmt.Sprintf("%s-%s", m["name"].(string), m["namespace_id"].(string)))
}

type ScriptData struct {
// The script id will be the `name` for named script
// or the `zone_name` for zone-scoped scripts
Expand All @@ -45,9 +69,7 @@ type ScriptData struct {
func getScriptData(d *schema.ResourceData, client *cloudflare.API) (ScriptData, error) {
scriptName := d.Get("name").(string)

var params cloudflare.WorkerRequestParams

params = cloudflare.WorkerRequestParams{
params := cloudflare.WorkerRequestParams{
ScriptName: scriptName,
}

Expand All @@ -57,6 +79,32 @@ func getScriptData(d *schema.ResourceData, client *cloudflare.API) (ScriptData,
}, nil
}

type ScriptBindings map[string]cloudflare.WorkerBinding

func getWorkerScriptBindings(scriptName string, client *cloudflare.API) (ScriptBindings, error) {
resp, err := client.ListWorkerBindings(&cloudflare.WorkerRequestParams{ScriptName: scriptName})
if err != nil {
return nil, fmt.Errorf("cannot list script bindings: %v", err)
}

bindings := make(ScriptBindings, len(resp.BindingList))

for _, b := range resp.BindingList {
bindings[b.Name] = b.Binding
}

return bindings, nil
}

func parseWorkerKvNamespaceBindings(d *schema.ResourceData, bindings ScriptBindings) {
for _, rawData := range d.Get("kv_namespace_binding").(*schema.Set).List() {
data := rawData.(map[string]interface{})
bindings[data["name"].(string)] = cloudflare.WorkerKvNamespaceBinding{
NamespaceID: data["namespace_id"].(string),
}
}
}

func resourceCloudflareWorkerScriptCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)

Expand All @@ -68,7 +116,7 @@ func resourceCloudflareWorkerScriptCreate(d *schema.ResourceData, meta interface
// make sure that the worker does not already exist
r, _ := client.DownloadWorker(&scriptData.Params)
if r.WorkerScript.Script != "" {
return fmt.Errorf("script already exists.")
return fmt.Errorf("script already exists")
}

scriptBody := d.Get("content").(string)
Expand All @@ -78,7 +126,16 @@ func resourceCloudflareWorkerScriptCreate(d *schema.ResourceData, meta interface

log.Printf("[INFO] Creating Cloudflare Worker Script from struct: %+v", &scriptData.Params)

_, err = client.UploadWorker(&scriptData.Params, scriptBody)
bindings := make(ScriptBindings)

parseWorkerKvNamespaceBindings(d, bindings)

scriptParams := cloudflare.WorkerScriptParams{
Script: scriptBody,
Bindings: bindings,
}

_, err = client.UploadWorkerWithBindings(&scriptData.Params, &scriptParams)
if err != nil {
return errors.Wrap(err, "error creating worker script")
}
Expand Down Expand Up @@ -109,7 +166,31 @@ func resourceCloudflareWorkerScriptRead(d *schema.ResourceData, meta interface{}
fmt.Sprintf("Error reading worker script from API for resouce %+v", &scriptData.Params))
}

d.Set("content", r.Script)
bindings, err := getWorkerScriptBindings(d.Get("name").(string), client)
if err != nil {
return err
}

kvNamespaceBindings := &schema.Set{
F: resourceCloudflareWorkerScriptKvNamespaceBindingHash,
}

for name, binding := range bindings {
switch v := binding.(type) {
case cloudflare.WorkerKvNamespaceBinding:
kvNamespaceBindings.Add(map[string]interface{}{
"name": name,
"namespace_id": v.NamespaceID,
})
}
}

_ = d.Set("content", r.Script)

if err := d.Set("kv_namespace_binding", kvNamespaceBindings); err != nil {
return fmt.Errorf("cannot set kv_namespace bindings for firewall policy (%s): %v", d.Id(), err)
}

return nil
}

Expand All @@ -128,7 +209,16 @@ func resourceCloudflareWorkerScriptUpdate(d *schema.ResourceData, meta interface

log.Printf("[INFO] Updating Cloudflare Worker Script from struct: %+v", &scriptData.Params)

_, err = client.UploadWorker(&scriptData.Params, scriptBody)
bindings := make(ScriptBindings)

parseWorkerKvNamespaceBindings(d, bindings)

scriptParams := cloudflare.WorkerScriptParams{
Script: scriptBody,
Bindings: bindings,
}

_, err = client.UploadWorkerWithBindings(&scriptData.Params, &scriptParams)
if err != nil {
return errors.Wrap(err, "error updating worker script")
}
Expand Down Expand Up @@ -162,9 +252,9 @@ func resourceCloudflareWorkerScriptDelete(d *schema.ResourceData, meta interface

func resourceCloudflareWorkerScriptImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
scriptID := d.Id()
d.Set("name", scriptID)
_ = d.Set("name", scriptID)

resourceCloudflareWorkerScriptRead(d, meta)
_ = resourceCloudflareWorkerScriptRead(d, meta)

return []*schema.ResourceData{d}, nil
}
49 changes: 46 additions & 3 deletions cloudflare/resource_cloudflare_worker_script_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cloudflare

import (
"fmt"
"strings"
"testing"

cloudflare "github.com/cloudflare/cloudflare-go"
Expand Down Expand Up @@ -32,15 +33,23 @@ func TestAccCloudflareWorkerScript_MultiScriptEnt(t *testing.T) {
{
Config: testAccCheckCloudflareWorkerScriptConfigMultiScriptInitial(rnd),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWorkerScriptExists(name, &script),
testAccCheckCloudflareWorkerScriptExists(name, &script, nil),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "content", scriptContent1),
),
},
{
Config: testAccCheckCloudflareWorkerScriptConfigMultiScriptUpdate(rnd),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWorkerScriptExists(name, &script),
testAccCheckCloudflareWorkerScriptExists(name, &script, nil),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "content", scriptContent2),
),
},
{
Config: testAccCheckCloudflareWorkerScriptConfigMultiScriptUpdateKvNamespaceBinding(rnd),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWorkerScriptExists(name, &script, []string{rnd, fmt.Sprintf("%s-copy", rnd)}),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "content", scriptContent2),
),
Expand All @@ -65,6 +74,28 @@ resource "cloudflare_worker_script" "%[1]s" {
}`, rnd, scriptContent2)
}

func testAccCheckCloudflareWorkerScriptConfigMultiScriptUpdateKvNamespaceBinding(rnd string) string {
return fmt.Sprintf(`
resource "cloudflare_workers_kv_namespace" "%[1]s" {
title = "%[1]s"
}

resource "cloudflare_worker_script" "%[1]s" {
name = "%[1]s"
content = "%[2]s"

kv_namespace_binding {
name = "%[1]s"
namespace_id = cloudflare_workers_kv_namespace.%[1]s.id
}

kv_namespace_binding {
name = "%[1]s-copy"
namespace_id = cloudflare_workers_kv_namespace.%[1]s.id
}
}`, rnd, scriptContent2)
}

func getRequestParamsFromResource(rs *terraform.ResourceState) cloudflare.WorkerRequestParams {
params := cloudflare.WorkerRequestParams{
ScriptName: rs.Primary.Attributes["name"],
Expand All @@ -73,7 +104,7 @@ func getRequestParamsFromResource(rs *terraform.ResourceState) cloudflare.Worker
return params
}

func testAccCheckCloudflareWorkerScriptExists(n string, script *cloudflare.WorkerScript) resource.TestCheckFunc {
func testAccCheckCloudflareWorkerScriptExists(n string, script *cloudflare.WorkerScript, bindings []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
Expand All @@ -95,6 +126,18 @@ func testAccCheckCloudflareWorkerScriptExists(n string, script *cloudflare.Worke
return fmt.Errorf("Worker Script not found")
}

name := strings.Replace(n, "cloudflare_worker_script.", "", -1)
foundBindings, err := getWorkerScriptBindings(name, client)
if err != nil {
return fmt.Errorf("cannot list script bindings: %v", err)
}

for _, binding := range bindings {
if _, ok := foundBindings[binding]; !ok {
return fmt.Errorf("cannot find binding with name %s", binding)
}
}

*script = r.WorkerScript
return nil
}
Expand Down
14 changes: 14 additions & 0 deletions website/docs/r/worker_script.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@ Provides a Cloudflare worker script resource. In order for a script to be active
## Example Usage

```hcl
resource "cloudflare_workers_kv_namespace" "my_namespace" {
title = "example"
}

# Sets the script with the name "script_1"
resource "cloudflare_worker_script" "my_script" {
name = "script_1"
content = file("script.js")

kv_namespace_binding {
name = "my_binding"
namespace_id = cloudflare_workers_kv_namespace.my_namespace.id
}
}
```

Expand All @@ -27,6 +36,11 @@ The following arguments are supported:
* `name` - (Required) The name for the script.
* `content` - (Required) The script content.

**kv_namespace_binding** (optional) block supports:

* `name` - (Required) The name for the binding.
* `namespace_id` - (Required) ID of KV namespace.

## Import

To import a script, use a script name, e.g. `script_name`
Expand Down