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

Cloudflare worker secret resource #2628

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2c94ecd
Initial commit - broken, just saving stuff.
helenatuominen Nov 3, 2020
728ef4b
Pushing current code, work_secret implemented, problems with temp wor…
helenatuominen Nov 12, 2020
83982d1
Still working on manual creation/destroy of workers
helenatuominen Nov 13, 2020
12b0a8c
Completed worker manual create/delete outside of terraform plan in test.
helenatuominen Nov 16, 2020
a673e67
Updated to remove length check.
helenatuominen Nov 16, 2020
364bd16
Update cloudflare/resource_cloudflare_worker_secret.go
helenatuominen Nov 17, 2020
4af614c
reformat resource_cloudflare_worker_secret.go
patryk Nov 17, 2020
0fb2d12
Committing prior to creation of test using Terraform plan for Worker
helenatuominen Nov 30, 2020
3f6bbb7
Merge remote-tracking branch 'origin/master' into cloudflare_worker_s…
lboynton Jul 20, 2023
a658f8e
Fix some imports
lboynton Jul 20, 2023
dbb46ee
Merge branch 'cloudflare:master' into cloudflare_worker_secret
lboynton Jul 21, 2023
2dd0cf4
Merge branch 'cloudflare:master' into cloudflare_worker_secret
lboynton Sep 5, 2023
ca7fc76
Add missing account ID to schema
lboynton Sep 5, 2023
d55a82c
Move schema to separate file
lboynton Sep 5, 2023
bac6515
Allow updating secret text
lboynton Sep 5, 2023
0f58ac4
Add changelog entry
lboynton Sep 5, 2023
9762adb
Add example
lboynton Sep 5, 2023
4483bd6
fix tests a bit, but still broken
lboynton Sep 5, 2023
2b62b6f
Tests passing :)
lboynton Sep 5, 2023
8348c92
Make it possible to use cloudflare_worker_script and cloudflare_worke…
lboynton Sep 5, 2023
7f5b5e4
attempt to fix tests again
lboynton Sep 5, 2023
cee170e
follow standards
lboynton Sep 15, 2023
704777f
use updating logging pattern
lboynton Sep 15, 2023
eddea35
return read secret
lboynton Sep 15, 2023
722220a
Merge branch 'master' into cloudflare_worker_secret
lboynton Sep 15, 2023
a261fca
improve importing
lboynton Sep 15, 2023
b596375
improve importing a bit more
lboynton Sep 15, 2023
c73dd00
Merge branch 'master' into cloudflare_worker_secret
lboynton Oct 20, 2023
c5f3460
Fix error message string interpolation
lboynton Oct 22, 2023
1a972ad
wip on import test
lboynton Oct 23, 2023
da3d000
Delete import test as secret cannot be imported
lboynton Oct 23, 2023
1157bea
Pass test if worker script does not exist
lboynton Oct 23, 2023
8963723
Add import example
lboynton Oct 23, 2023
b82429e
fix import example
lboynton Oct 23, 2023
4669c6e
fix formatting
lboynton Oct 23, 2023
7212905
Don't create new contexts
lboynton Oct 23, 2023
2e5fdb2
Merge branch 'master' into cloudflare_worker_secret
lboynton Oct 23, 2023
1d94ae8
Revert "Delete import test as secret cannot be imported"
lboynton Oct 30, 2023
ee4ca83
Apply suggestions from code review
lboynton Oct 30, 2023
42a1bf0
Reinstate import worker secret test
lboynton Oct 30, 2023
375d00c
Merge remote-tracking branch 'lboynton/cloudflare_worker_secret' into…
lboynton Oct 30, 2023
5946cef
Ignore missing secret_text in state from import test
lboynton Oct 30, 2023
7371ec1
Merge remote-tracking branch 'origin/master' into cloudflare_worker_s…
lboynton Oct 30, 2023
d354617
formatting
lboynton Oct 30, 2023
51069dc
Merge branch 'master' into cloudflare_worker_secret
lboynton Nov 7, 2023
30a9c86
Merge branch 'master' into cloudflare_worker_secret
lboynton Nov 29, 2023
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/2628.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
cloudflare_worker_secret
```
1 change: 1 addition & 0 deletions examples/resources/cloudflare_worker_secret/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$ terraform import cloudflare_worker_secret.example <account_id>/<script_name>/<secret_name>
6 changes: 6 additions & 0 deletions examples/resources/cloudflare_worker_secret/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
resource "cloudflare_worker_secret" "my_secret" {
account_id = "f037e56e89293a057740de681ac9abbe"
name = "MY_EXAMPLE_SECRET_TEXT"
script_name = "script_1"
secret_text = "my_secret_value"
}
44 changes: 44 additions & 0 deletions internal/sdkv2provider/import_cloudflare_worker_secret_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package sdkv2provider

import (
"fmt"
"os"
"testing"

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

func TestAccCloudflareWorkerSecret_Import(t *testing.T) {
secretName := generateRandomResourceName()
resourceName := "cloudflare_worker_secret." + secretName
secretText := generateRandomResourceName()
workerSecretTestScriptName = generateRandomResourceName()
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
testAccPreCheckAccount(t)
},
ProviderFactories: providerFactories,
CheckDestroy: testAccCheckCloudflareWorkerSecretDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareWorkerSecretWithWorkerScript(workerSecretTestScriptName, secretName, secretText, accountID),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWorkerSecretExists(workerSecretTestScriptName, secretName, accountID),
),
},
{
ResourceName: resourceName,
ImportStateId: fmt.Sprintf("%s/%s/%s", accountID, workerSecretTestScriptName, secretName),
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"secret_text"},
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWorkerSecretExists(workerSecretTestScriptName, secretName, accountID),
),
},
},
})
}
1 change: 1 addition & 0 deletions internal/sdkv2provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ func New(version string) func() *schema.Provider {
"cloudflare_worker_domain": resourceCloudflareWorkerDomain(),
"cloudflare_worker_route": resourceCloudflareWorkerRoute(),
"cloudflare_worker_script": resourceCloudflareWorkerScript(),
"cloudflare_worker_secret": resourceCloudflareWorkerSecret(),
"cloudflare_workers_kv_namespace": resourceCloudflareWorkersKVNamespace(),
"cloudflare_workers_kv": resourceCloudflareWorkerKV(),
"cloudflare_zone_cache_reserve": resourceCloudflareZoneCacheReserve(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,10 @@ func resourceCloudflareWorkerScriptRead(ctx context.Context, d *schema.ResourceD
return diag.FromErr(fmt.Errorf("cannot set plain text bindings (%s): %w", d.Id(), err))
}

if err := d.Set("secret_text_binding", secretTextBindings); err != nil {
return diag.FromErr(fmt.Errorf("cannot set secret text bindings (%s): %w", d.Id(), err))
if d.HasChange("secret_text_binding") || len(d.Get("secret_text_binding").(*schema.Set).List()) > 0 {
if err := d.Set("secret_text_binding", secretTextBindings); err != nil {
return diag.FromErr(fmt.Errorf("cannot set secret text bindings (%s): %w", d.Id(), err))
}
}

if err := d.Set("webassembly_binding", webAssemblyBindings); err != nil {
Expand Down
116 changes: 116 additions & 0 deletions internal/sdkv2provider/resource_cloudflare_workers_secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package sdkv2provider

import (
"context"
"fmt"
"github.com/MakeNowJust/heredoc/v2"
cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/cloudflare/terraform-provider-cloudflare/internal/consts"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/pkg/errors"
"strings"
)

func resourceCloudflareWorkerSecret() *schema.Resource {
return &schema.Resource{
Schema: resourceCloudflareWorkerSecretSchema(),
CreateContext: resourceCloudflareWorkerSecretCreate,
ReadContext: resourceCloudflareWorkerSecretRead,
UpdateContext: resourceCloudflareWorkerSecretCreate,
DeleteContext: resourceCloudflareWorkerSecretDelete,
Importer: &schema.ResourceImporter{
StateContext: resourceCloudflareWorkerSecretImport,
},
Description: heredoc.Doc("Provides a Cloudflare Worker secret resource."),
}
}

func resourceCloudflareWorkerSecretRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
accountID := d.Get(consts.AccountIDSchemaKey).(string)
secrets, err := client.ListWorkersSecrets(ctx, cloudflare.AccountIdentifier(accountID), cloudflare.ListWorkersSecretsParams{
ScriptName: d.Get("script_name").(string),
})
if err != nil {
return diag.FromErr(errors.Wrap(err, fmt.Sprintf("Error listing worker secrets")))
}

for _, secret := range secrets.Result {
tflog.Info(ctx, fmt.Sprintf("Found secret %s", secret.Name))
if secret.Name == d.Get("name") {
return nil
}
}

return diag.Errorf(fmt.Sprintf("worker secret %s not found for script %s", d.Get("name"), d.Get("script_name")))
}

func resourceCloudflareWorkerSecretCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
accountID := d.Get(consts.AccountIDSchemaKey).(string)
scriptName := d.Get("script_name").(string)
name := d.Get("name").(string)
secretText := d.Get("secret_text").(string)

params := cloudflare.SetWorkersSecretParams{
Secret: &cloudflare.WorkersPutSecretRequest{
Name: name,
Text: secretText,
Type: cloudflare.WorkerSecretTextBindingType,
},
ScriptName: scriptName,
}

_, err := client.SetWorkersSecret(ctx, cloudflare.AccountIdentifier(accountID), params)
if err != nil {
return diag.FromErr(errors.Wrap(err, "error creating worker secret"))
}

d.SetId(stringChecksum(fmt.Sprintf("%s/%s/%s", accountID, scriptName, name)))

tflog.Info(ctx, fmt.Sprintf("Created Cloudflare Workers secret with ID: %s", d.Id()))

return resourceCloudflareWorkerSecretRead(ctx, d, meta)
}

func resourceCloudflareWorkerSecretDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)
accountID := d.Get(consts.AccountIDSchemaKey).(string)
scriptName := d.Get("script_name").(string)
name := d.Get("name").(string)

params := cloudflare.DeleteWorkersSecretParams{
SecretName: name,
ScriptName: scriptName,
}

tflog.Info(ctx, fmt.Sprintf("Deleting Cloudflare Workers secret with id: %s", d.Id()))

_, err := client.DeleteWorkersSecret(ctx, cloudflare.AccountIdentifier(accountID), params)
if err != nil {
return diag.FromErr(errors.Wrap(err, "error deleting worker secret"))
}

return nil
}

func resourceCloudflareWorkerSecretImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
attributes := strings.SplitN(d.Id(), "/", 3)

if len(attributes) != 3 {
return []*schema.ResourceData{nil}, fmt.Errorf(`invalid id (%q) specified, should be in format "accountID/scriptName/secretName"`, d.Id())
}

accountID, scriptName, secretName := attributes[0], attributes[1], attributes[2]

d.SetId(stringChecksum(fmt.Sprintf("%s/%s/%s", accountID, scriptName, secretName)))
d.Set("name", secretName)
d.Set(consts.AccountIDSchemaKey, accountID)
d.Set("script_name", scriptName)

resourceCloudflareWorkerSecretRead(ctx, d, meta)

return []*schema.ResourceData{d}, nil
}
104 changes: 104 additions & 0 deletions internal/sdkv2provider/resource_cloudflare_workers_secret_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package sdkv2provider

import (
"context"
"fmt"
"os"
"testing"

"github.com/cloudflare/cloudflare-go"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)

const scriptContentForSecret = `addEventListener('fetch', event => {event.respondWith(new Response('test 1'))});`

var workerSecretTestScriptName string

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

name := generateRandomResourceName()
secretText := generateRandomResourceName()
workerSecretTestScriptName = generateRandomResourceName()
accountId := os.Getenv("CLOUDFLARE_ACCOUNT_ID")

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
testAccPreCheckAccount(t)
},
ProviderFactories: providerFactories,
CheckDestroy: testAccCheckCloudflareWorkerSecretDestroy,
Steps: []resource.TestStep{
Copy link
Member

Choose a reason for hiding this comment

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

you should include a ImportState assertion here too (which would have caught your incorrect Import ID above).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added back a separate import secret test to be consistent with the other tests. This is currently failing with:

=== RUN   TestAccCloudflareWorkerSecret_Import
    import_cloudflare_worker_secret_test.go:17: Step 2/2 error running import: exit status 1
        
        Error: invalid id ("0e5935a6b09194ee32e735261911e922") specified, should be in format "accountID/scriptName/secretName"
        
        
--- FAIL: TestAccCloudflareWorkerSecret_Import (23.28s)

This is because the Cloudflare API doesn't let you read back the secret text as it's encrypted. I'm not sure how to reconcile this to make the tests pass?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got the tests to pass by ignoring state verification for that field

{
Config: testAccCheckCloudflareWorkerSecretWithWorkerScript(workerSecretTestScriptName, name, secretText, accountId),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareWorkerSecretExists(workerSecretTestScriptName, name, accountId),
),
},
},
})
}

func testAccCheckCloudflareWorkerSecretDestroy(s *terraform.State) error {
accountId := os.Getenv("CLOUDFLARE_ACCOUNT_ID")

for _, rs := range s.RootModule().Resources {
if rs.Type != "cloudflare_worker_secret" {
continue
}

client := testAccProvider.Meta().(*cloudflare.API)
params := cloudflare.ListWorkersSecretsParams{
ScriptName: rs.Primary.Attributes["script_name"],
}

secretResponse, err := client.ListWorkersSecrets(context.Background(), cloudflare.AccountIdentifier(accountId), params)

if err == nil {
return fmt.Errorf("worker secret with name %s still exists against Worker Script %s", secretResponse.Result[0].Name, params.ScriptName)
}
}

return nil
}

func testAccCheckCloudflareWorkerSecretWithWorkerScript(scriptName string, name string, secretText string, accountId string) string {
return fmt.Sprintf(`
resource "cloudflare_worker_script" "%[2]s" {
account_id = "%[4]s"
name = "%[1]s"
content = "%[5]s"
}

