Skip to content

Commit

Permalink
Add fields to compute instance template, image (#6919) (#13253)
Browse files Browse the repository at this point in the history
* Add the source_snapshot, source_snapshot_encyption_key, and source_image_encryption_key fields to compute instance template

* Add test for compute image encryption key

* Added website documentation

* Make kms_key_self_link fields required

Signed-off-by: Modular Magician <magic-modules@google.com>

Signed-off-by: Modular Magician <magic-modules@google.com>
  • Loading branch information
modular-magician authored Dec 15, 2022
1 parent f486fd5 commit ffc3213
Show file tree
Hide file tree
Showing 9 changed files with 543 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changelog/6919.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:enhancement
compute: added support for `image_encryption_key` to `google_compute_image`
```
```release-note:enhancement
compute: added support for `source_snapshot`, `source_snapshot_encyption_key`, and `source_image_encryption_key` to `google_compute_instance_template`
```
102 changes: 102 additions & 0 deletions google/resource_compute_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"log"
"reflect"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -87,6 +88,37 @@ Applicable only for bootable images.`,
Elem: computeImageGuestOsFeaturesSchema(),
// Default schema.HashSchema is used.
},
"image_encryption_key": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Description: `Encrypts the image using a customer-supplied encryption key.
After you encrypt an image with a customer-supplied key, you must
provide the same key if you use the image later (e.g. to create a
disk from the image)`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"kms_key_self_link": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
DiffSuppressFunc: compareSelfLinkRelativePaths,
Description: `The self link of the encryption key that is stored in Google Cloud
KMS.`,
},
"kms_key_service_account": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: `The service account being used for the encryption request for the
given KMS key. If absent, the Compute Engine default service
account is used.`,
},
},
},
},
"labels": {
Type: schema.TypeMap,
Optional: true,
Expand Down Expand Up @@ -256,6 +288,12 @@ func resourceComputeImageCreate(d *schema.ResourceData, meta interface{}) error
} else if v, ok := d.GetOkExists("guest_os_features"); !isEmptyValue(reflect.ValueOf(guestOsFeaturesProp)) && (ok || !reflect.DeepEqual(v, guestOsFeaturesProp)) {
obj["guestOsFeatures"] = guestOsFeaturesProp
}
imageEncryptionKeyProp, err := expandComputeImageImageEncryptionKey(d.Get("image_encryption_key"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("image_encryption_key"); !isEmptyValue(reflect.ValueOf(imageEncryptionKeyProp)) && (ok || !reflect.DeepEqual(v, imageEncryptionKeyProp)) {
obj["imageEncryptionKey"] = imageEncryptionKeyProp
}
labelsProp, err := expandComputeImageLabels(d.Get("labels"), d, config)
if err != nil {
return err
Expand Down Expand Up @@ -403,6 +441,9 @@ func resourceComputeImageRead(d *schema.ResourceData, meta interface{}) error {
if err := d.Set("guest_os_features", flattenComputeImageGuestOsFeatures(res["guestOsFeatures"], d, config)); err != nil {
return fmt.Errorf("Error reading Image: %s", err)
}
if err := d.Set("image_encryption_key", flattenComputeImageImageEncryptionKey(res["imageEncryptionKey"], d, config)); err != nil {
return fmt.Errorf("Error reading Image: %s", err)
}
if err := d.Set("labels", flattenComputeImageLabels(res["labels"], d, config)); err != nil {
return fmt.Errorf("Error reading Image: %s", err)
}
Expand Down Expand Up @@ -627,6 +668,33 @@ func flattenComputeImageGuestOsFeaturesType(v interface{}, d *schema.ResourceDat
return v
}

func flattenComputeImageImageEncryptionKey(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return nil
}
original := v.(map[string]interface{})
if len(original) == 0 {
return nil
}
transformed := make(map[string]interface{})
transformed["kms_key_self_link"] =
flattenComputeImageImageEncryptionKeyKmsKeySelfLink(original["kmsKeyName"], d, config)
transformed["kms_key_service_account"] =
flattenComputeImageImageEncryptionKeyKmsKeyServiceAccount(original["kmsKeyServiceAccount"], d, config)
return []interface{}{transformed}
}
func flattenComputeImageImageEncryptionKeyKmsKeySelfLink(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return v
}
vStr := v.(string)
return strings.Split(vStr, "/cryptoKeyVersions/")[0]
}

func flattenComputeImageImageEncryptionKeyKmsKeyServiceAccount(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenComputeImageLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}
Expand Down Expand Up @@ -706,6 +774,40 @@ func expandComputeImageGuestOsFeaturesType(v interface{}, d TerraformResourceDat
return v, nil
}

func expandComputeImageImageEncryptionKey(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
l := v.([]interface{})
if len(l) == 0 || l[0] == nil {
return nil, nil
}
raw := l[0]
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})

transformedKmsKeySelfLink, err := expandComputeImageImageEncryptionKeyKmsKeySelfLink(original["kms_key_self_link"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedKmsKeySelfLink); val.IsValid() && !isEmptyValue(val) {
transformed["kmsKeyName"] = transformedKmsKeySelfLink
}

transformedKmsKeyServiceAccount, err := expandComputeImageImageEncryptionKeyKmsKeyServiceAccount(original["kms_key_service_account"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedKmsKeyServiceAccount); val.IsValid() && !isEmptyValue(val) {
transformed["kmsKeyServiceAccount"] = transformedKmsKeyServiceAccount
}

return transformed, nil
}

func expandComputeImageImageEncryptionKeyKmsKeySelfLink(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandComputeImageImageEncryptionKeyKmsKeyServiceAccount(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandComputeImageLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) {
if v == nil {
return map[string]string{}, nil
Expand Down
63 changes: 63 additions & 0 deletions google/resource_compute_image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,30 @@ func TestAccComputeImage_resolveImage(t *testing.T) {
})
}

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

kmsKey := BootstrapKMSKeyInLocation(t, "us-central1")
kmsKeyName := GetResourceNameFromSelfLink(kmsKey.CryptoKey.Name)
kmsRingName := GetResourceNameFromSelfLink(kmsKey.KeyRing.Name)

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeInstanceTemplateDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccComputeImage_imageEncryptionKey(kmsRingName, kmsKeyName, randString(t, 10)),
},
{
ResourceName: "google_compute_image.image",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckComputeImageResolution(t *testing.T, n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := googleProviderConfig(t)
Expand Down Expand Up @@ -450,3 +474,42 @@ resource "google_compute_image" "foobar" {
}
`, diskName, snapshotName, imageName)
}

func testAccComputeImage_imageEncryptionKey(kmsRingName, kmsKeyName, suffix string) string {
return fmt.Sprintf(`
data "google_kms_key_ring" "ring" {
name = "%s"
location = "us-central1"
}
data "google_kms_crypto_key" "key" {
name = "%s"
key_ring = data.google_kms_key_ring.ring.id
}
resource "google_service_account" "test" {
account_id = "tf-test-sa-%s"
display_name = "KMS Ops Account"
}
resource "google_kms_crypto_key_iam_member" "crypto_key" {
crypto_key_id = data.google_kms_crypto_key.key.id
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:${google_service_account.test.email}"
}
data "google_compute_image" "debian" {
family = "debian-11"
project = "debian-cloud"
}
resource "google_compute_image" "image" {
name = "tf-test-image-%s"
source_image = data.google_compute_image.debian.self_link
image_encryption_key {
kms_key_self_link = data.google_kms_crypto_key.key.id
kms_key_service_account = google_service_account.test.email
}
}
`, kmsRingName, kmsKeyName, suffix, suffix)
}
109 changes: 104 additions & 5 deletions google/resource_compute_instance_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,74 @@ func resourceComputeInstanceTemplate() *schema.Resource {
ForceNew: true,
Description: `The image from which to initialize this disk. This can be one of: the image's self_link, projects/{project}/global/images/{image}, projects/{project}/global/images/family/{family}, global/images/{image}, global/images/family/{family}, family/{family}, {project}/{family}, {project}/{image}, {family}, or {image}. ~> Note: Either source or source_image is required when creating a new instance except for when creating a local SSD.`,
},
"source_image_encryption_key": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Description: `The customer-supplied encryption key of the source
image. Required if the source image is protected by a
customer-supplied encryption key.
Instance templates do not store customer-supplied
encryption keys, so you cannot create disks for
instances in a managed instance group if the source
images are encrypted with your own keys.`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"kms_key_service_account": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: `The service account being used for the encryption
request for the given KMS key. If absent, the Compute
Engine default service account is used.`,
},
"kms_key_self_link": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: `The self link of the encryption key that is stored in
Google Cloud KMS.`,
},
},
},
},
"source_snapshot": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: `The source snapshot to create this disk. When creating
a new instance, one of initializeParams.sourceSnapshot,
initializeParams.sourceImage, or disks.source is
required except for local SSD.`,
},
"source_snapshot_encryption_key": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Description: `The customer-supplied encryption key of the source snapshot.`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"kms_key_service_account": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: `The service account being used for the encryption
request for the given KMS key. If absent, the Compute
Engine default service account is used.`,
},
"kms_key_self_link": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: `The self link of the encryption key that is stored in
Google Cloud KMS.`,
},
},
},
},

