Skip to content

Commit

Permalink
Add CMEK support to google_storage_bucket_object (#4561) (#8615)
Browse files Browse the repository at this point in the history
* mark field as updatable

Co-authored-by: upodroid <[email protected]>

* add object cmek support

Co-authored-by: upodroid <[email protected]>

* fix typo

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Mar 4, 2021
1 parent 552c3ca commit 75a641d
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 28 deletions.
3 changes: 3 additions & 0 deletions .changelog/4561.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
storage: added `kms_key_name` to `google_storage_bucket_object` resource
```
33 changes: 31 additions & 2 deletions google/resource_storage_bucket_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"log"
"os"
"strings"

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

Expand Down Expand Up @@ -151,6 +152,15 @@ func resourceStorageBucketObject() *schema.Resource {
Description: `The StorageClass of the new bucket object. Supported values include: MULTI_REGIONAL, REGIONAL, NEARLINE, COLDLINE, ARCHIVE. If not provided, this defaults to the bucket's default storage class or to a standard class.`,
},

"kms_key_name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
DiffSuppressFunc: compareCryptoKeyVersions,
Description: `Resource name of the Cloud KMS key that will be used to encrypt the object. Overrides the object metadata's kmsKeyName value, if any.`,
},

"metadata": {
Type: schema.TypeMap,
Optional: true,
Expand Down Expand Up @@ -182,10 +192,22 @@ func resourceStorageBucketObject() *schema.Resource {
}
}

func objectGetId(object *storage.Object) string {
func objectGetID(object *storage.Object) string {
return object.Bucket + "-" + object.Name
}

func compareCryptoKeyVersions(_, old, new string, _ *schema.ResourceData) bool {
// The API can return cryptoKeyVersions even though it wasn't specified.
// format: projects/<project>/locations/<region>/keyRings/<keyring>/cryptoKeys/<key>/cryptoKeyVersions/1

kmsKeyWithoutVersions := strings.Split(old, "/cryptoKeyVersions")[0]
if kmsKeyWithoutVersions == new {
return true
}

return false
}

func resourceStorageBucketObjectCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
userAgent, err := generateUserAgentString(d, config.userAgent)
Expand Down Expand Up @@ -240,6 +262,10 @@ func resourceStorageBucketObjectCreate(d *schema.ResourceData, meta interface{})
object.StorageClass = v.(string)
}

if v, ok := d.GetOk("kms_key_name"); ok {
object.KmsKeyName = v.(string)
}

insertCall := objectsService.Insert(bucket, object)
insertCall.Name(name)
insertCall.Media(media)
Expand Down Expand Up @@ -299,6 +325,9 @@ func resourceStorageBucketObjectRead(d *schema.ResourceData, meta interface{}) e
if err := d.Set("storage_class", res.StorageClass); err != nil {
return fmt.Errorf("Error setting storage_class: %s", err)
}
if err := d.Set("kms_key_name", res.KmsKeyName); err != nil {
return fmt.Errorf("Error setting kms_key_name: %s", err)
}
if err := d.Set("self_link", res.SelfLink); err != nil {
return fmt.Errorf("Error setting self_link: %s", err)
}
Expand All @@ -312,7 +341,7 @@ func resourceStorageBucketObjectRead(d *schema.ResourceData, meta interface{}) e
return fmt.Errorf("Error setting media_link: %s", err)
}

d.SetId(objectGetId(res))
d.SetId(objectGetID(res))