resource "cloudflare_worker_secret" "%[2]s" {
account_id = "%[4]s"
script_name = cloudflare_worker_script.%[2]s.name
name = "%[2]s"
secret_text = "%[3]s"
}`, scriptName, name, secretText, accountId, scriptContentForSecret)
}

func testAccCheckCloudflareWorkerSecretExists(scriptName string, name string, accountId string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*cloudflare.API)
params := cloudflare.ListWorkersSecretsParams{
ScriptName: scriptName,
}

secretResponse, err := client.ListWorkersSecrets(context.Background(), cloudflare.AccountIdentifier(accountId), params)

if err != nil {
return err
}

for _, secret := range secretResponse.Result {
if secret.Name == name {
return nil
}
}

return fmt.Errorf("worker secret with name %s not found against Worker Script %s", name, scriptName)
}
}
34 changes: 34 additions & 0 deletions internal/sdkv2provider/schema_cloudflare_workers_secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package sdkv2provider

import (
"github.com/cloudflare/terraform-provider-cloudflare/internal/consts"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceCloudflareWorkerSecretSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
consts.AccountIDSchemaKey: {
Description: consts.AccountIDSchemaDescription,
Type: schema.TypeString,
Required: true,
},
"script_name": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
Description: "The name of the Worker script to associate the secret with.",
},
"name": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
Description: "The name of the Worker secret.",
},
"secret_text": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
Description: "The text of the Worker secret, this cannot be read back after creation and is stored encrypted .",
},
}
}
Loading