Skip to content

Commit e630450

Browse files
feat(resource_fastly_object_storage_access_key): add ability to manage access keys (#955)
* feat(resource_fastly_object_storage_access_key): add ability to manage access keys
1 parent 783bf52 commit e630450

File tree

7 files changed

+339
-26
lines changed

7 files changed

+339
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- feat(fastly_vcl_service): Add DDoS protection product enablement/configuration ([#954](https://github.com/fastly/terraform-provider-fastly/pull/954))
2020
- feat(fastly_compute_service): Add Next-Gen WAF product enablement/configuration ([#956](https://github.com/fastly/terraform-provider-fastly/pull/956))
2121
- feat(fastly_vcl_service): Add Next-Gen WAF product enablement/configuration ([#956](https://github.com/fastly/terraform-provider-fastly/pull/956))
22+
- feat(resource_fastly_object_storage_access_key): Add object storage access keys configuration ([#955](https://github.com/fastly/terraform-provider-fastly/pull/955))
2223

2324
### BUG FIXES:
2425

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
layout: "fastly"
3+
page_title: "fastly_object_storage_access_keys Resource - terraform-provider-fastly"
4+
sidebar_current: "docs-fastly-resource-object-storage-access-keys"
5+
description: |-
6+
An access key can be used to manage different object storage buckets in your cloud of choice
7+
---
8+
9+
# fastly_object_storage_access_keys (Resource)
10+
11+
Provides an Object Storage Access Key, which can be used to manage resources in various clouds.
12+
13+
Implements the [Object Storage Access Key API functions](https://www.fastly.com/documentation/reference/api/services/resources/object-storage-access-keys/)
14+
15+
-> **Note:** Access Keys cannot be updated, so when you change a value in any of the editable fields the key is destroyed and remade
16+
17+
Basic usage
18+
19+
```terraform
20+
resource "fastly_object_storage_access_keys" "demo" {
21+
buckets = ["bucket1", "bucket2" ]
22+
description = "this is a bucket"
23+
permission = ""
24+
}
25+
```
26+
-> **Note:** Permissions can only be one of these values listed [here](https://quic.fastly.com/documentation/reference/api/services/resources/object-storage-access-keys/#permissions), any other values will return an error
27+
<!-- schema generated by tfplugindocs -->
28+
## Schema
29+
30+
### Required
31+
32+
- `description` (String) The description of the access key
33+
- `permission` (String) The permissions of the access key
34+
35+
### Optional
36+
37+
- `buckets` (List of String) Optional list of buckets the access key will be associated with. Example: `["bucket1", "bucket2"]`
38+
39+
### Read-Only
40+
41+
- `access_key_id` (String) ID for the object storage access token
42+
- `id` (String) The ID of this resource.
43+
- `secret_key` (String, Sensitive) Secret key for the object storage access token

fastly/fastly_test.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,13 @@ import (
77
"errors"
88
"fmt"
99
"os"
10-
"reflect"
1110
"strings"
1211
"testing"
1312
"text/template"
1413

1514
mapset "github.com/deckarep/golang-set/v2"
1615
gofastly "github.com/fastly/go-fastly/v9/fastly"
1716
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
18-
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1917
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
2018
)
2119

@@ -109,30 +107,6 @@ func appendNewLine(s string) string {
109107
return s + "\n"
110108
}
111109

112-
// assertEqualsSliceOfMaps compares a slice of maps even if they include schema.Set values
113-
func assertEqualsSliceOfMaps(t *testing.T, actualSlice []map[string]any, expectedSlice []map[string]any) {
114-
for i, actualMap := range actualSlice {
115-
var keysToBeRemoved []string
116-
for key, value := range actualMap {
117-
if v, ok := value.(*schema.Set); ok {
118-
expected := expectedSlice[i][key]
119-
keysToBeRemoved = append(keysToBeRemoved, key)
120-
if !v.Equal(expected) {
121-
t.Errorf("expected sets %s to be equal: %#v\n got: %#v", key, expected, actualSlice)
122-
}
123-
}
124-
}
125-
for _, key := range keysToBeRemoved {
126-
delete(actualMap, key)
127-
delete(expectedSlice[i], key)
128-
}
129-
}
130-
131-
if !reflect.DeepEqual(actualSlice, expectedSlice) {
132-
t.Fatalf("Error matching:\nexpected: %#v\n got: %#v", expectedSlice, actualSlice)
133-
}
134-
}
135-
136110
// generateHex produces a slice of 16 random bytes.
137111
// This is useful for dynamically generating resource names.
138112
func generateHex() string {

fastly/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func Provider() *schema.Provider {
7575
"fastly_domain_v1": resourceFastlyDomainV1(),
7676
"fastly_integration": resourceFastlyIntegration(),
7777
"fastly_kvstore": resourceFastlyKVStore(),
78+
"fastly_object_storage_access_keys": resourceObjectStorageAccessKey(),
7879
"fastly_secretstore": resourceFastlySecretStore(),
7980
"fastly_service_acl_entries": resourceServiceACLEntries(),
8081
"fastly_service_authorization": resourceServiceAuthorization(),
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package fastly
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
gofastly "github.com/fastly/go-fastly/v9/fastly"
8+
"github.com/fastly/go-fastly/v9/fastly/objectstorage/accesskeys"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
)
12+
13+
func resourceObjectStorageAccessKey() *schema.Resource {
14+
return &schema.Resource{
15+
CreateContext: resourceObjectStorageAccessKeyCreate,
16+
ReadContext: resourceObjectStorageAccessKeyRead,
17+
DeleteContext: resourceObjectStorageAccessKeyDelete,
18+
Importer: &schema.ResourceImporter{
19+
StateContext: schema.ImportStatePassthroughContext,
20+
},
21+
Schema: map[string]*schema.Schema{
22+
"access_key_id": {
23+
Type: schema.TypeString,
24+
Computed: true,
25+
Description: "ID for the object storage access token",
26+
},
27+
"buckets": {
28+
Type: schema.TypeList,
29+
Optional: true,
30+
Description: "Optional list of buckets the access key will be associated with. Example: `[\"bucket1\", \"bucket2\"]`",
31+
Elem: &schema.Schema{Type: schema.TypeString},
32+
ForceNew: true,
33+
},
34+
"description": {
35+
Type: schema.TypeString,
36+
Required: true,
37+
Description: "The description of the access key",
38+
ForceNew: true,
39+
},
40+
"permission": {
41+
Type: schema.TypeString,
42+
Required: true,
43+
Description: "The permissions of the access key",
44+
ForceNew: true,
45+
},
46+
"secret_key": {
47+
Type: schema.TypeString,
48+
Computed: true,
49+
Sensitive: true,
50+
Description: "Secret key for the object storage access token",
51+
},
52+
},
53+
}
54+
}
55+
56+
func resourceObjectStorageAccessKeyCreate(_ context.Context, resourceData *schema.ResourceData, meta any) diag.Diagnostics {
57+
conn := meta.(*APIClient).conn
58+
59+
opts := accesskeys.CreateInput{
60+
Description: gofastly.ToPointer(resourceData.Get("description").(string)),
61+
Permission: gofastly.ToPointer(resourceData.Get("permission").(string)),
62+
}
63+
64+
buckets := []string{}
65+
if val, ok := resourceData.GetOk("buckets"); ok {
66+
for _, bucket := range val.([]any) {
67+
buckets = append(buckets, bucket.(string))
68+
}
69+
opts.Buckets = gofastly.ToPointer(buckets)
70+
}
71+
createdAK, err := accesskeys.Create(conn, &opts)
72+
if err != nil {
73+
return diag.FromErr(err)
74+
}
75+
76+
if createdAK.AccessKeyID == "" {
77+
return diag.Errorf("error: accessKey.AccessKeyID is empty")
78+
}
79+
resourceData.SetId(createdAK.AccessKeyID)
80+
resourceData.Set("access_key_id", createdAK.AccessKeyID)
81+
if createdAK.SecretKey == "" {
82+
return diag.Errorf("error: accessKey.SecretKey is empty")
83+
}
84+
resourceData.Set("secret_key", createdAK.SecretKey)
85+
86+
return nil
87+
}
88+
89+
func resourceObjectStorageAccessKeyRead(_ context.Context, resourceData *schema.ResourceData, meta any) diag.Diagnostics {
90+
log.Printf("[DEBUG] Refreshing Object Storage Access Key Configuration for (%s)", resourceData.Get("access_key_id"))
91+
conn := meta.(*APIClient).conn
92+
93+
opts := accesskeys.GetInput{
94+
AccessKeyID: gofastly.ToPointer(resourceData.Id()),
95+
}
96+
97+
readAK, err := accesskeys.Get(conn, &opts)
98+
if err != nil {
99+
return diag.FromErr(err)
100+
}
101+
if readAK.Permission != "" {
102+
resourceData.Set("permission", readAK.Permission)
103+
}
104+
if readAK.Description != "" {
105+
resourceData.Set("description", readAK.Description)
106+
}
107+
if len(readAK.Buckets) != 0 {
108+
err = resourceData.Set("buckets", readAK.Buckets)
109+
if err != nil {
110+
return diag.FromErr(err)
111+
}
112+
}
113+
if readAK.SecretKey != "" {
114+
resourceData.Set("secret_key", readAK.SecretKey)
115+
}
116+
if readAK.AccessKeyID != "" {
117+
resourceData.Set("access_key_id", readAK.SecretKey)
118+
}
119+
120+
return nil
121+
}
122+
123+
func resourceObjectStorageAccessKeyDelete(_ context.Context, resourceData *schema.ResourceData, meta any) diag.Diagnostics {
124+
conn := meta.(*APIClient).conn
125+
126+
opts := accesskeys.DeleteInput{
127+
AccessKeyID: gofastly.ToPointer(resourceData.Id()),
128+
}
129+
130+
err := accesskeys.Delete(conn, &opts)
131+
if err != nil {
132+
return diag.FromErr(err)
133+
}
134+
135+
return nil
136+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package fastly
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
"text/template"
8+
9+
gofastly "github.com/fastly/go-fastly/v9/fastly"
10+
"github.com/fastly/go-fastly/v9/fastly/objectstorage/accesskeys"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
14+
)
15+
16+
func TestAccFastlyObjectStorageAccessKey_basic(t *testing.T) {
17+
resourceName := "fastly_object_storage_access_keys.storage_key1"
18+
var accessKey accesskeys.AccessKey
19+
description := fmt.Sprintf("this is a test key %s", acctest.RandString(10))
20+
permission := "read-write-objects"
21+
bucket1 := "bucket1"
22+
bucket2 := "bucket2"
23+
24+
type Config struct {
25+
Description string
26+
Permission string
27+
Buckets []string
28+
}
29+
30+
tmplText := `
31+
resource "fastly_object_storage_access_keys" "storage_key1" {
32+
buckets = ["{{ StringsJoin .Buckets "\", \"" }}"]
33+
description = "{{ .Description }}"
34+
permission = "{{ .Permission }}"
35+
}`
36+
tmpl, err := template.New("test").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(tmplText)
37+
if err != nil {
38+
t.Fatal(err)
39+
}
40+
41+
resource.ParallelTest(t, resource.TestCase{
42+
PreCheck: func() {
43+
testAccPreCheck(t)
44+
},
45+
ProviderFactories: testAccProviders,
46+
CheckDestroy: testAccCheckObjectStorageAccessKeyDestroy,
47+
Steps: []resource.TestStep{
48+
{
49+
Config: renderTestConfigTemplate(t, tmpl, Config{
50+
Description: description,
51+
Permission: permission,
52+
Buckets: []string{bucket1, bucket2},
53+
}),
54+
Check: resource.ComposeTestCheckFunc(
55+
testAccCheckObjectStorageAccessKeyExists(resourceName, &accessKey),
56+
resource.TestCheckResourceAttr(resourceName, "description", description),
57+
resource.TestCheckResourceAttr(resourceName, "permission", permission),
58+
resource.TestCheckResourceAttr(resourceName, "buckets.#", "2"),
59+
resource.TestCheckTypeSetElemAttr(resourceName, "buckets.*", bucket1),
60+
resource.TestCheckTypeSetElemAttr(resourceName, "buckets.*", bucket2),
61+
resource.TestCheckResourceAttrSet(resourceName, "secret_key"),
62+
resource.TestCheckResourceAttrSet(resourceName, "access_key_id"),
63+
),
64+
},
65+
},
66+
})
67+
}
68+
69+
func testAccCheckObjectStorageAccessKeyExists(n string, accessKey *accesskeys.AccessKey) resource.TestCheckFunc {
70+
return func(s *terraform.State) error {
71+
rs, ok := s.RootModule().Resources[n]
72+
if !ok {
73+
return fmt.Errorf("not found: %s", n)
74+
}
75+
76+
if rs.Primary.ID == "" {
77+
return fmt.Errorf("no ObjectStorageAccessKey ID is set")
78+
}
79+
80+
conn := testAccProvider.Meta().(*APIClient).conn
81+
opts := accesskeys.GetInput{
82+
AccessKeyID: gofastly.ToPointer(rs.Primary.ID),
83+
}
84+
ak, err := accesskeys.Get(conn, &opts)
85+
if err != nil {
86+
return err
87+
}
88+
89+
*accessKey = *ak
90+
91+
return nil
92+
}
93+
}
94+
95+
func testAccCheckObjectStorageAccessKeyDestroy(s *terraform.State) error {
96+
for _, rs := range s.RootModule().Resources {
97+
if rs.Type != "fastly_object_storage_access_keys" {
98+
continue
99+
}
100+
101+
conn := testAccProvider.Meta().(*APIClient).conn
102+
akl, err := accesskeys.ListAccessKeys(conn)
103+
if err != nil {
104+
return fmt.Errorf("error getting current accessKeys when deleting Fastly Object Storage AccessKey (%s): %s", rs.Primary.ID, err)
105+
}
106+
107+
for _, ak := range akl.Data {
108+
if ak.AccessKeyID == rs.Primary.ID {
109+
// accessKey still found
110+
return fmt.Errorf("tried deleting ObjectStorageAccessKey (%s), but was still found", rs.Primary.ID)
111+
}
112+
}
113+
}
114+
return nil
115+
}

0 commit comments

Comments
 (0)