diff --git a/builtin/providers/google/provider.go b/builtin/providers/google/provider.go index adf7275c2..5983fa1b3 100644 --- a/builtin/providers/google/provider.go +++ b/builtin/providers/google/provider.go @@ -61,6 +61,7 @@ func Provider() terraform.ResourceProvider { "google_compute_backend_bucket": resourceComputeBackendBucket(), "google_compute_backend_service": resourceComputeBackendService(), "google_compute_disk": resourceComputeDisk(), + "google_compute_snapshot": resourceComputeSnapshot(), "google_compute_firewall": resourceComputeFirewall(), "google_compute_forwarding_rule": resourceComputeForwardingRule(), "google_compute_global_address": resourceComputeGlobalAddress(), diff --git a/builtin/providers/google/resource_compute_snapshot.go b/builtin/providers/google/resource_compute_snapshot.go new file mode 100644 index 000000000..e482c86f9 --- /dev/null +++ b/builtin/providers/google/resource_compute_snapshot.go @@ -0,0 +1,210 @@ +package google + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" +) + +func resourceComputeSnapshot() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeSnapshotCreate, + Read: resourceComputeSnapshotRead, + Delete: resourceComputeSnapshotDelete, + Exists: resourceComputeSnapshotExists, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "zone": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "snapshot_encryption_key_raw": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Sensitive: true, + }, + + "snapshot_encryption_key_sha256": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "source_disk_encryption_key_raw": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Sensitive: true, + }, + + "source_disk_encryption_key_sha256": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "source_disk": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "source_disk_link": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "project": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "self_link": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceComputeSnapshotCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + // Build the snapshot parameter + snapshot := &compute.Snapshot{ + Name: d.Get("name").(string), + } + + source_disk := d.Get("source_disk").(string) + + if v, ok := d.GetOk("snapshot_encryption_key_raw"); ok { + snapshot.SnapshotEncryptionKey = &compute.CustomerEncryptionKey{} + snapshot.SnapshotEncryptionKey.RawKey = v.(string) + } + + if v, ok := d.GetOk("source_disk_encryption_key_raw"); ok { + snapshot.SourceDiskEncryptionKey = &compute.CustomerEncryptionKey{} + snapshot.SourceDiskEncryptionKey.RawKey = v.(string) + } + + op, err := config.clientCompute.Disks.CreateSnapshot( + project, d.Get("zone").(string), source_disk, snapshot).Do() + if err != nil { + return fmt.Errorf("Error creating snapshot: %s", err) + } + + // It probably maybe worked, so store the ID now + d.SetId(snapshot.Name) + + err = computeOperationWaitZone(config, op, project, d.Get("zone").(string), "Creating Snapshot") + if err != nil { + return err + } + return resourceComputeSnapshotRead(d, meta) +} + +func resourceComputeSnapshotRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + snapshot, err := config.clientCompute.Snapshots.Get( + project, d.Id()).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + log.Printf("[WARN] Removing Snapshot %q because it's gone", d.Get("name").(string)) + // The resource doesn't exist anymore + d.SetId("") + + return nil + } + + return fmt.Errorf("Error reading snapshot: %s", err) + } + + d.Set("self_link", snapshot.SelfLink) + d.Set("source_disk_link", snapshot.SourceDisk) + d.Set("name", snapshot.Name) + + if snapshot.SnapshotEncryptionKey != nil && snapshot.SnapshotEncryptionKey.Sha256 != "" { + d.Set("snapshot_encryption_key_sha256", snapshot.SnapshotEncryptionKey.Sha256) + } + + if snapshot.SourceDiskEncryptionKey != nil && snapshot.SourceDiskEncryptionKey.Sha256 != "" { + d.Set("source_disk_encryption_key_sha256", snapshot.SourceDiskEncryptionKey.Sha256) + } + + return nil +} + +func resourceComputeSnapshotDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + // Delete the snapshot + op, err := config.clientCompute.Snapshots.Delete( + project, d.Id()).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + log.Printf("[WARN] Removing Snapshot %q because it's gone", d.Get("name").(string)) + // The resource doesn't exist anymore + d.SetId("") + return nil + } + return fmt.Errorf("Error deleting snapshot: %s", err) + } + + err = computeOperationWaitGlobal(config, op, project, "Deleting Snapshot") + if err != nil { + return err + } + + d.SetId("") + return nil +} + +func resourceComputeSnapshotExists(d *schema.ResourceData, meta interface{}) (bool, error) { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return false, err + } + + _, err = config.clientCompute.Snapshots.Get( + project, d.Id()).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + log.Printf("[WARN] Removing Snapshot %q because it's gone", d.Get("name").(string)) + // The resource doesn't exist anymore + d.SetId("") + + return false, err + } + return true, err + } + return true, nil +} diff --git a/builtin/providers/google/resource_compute_snapshot_test.go b/builtin/providers/google/resource_compute_snapshot_test.go new file mode 100644 index 000000000..2a29f940d --- /dev/null +++ b/builtin/providers/google/resource_compute_snapshot_test.go @@ -0,0 +1,183 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" +) + +func TestAccComputeSnapshot_basic(t *testing.T) { + snapshotName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var snapshot compute.Snapshot + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSnapshotDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSnapshot_basic(snapshotName, diskName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSnapshotExists( + "google_compute_snapshot.foobar", &snapshot), + ), + }, + }, + }) +} + +func TestAccComputeSnapshot_encryption(t *testing.T) { + snapshotName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var snapshot compute.Snapshot + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSnapshotDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSnapshot_encryption(snapshotName, diskName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSnapshotExists( + "google_compute_snapshot.foobar", &snapshot), + ), + }, + }, + }) +} + +func testAccCheckComputeSnapshotDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_snapshot" { + continue + } + + _, err := config.clientCompute.Snapshots.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + return nil + } else if ok { + return fmt.Errorf("Error while requesting Google Cloud Plateform: http code error : %d, http message error: %s", gerr.Code, gerr.Message) + } + return fmt.Errorf("Error while requesting Google Cloud Plateform") + } + return fmt.Errorf("Snapshot still exists") + } + + return nil +} + +func testAccCheckComputeSnapshotExists(n string, snapshot *compute.Snapshot) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + found, err := config.clientCompute.Snapshots.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Snapshot %s not found", n) + } + + attr := rs.Primary.Attributes["snapshot_encryption_key_sha256"] + if found.SnapshotEncryptionKey != nil && found.SnapshotEncryptionKey.Sha256 != attr { + return fmt.Errorf("Snapshot %s has mismatched encryption key (Sha256).\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SnapshotEncryptionKey.Sha256) + } else if found.SnapshotEncryptionKey == nil && attr != "" { + return fmt.Errorf("Snapshot %s has mismatched encryption key.\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SnapshotEncryptionKey) + } + + attr = rs.Primary.Attributes["source_disk_encryption_key_sha256"] + if found.SourceDiskEncryptionKey != nil && found.SourceDiskEncryptionKey.Sha256 != attr { + return fmt.Errorf("Snapshot %s has mismatched source disk encryption key (Sha256).\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SourceDiskEncryptionKey.Sha256) + } else if found.SourceDiskEncryptionKey == nil && attr != "" { + return fmt.Errorf("Snapshot %s has mismatched source disk encryption key.\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SourceDiskEncryptionKey) + } + + attr = rs.Primary.Attributes["source_disk_link"] + if found.SourceDisk != attr { + return fmt.Errorf("Snapshot %s has mismatched source disk link.\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SourceDisk) + } + + foundDisk, errDisk := config.clientCompute.Disks.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.Attributes["source_disk"]).Do() + if errDisk != nil { + return errDisk + } + if foundDisk.SelfLink != attr { + return fmt.Errorf("Snapshot %s has mismatched source disk\nTF State: %+v.\nGCP State: %+v", + n, attr, foundDisk.SelfLink) + } + + attr = rs.Primary.Attributes["self_link"] + if found.SelfLink != attr { + return fmt.Errorf("Snapshot %s has mismatched self link.\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SelfLink) + } + + *snapshot = *found + + return nil + } +} + +func testAccComputeSnapshot_basic(snapshotName string, diskName string) string { + return fmt.Sprintf(` +resource "google_compute_disk" "foobar" { + name = "%s" + image = "debian-8-jessie-v20160921" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_snapshot" "foobar" { + name = "%s" + source_disk = "${google_compute_disk.foobar.name}" + zone = "us-central1-a" +}`, diskName, snapshotName) +} + +func testAccComputeSnapshot_encryption(snapshotName string, diskName string) string { + return fmt.Sprintf(` +resource "google_compute_disk" "foobar" { + name = "%s" + image = "debian-8-jessie-v20160921" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + disk_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" +} +resource "google_compute_snapshot" "foobar" { + name = "%s" + source_disk = "${google_compute_disk.foobar.name}" + zone = "us-central1-a" + source_disk_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" + snapshot_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" +}`, diskName, snapshotName) +} diff --git a/website/source/docs/providers/google/r/compute_snapshot.html.markdown b/website/source/docs/providers/google/r/compute_snapshot.html.markdown new file mode 100644 index 000000000..cdeb4fea9 --- /dev/null +++ b/website/source/docs/providers/google/r/compute_snapshot.html.markdown @@ -0,0 +1,66 @@ +--- +layout: "google" +page_title: "Google: google_compute_snapshot" +sidebar_current: "docs-google-compute-snapshot" +description: |- + Creates a new snapshot of a disk within GCE. +--- + +# google\_compute\_snapshot + +Creates a new snapshot of a disk within GCE. + +## Example Usage + +```js +resource "google_compute_snapshot" "default" { + name = "test-snapshot" + source_disk = "test-disk" + zone = "us-central1-a" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) A unique name for the resource, required by GCE. + Changing this forces a new resource to be created. + +* `zone` - (Required) The zone where the source disk is located. + +* `source_disk` - (Required) The disk which will be used as the source of the snapshot. + +- - - + +* `source_disk_encryption_key_raw` - (Optional) A 256-bit [customer-supplied encryption key] + (https://cloud.google.com/compute/docs/disks/customer-supplied-encryption), + encoded in [RFC 4648 base64](https://tools.ietf.org/html/rfc4648#section-4) + to decrypt the source disk. + +* `snapshot_encryption_key_raw` - (Optional) A 256-bit [customer-supplied encryption key] + (https://cloud.google.com/compute/docs/disks/customer-supplied-encryption), + encoded in [RFC 4648 base64](https://tools.ietf.org/html/rfc4648#section-4) + to encrypt this snapshot. + +* `project` - (Optional) The project in which the resource belongs. If it + is not provided, the provider project is used. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `snapshot_encryption_key_sha256` - The [RFC 4648 base64] + (https://tools.ietf.org/html/rfc4648#section-4) encoded SHA-256 hash of the + [customer-supplied encryption key](https://cloud.google.com/compute/docs/disks/customer-supplied-encryption) + that protects this resource. + +* `source_disk_encryption_key_sha256` - The [RFC 4648 base64] + (https://tools.ietf.org/html/rfc4648#section-4) encoded SHA-256 hash of the + [customer-supplied encryption key](https://cloud.google.com/compute/docs/disks/customer-supplied-encryption) + that protects the source disk. + +* `source_disk_link` - The URI of the source disk. + +* `self_link` - The URI of the created resource. diff --git a/website/source/layouts/google.erb b/website/source/layouts/google.erb index cbefd24f8..75b003cde 100644 --- a/website/source/layouts/google.erb +++ b/website/source/layouts/google.erb @@ -138,6 +138,10 @@ google_compute_route +