"interface": {
Type: schema.TypeString,
Expand Down Expand Up @@ -886,7 +954,7 @@ func buildDisks(d *schema.ResourceData, config *Config) ([]*compute.AttachedDisk

if v, ok := d.GetOk(prefix + ".source"); ok {
disk.Source = v.(string)
conflicts := []string{"disk_size_gb", "disk_name", "disk_type", "source_image", "labels"}
conflicts := []string{"disk_size_gb", "disk_name", "disk_type", "source_image", "source_snapshot", "labels"}
for _, conflict := range conflicts {
if _, ok := d.GetOk(prefix + "." + conflict); ok {
return nil, fmt.Errorf("Cannot use `source` with any of the fields in %s", conflicts)
Expand All @@ -906,6 +974,8 @@ func buildDisks(d *schema.ResourceData, config *Config) ([]*compute.AttachedDisk
disk.InitializeParams.DiskType = v.(string)
}

disk.InitializeParams.Labels = expandStringMap(d, prefix+".labels")

if v, ok := d.GetOk(prefix + ".source_image"); ok {
imageName := v.(string)
imageUrl, err := resolveImage(config, project, imageName, userAgent)
Expand All @@ -917,7 +987,29 @@ func buildDisks(d *schema.ResourceData, config *Config) ([]*compute.AttachedDisk
disk.InitializeParams.SourceImage = imageUrl
}

disk.InitializeParams.Labels = expandStringMap(d, prefix+".labels")
if _, ok := d.GetOk(prefix + ".source_image_encryption_key"); ok {
disk.InitializeParams.SourceImageEncryptionKey = &compute.CustomerEncryptionKey{}
if v, ok := d.GetOk(prefix + ".source_image_encryption_key.0.kms_key_self_link"); ok {
disk.InitializeParams.SourceImageEncryptionKey.KmsKeyName = v.(string)
}
if v, ok := d.GetOk(prefix + ".source_image_encryption_key.0.kms_key_service_account"); ok {
disk.InitializeParams.SourceImageEncryptionKey.KmsKeyServiceAccount = v.(string)
}
}

if v, ok := d.GetOk(prefix + ".source_snapshot"); ok {
disk.InitializeParams.SourceSnapshot = v.(string)
}

if _, ok := d.GetOk(prefix + ".source_snapshot_encryption_key"); ok {
disk.InitializeParams.SourceSnapshotEncryptionKey = &compute.CustomerEncryptionKey{}
if v, ok := d.GetOk(prefix + ".source_snapshot_encryption_key.0.kms_key_self_link"); ok {
disk.InitializeParams.SourceSnapshotEncryptionKey.KmsKeyName = v.(string)
}
if v, ok := d.GetOk(prefix + ".source_snapshot_encryption_key.0.kms_key_service_account"); ok {
disk.InitializeParams.SourceSnapshotEncryptionKey.KmsKeyServiceAccount = v.(string)
}
}

if _, ok := d.GetOk(prefix + ".resource_policies"); ok {
// instance template only supports a resource name here (not uri)
Expand Down Expand Up @@ -1100,8 +1192,14 @@ func diskCharacteristicsFromMap(m map[string]interface{}) diskCharacteristics {
return dc
}

func flattenDisk(disk *compute.AttachedDisk, defaultProject string) (map[string]interface{}, error) {
func flattenDisk(disk *compute.AttachedDisk, configDisk map[string]any, defaultProject string) (map[string]interface{}, error) {
diskMap := make(map[string]interface{})

// These values are not returned by the API, so we copy them from the config.
diskMap["source_image_encryption_key"] = configDisk["source_image_encryption_key"]
diskMap["source_snapshot"] = configDisk["source_snapshot"]
diskMap["source_snapshot_encryption_key"] = configDisk["source_snapshot_encryption_key"]

if disk.InitializeParams != nil {
if disk.InitializeParams.SourceImage != "" {
path, err := resolveImageRefToRelativeURI(defaultProject, disk.InitializeParams.SourceImage)
Expand Down Expand Up @@ -1266,11 +1364,12 @@ func flattenDisks(disks []*compute.AttachedDisk, d *schema.ResourceData, default
apiDisks := make([]map[string]interface{}, len(disks))

for i, disk := range disks {
d, err := flattenDisk(disk, defaultProject)
configDisk := d.Get(fmt.Sprintf("disk.%d", i)).(map[string]any)
apiDisk, err := flattenDisk(disk, configDisk, defaultProject)
if err != nil {
return nil, err
}
apiDisks[i] = d
apiDisks[i] = apiDisk
}

return reorderDisks(d.Get("disk").([]interface{}), apiDisks), nil
Expand Down
Loading

0 comments on commit ffc3213

Please sign in to comment.