return nil
}
Expand Down
106 changes: 80 additions & 26 deletions google/resource_storage_bucket_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestAccStorageObject_basic(t *testing.T) {
if _, err := h.Write(data); err != nil {
t.Errorf("error calculating md5: %v", err)
}
data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
dataMd5 := base64.StdEncoding.EncodeToString(h.Sum(nil))

testFile := getNewTmpTestFile(t, "tf-test")
if err := ioutil.WriteFile(testFile.Name(), data, 0644); err != nil {
Expand All @@ -42,7 +42,7 @@ func TestAccStorageObject_basic(t *testing.T) {
Steps: []resource.TestStep{
{
Config: testGoogleStorageBucketsObjectBasic(bucketName, testFile.Name()),
Check: testAccCheckGoogleStorageObject(t, bucketName, objectName, data_md5),
Check: testAccCheckGoogleStorageObject(t, bucketName, objectName, dataMd5),
},
},
})
Expand All @@ -58,17 +58,17 @@ func TestAccStorageObject_recreate(t *testing.T) {
if _, err := h.Write(data); err != nil {
t.Errorf("error calculating md5: %v", err)
}
data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
dataMd5 := base64.StdEncoding.EncodeToString(h.Sum(nil))

if err := ioutil.WriteFile(name, data, 0644); err != nil {
t.Errorf("error writing file: %v", err)
}
return data_md5
return dataMd5
}
testFile := getNewTmpTestFile(t, "tf-test")
data_md5 := writeFile(testFile.Name(), []byte("data data data"))
dataMd5 := writeFile(testFile.Name(), []byte("data data data"))
updatedName := testFile.Name() + ".update"
updated_data_md5 := writeFile(updatedName, []byte("datum"))
updatedDataMd5 := writeFile(updatedName, []byte("datum"))

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand All @@ -77,7 +77,7 @@ func TestAccStorageObject_recreate(t *testing.T) {
Steps: []resource.TestStep{
{
Config: testGoogleStorageBucketsObjectBasic(bucketName, testFile.Name()),
Check: testAccCheckGoogleStorageObject(t, bucketName, objectName, data_md5),
Check: testAccCheckGoogleStorageObject(t, bucketName, objectName, dataMd5),
},
{
PreConfig: func() {
Expand All @@ -87,7 +87,7 @@ func TestAccStorageObject_recreate(t *testing.T) {
}
},
Config: testGoogleStorageBucketsObjectBasic(bucketName, testFile.Name()),
Check: testAccCheckGoogleStorageObject(t, bucketName, objectName, updated_data_md5),
Check: testAccCheckGoogleStorageObject(t, bucketName, objectName, updatedDataMd5),
},
},
})
Expand All @@ -102,7 +102,7 @@ func TestAccStorageObject_content(t *testing.T) {
if _, err := h.Write(data); err != nil {
t.Errorf("error calculating md5: %v", err)
}
data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
dataMd5 := base64.StdEncoding.EncodeToString(h.Sum(nil))

testFile := getNewTmpTestFile(t, "tf-test")
if err := ioutil.WriteFile(testFile.Name(), data, 0644); err != nil {
Expand All @@ -116,7 +116,7 @@ func TestAccStorageObject_content(t *testing.T) {
{
Config: testGoogleStorageBucketsObjectContent(bucketName),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleStorageObject(t, bucketName, objectName, data_md5),
testAccCheckGoogleStorageObject(t, bucketName, objectName, dataMd5),
resource.TestCheckResourceAttr(
"google_storage_bucket_object.object", "content_type", "text/plain; charset=utf-8"),
resource.TestCheckResourceAttr(
Expand All @@ -136,7 +136,7 @@ func TestAccStorageObject_withContentCharacteristics(t *testing.T) {
if _, err := h.Write(data); err != nil {
t.Errorf("error calculating md5: %v", err)
}
data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
dataMd5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
testFile := getNewTmpTestFile(t, "tf-test")
if err := ioutil.WriteFile(testFile.Name(), data, 0644); err != nil {
t.Errorf("error writing file: %v", err)
Expand All @@ -149,10 +149,10 @@ func TestAccStorageObject_withContentCharacteristics(t *testing.T) {
CheckDestroy: testAccStorageObjectDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testGoogleStorageBucketsObject_optionalContentFields(
Config: testGoogleStorageBucketsObjectOptionalContentFields(
bucketName, disposition, encoding, language, content_type),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleStorageObject(t, bucketName, objectName, data_md5),
testAccCheckGoogleStorageObject(t, bucketName, objectName, dataMd5),
resource.TestCheckResourceAttr(
"google_storage_bucket_object.object", "content_disposition", disposition),
resource.TestCheckResourceAttr(
Expand Down Expand Up @@ -197,7 +197,7 @@ func TestAccStorageObject_cacheControl(t *testing.T) {
if _, err := h.Write(data); err != nil {
t.Errorf("error calculating md5: %v", err)
}
data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
dataMd5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
testFile := getNewTmpTestFile(t, "tf-test")
if err := ioutil.WriteFile(testFile.Name(), data, 0644); err != nil {
t.Errorf("error writing file: %v", err)
Expand All @@ -210,9 +210,9 @@ func TestAccStorageObject_cacheControl(t *testing.T) {
CheckDestroy: testAccStorageObjectDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testGoogleStorageBucketsObject_cacheControl(bucketName, testFile.Name(), cacheControl),
Config: testGoogleStorageBucketsObjectCacheControl(bucketName, testFile.Name(), cacheControl),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleStorageObject(t, bucketName, objectName, data_md5),
testAccCheckGoogleStorageObject(t, bucketName, objectName, dataMd5),
resource.TestCheckResourceAttr(
"google_storage_bucket_object.object", "cache_control", cacheControl),
),
Expand All @@ -230,7 +230,7 @@ func TestAccStorageObject_storageClass(t *testing.T) {
if _, err := h.Write(data); err != nil {
t.Errorf("error calculating md5: %v", err)
}
data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
dataMd5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
testFile := getNewTmpTestFile(t, "tf-test")
if err := ioutil.WriteFile(testFile.Name(), data, 0644); err != nil {
t.Errorf("error writing file: %v", err)
Expand All @@ -243,9 +243,9 @@ func TestAccStorageObject_storageClass(t *testing.T) {
CheckDestroy: testAccStorageObjectDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testGoogleStorageBucketsObject_storageClass(bucketName, storageClass),
Config: testGoogleStorageBucketsObjectStorageClass(bucketName, storageClass),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleStorageObject(t, bucketName, objectName, data_md5),
testAccCheckGoogleStorageObject(t, bucketName, objectName, dataMd5),
resource.TestCheckResourceAttr(
"google_storage_bucket_object.object", "storage_class", storageClass),
),
Expand All @@ -263,7 +263,7 @@ func TestAccStorageObject_metadata(t *testing.T) {
if _, err := h.Write(data); err != nil {
t.Errorf("error calculating md5: %v", err)
}
data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
dataMd5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
testFile := getNewTmpTestFile(t, "tf-test")
if err := ioutil.WriteFile(testFile.Name(), data, 0644); err != nil {
t.Errorf("error writing file: %v", err)
Expand All @@ -275,9 +275,9 @@ func TestAccStorageObject_metadata(t *testing.T) {
CheckDestroy: testAccStorageObjectDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testGoogleStorageBucketsObject_metadata(bucketName),
Config: testGoogleStorageBucketsObjectMetadata(bucketName),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleStorageObject(t, bucketName, objectName, data_md5),
testAccCheckGoogleStorageObject(t, bucketName, objectName, dataMd5),
resource.TestCheckResourceAttr(
"google_storage_bucket_object.object", "metadata.customKey", "custom_value"),
),
Expand All @@ -286,6 +286,35 @@ func TestAccStorageObject_metadata(t *testing.T) {
})
}

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

kms := BootstrapKMSKeyInLocation(t, "us")
bucketName := testBucketName(t)
data := []byte("data data data")
h := md5.New()
if _, err := h.Write(data); err != nil {
t.Errorf("error calculating md5: %v", err)
}
dataMd5 := base64.StdEncoding.EncodeToString(h.Sum(nil))

testFile := getNewTmpTestFile(t, "tf-test")
if err := ioutil.WriteFile(testFile.Name(), data, 0644); err != nil {
t.Errorf("error writing file: %v", err)
}
vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccStorageObjectDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testGoogleStorageBucketsObjectKms(bucketName, testFile.Name(), kms.CryptoKey.Name),
Check: testAccCheckGoogleStorageObject(t, bucketName, objectName, dataMd5),
},
},
})
}

func testAccCheckGoogleStorageObject(t *testing.T, bucket, object, md5 string) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := googleProviderConfig(t)
Expand Down Expand Up @@ -375,7 +404,7 @@ resource "google_storage_bucket_object" "object" {
`, bucketName, objectName, sourceFilename)
}

func testGoogleStorageBucketsObject_optionalContentFields(
func testGoogleStorageBucketsObjectOptionalContentFields(
bucketName, disposition, encoding, language, content_type string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
Expand All @@ -394,7 +423,7 @@ resource "google_storage_bucket_object" "object" {
`, bucketName, objectName, content, disposition, encoding, language, content_type)
}

func testGoogleStorageBucketsObject_cacheControl(bucketName, sourceFilename, cacheControl string) string {
func testGoogleStorageBucketsObjectCacheControl(bucketName, sourceFilename, cacheControl string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
name = "%s"
Expand All @@ -409,7 +438,7 @@ resource "google_storage_bucket_object" "object" {
`, bucketName, objectName, sourceFilename, cacheControl)
}

func testGoogleStorageBucketsObject_storageClass(bucketName string, storageClass string) string {
func testGoogleStorageBucketsObjectStorageClass(bucketName string, storageClass string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
name = "%s"
Expand All @@ -424,7 +453,7 @@ resource "google_storage_bucket_object" "object" {
`, bucketName, objectName, content, storageClass)
}

func testGoogleStorageBucketsObject_metadata(bucketName string) string {
func testGoogleStorageBucketsObjectMetadata(bucketName string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
name = "%s"
Expand All @@ -442,6 +471,31 @@ resource "google_storage_bucket_object" "object" {
`, bucketName, objectName, content)
}

func testGoogleStorageBucketsObjectKms(bucketName, sourceFilename, kmsKey string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
name = "%s"
}
data "google_storage_project_service_account" "gcs" {
}
resource "google_kms_crypto_key_iam_member" "crypto_key" {
crypto_key_id = "%s"
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:${data.google_storage_project_service_account.gcs.email_address}"
}
resource "google_storage_bucket_object" "object" {
name = "%s"
bucket = google_storage_bucket.bucket.name
source = "%s"
kms_key_name = google_kms_crypto_key_iam_member.crypto_key.crypto_key_id
}
`, bucketName, kmsKey, objectName, sourceFilename)
}

// Creates a new tmp test file. Fails the current test if we cannot create
// new tmp file in the filesystem.
func getNewTmpTestFile(t *testing.T, prefix string) *os.File {
Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/storage_bucket_object.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ One of the following is required:
Supported values include: `MULTI_REGIONAL`, `REGIONAL`, `NEARLINE`, `COLDLINE`, `ARCHIVE`. If not provided, this defaults to the bucket's default
storage class or to a [standard](https://cloud.google.com/storage/docs/storage-classes#standard) class.

* `kms_key_name` - (Optional) The resource name of the Cloud KMS key that will be used to [encrypt](https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys) the object.

## Attributes Reference

In addition to the arguments listed above, the following computed attributes are
Expand Down

0 comments on commit 75a641d

Please sign in to comment.