diff --git a/builtin/providers/opc/import_storage_volume_snapshot_test.go b/builtin/providers/opc/import_storage_volume_snapshot_test.go new file mode 100644 index 000000000..68654c13b --- /dev/null +++ b/builtin/providers/opc/import_storage_volume_snapshot_test.go @@ -0,0 +1,29 @@ +package opc + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccOPCStorageVolumeSnapshot_importBasic(t *testing.T) { + resourceName := "opc_compute_storage_volume_snapshot.test" + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: opcResourceCheck(resourceName, testAccCheckStorageVolumeSnapshotDestroyed), + Steps: []resource.TestStep{ + { + Config: testAccStorageVolumeSnapshot_basic(rInt), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/opc/provider.go b/builtin/providers/opc/provider.go index e1ac86dee..579d5ac7b 100644 --- a/builtin/providers/opc/provider.go +++ b/builtin/providers/opc/provider.go @@ -50,27 +50,28 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "opc_compute_ip_network": resourceOPCIPNetwork(), - "opc_compute_acl": resourceOPCACL(), - "opc_compute_image_list": resourceOPCImageList(), - "opc_compute_image_list_entry": resourceOPCImageListEntry(), - "opc_compute_instance": resourceInstance(), - "opc_compute_ip_address_reservation": resourceOPCIPAddressReservation(), - "opc_compute_ip_association": resourceOPCIPAssociation(), - "opc_compute_ip_network_exchange": resourceOPCIPNetworkExchange(), - "opc_compute_ip_reservation": resourceOPCIPReservation(), - "opc_compute_route": resourceOPCRoute(), - "opc_compute_security_application": resourceOPCSecurityApplication(), - "opc_compute_security_association": resourceOPCSecurityAssociation(), - "opc_compute_security_ip_list": resourceOPCSecurityIPList(), - "opc_compute_security_list": resourceOPCSecurityList(), - "opc_compute_security_rule": resourceOPCSecurityRule(), - "opc_compute_sec_rule": resourceOPCSecRule(), - "opc_compute_ssh_key": resourceOPCSSHKey(), - "opc_compute_storage_volume": resourceOPCStorageVolume(), - "opc_compute_vnic_set": resourceOPCVNICSet(), - "opc_compute_security_protocol": resourceOPCSecurityProtocol(), - "opc_compute_ip_address_prefix_set": resourceOPCIPAddressPrefixSet(), + "opc_compute_ip_network": resourceOPCIPNetwork(), + "opc_compute_acl": resourceOPCACL(), + "opc_compute_image_list": resourceOPCImageList(), + "opc_compute_image_list_entry": resourceOPCImageListEntry(), + "opc_compute_instance": resourceInstance(), + "opc_compute_ip_address_reservation": resourceOPCIPAddressReservation(), + "opc_compute_ip_association": resourceOPCIPAssociation(), + "opc_compute_ip_network_exchange": resourceOPCIPNetworkExchange(), + "opc_compute_ip_reservation": resourceOPCIPReservation(), + "opc_compute_route": resourceOPCRoute(), + "opc_compute_security_application": resourceOPCSecurityApplication(), + "opc_compute_security_association": resourceOPCSecurityAssociation(), + "opc_compute_security_ip_list": resourceOPCSecurityIPList(), + "opc_compute_security_list": resourceOPCSecurityList(), + "opc_compute_security_rule": resourceOPCSecurityRule(), + "opc_compute_sec_rule": resourceOPCSecRule(), + "opc_compute_ssh_key": resourceOPCSSHKey(), + "opc_compute_storage_volume": resourceOPCStorageVolume(), + "opc_compute_storage_volume_snapshot": resourceOPCStorageVolumeSnapshot(), + "opc_compute_vnic_set": resourceOPCVNICSet(), + "opc_compute_security_protocol": resourceOPCSecurityProtocol(), + "opc_compute_ip_address_prefix_set": resourceOPCIPAddressPrefixSet(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/opc/resource_storage_volume.go b/builtin/providers/opc/resource_storage_volume.go index 9d5a2b13d..28f3e7e05 100644 --- a/builtin/providers/opc/resource_storage_volume.go +++ b/builtin/providers/opc/resource_storage_volume.go @@ -42,6 +42,26 @@ func resourceOPCStorageVolume() *schema.Resource { }, true), }, + "snapshot": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "snapshot_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "snapshot_account": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "bootable": { Type: schema.TypeList, Optional: true, @@ -128,7 +148,19 @@ func resourceOPCStorageVolumeCreate(d *schema.ResourceData, meta interface{}) er Tags: getStringList(d, "tags"), } - expandOPCStorageVolumeOptionalFields(d, input) + expandOPCStorageVolumeOptionalFields(d, &input) + + if v, ok := d.GetOk("snapshot"); ok { + input.Snapshot = v.(string) + } + + if v, ok := d.GetOk("snapshot_account"); ok { + input.SnapshotAccount = v.(string) + } + + if v, ok := d.GetOk("snapshot_id"); ok { + input.SnapshotID = v.(string) + } info, err := client.CreateStorageVolume(&input) if err != nil { @@ -188,6 +220,9 @@ func resourceOPCStorageVolumeRead(d *schema.ResourceData, meta interface{}) erro d.Set("name", result.Name) d.Set("description", result.Description) d.Set("storage", result.Properties[0]) + d.Set("snapshot", result.Snapshot) + d.Set("snapshot_id", result.SnapshotID) + d.Set("snapshot_account", result.SnapshotAccount) size, err := strconv.Atoi(result.Size) if err != nil { return err @@ -220,11 +255,11 @@ func resourceOPCStorageVolumeDelete(d *schema.ResourceData, meta interface{}) er return nil } -func expandOPCStorageVolumeOptionalFields(d *schema.ResourceData, input compute.CreateStorageVolumeInput) { - value, exists := d.GetOk("bootable") - input.Bootable = exists - if exists { - configs := value.([]interface{}) +func expandOPCStorageVolumeOptionalFields(d *schema.ResourceData, input *compute.CreateStorageVolumeInput) { + bootValue, bootExists := d.GetOk("bootable") + input.Bootable = bootExists + if bootExists { + configs := bootValue.([]interface{}) config := configs[0].(map[string]interface{}) input.ImageList = config["image_list"].(string) diff --git a/builtin/providers/opc/resource_storage_volume_snapshot.go b/builtin/providers/opc/resource_storage_volume_snapshot.go new file mode 100644 index 000000000..4612c9f95 --- /dev/null +++ b/builtin/providers/opc/resource_storage_volume_snapshot.go @@ -0,0 +1,230 @@ +package opc + +import ( + "fmt" + "strconv" + + "github.com/hashicorp/go-oracle-terraform/compute" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceOPCStorageVolumeSnapshot() *schema.Resource { + return &schema.Resource{ + Create: resourceOPCStorageVolumeSnapshotCreate, + Read: resourceOPCStorageVolumeSnapshotRead, + Delete: resourceOPCStorageVolumeSnapshotDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + // Required Attributes + "volume_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + // Optional Attributes + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + // Optional, but also computed if unspecified + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "parent_volume_bootable": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Default: false, + }, + + "collocated": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + + "tags": tagsForceNewSchema(), + + // Computed Attributes + "account": { + Type: schema.TypeString, + Computed: true, + }, + + "machine_image_name": { + Type: schema.TypeString, + Computed: true, + }, + + "size": { + Type: schema.TypeString, + Computed: true, + }, + + "property": { + Type: schema.TypeString, + Computed: true, + }, + + "platform": { + Type: schema.TypeString, + Computed: true, + }, + + "snapshot_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + + "snapshot_id": { + Type: schema.TypeString, + Computed: true, + }, + + "start_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + + "status": { + Type: schema.TypeString, + Computed: true, + }, + + "status_detail": { + Type: schema.TypeString, + Computed: true, + }, + + "status_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + + "uri": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceOPCStorageVolumeSnapshotCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*compute.Client).StorageVolumeSnapshots() + + // Get required attribute + input := &compute.CreateStorageVolumeSnapshotInput{ + Volume: d.Get("volume_name").(string), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = v.(string) + } + + if v, ok := d.GetOk("name"); ok { + input.Name = v.(string) + } + + // Convert parent_volume_bootable to string + bootable := d.Get("parent_volume_bootable").(bool) + if bootable { + input.ParentVolumeBootable = "true" + } + + collocated := d.Get("collocated").(bool) + if collocated { + input.Property = compute.SnapshotPropertyCollocated + } + + tags := getStringList(d, "tags") + if len(tags) > 0 { + input.Tags = tags + } + + info, err := client.CreateStorageVolumeSnapshot(input) + if err != nil { + return fmt.Errorf("Error creating snapshot '%s': %v", input.Name, err) + } + + d.SetId(info.Name) + return resourceOPCStorageVolumeSnapshotRead(d, meta) +} + +func resourceOPCStorageVolumeSnapshotRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*compute.Client).StorageVolumeSnapshots() + + name := d.Id() + input := &compute.GetStorageVolumeSnapshotInput{ + Name: name, + } + + result, err := client.GetStorageVolumeSnapshot(input) + if err != nil { + if compute.WasNotFoundError(err) { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading storage volume snapshot '%s': %v", name, err) + } + + d.Set("volume_name", result.Volume) + d.Set("description", result.Description) + d.Set("name", result.Name) + d.Set("property", result.Property) + d.Set("platform", result.Platform) + d.Set("account", result.Account) + d.Set("machine_image_name", result.MachineImageName) + d.Set("size", result.Size) + d.Set("snapshot_timestamp", result.SnapshotTimestamp) + d.Set("snapshot_id", result.SnapshotID) + d.Set("start_timestamp", result.StartTimestamp) + d.Set("status", result.Status) + d.Set("status_detail", result.StatusDetail) + d.Set("status_timestamp", result.StatusTimestamp) + d.Set("uri", result.URI) + + bootable, err := strconv.ParseBool(result.ParentVolumeBootable) + if err != nil { + return fmt.Errorf("Error converting parent volume to boolean: %v", err) + } + d.Set("parent_volume_bootable", bootable) + + if result.Property != compute.SnapshotPropertyCollocated { + d.Set("collocated", false) + } else { + d.Set("collocated", true) + } + + if err := setStringList(d, "tags", result.Tags); err != nil { + return err + } + + return nil +} + +func resourceOPCStorageVolumeSnapshotDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*compute.Client).StorageVolumeSnapshots() + + name := d.Id() + + input := &compute.DeleteStorageVolumeSnapshotInput{ + Name: name, + } + + if err := client.DeleteStorageVolumeSnapshot(input); err != nil { + return fmt.Errorf("Error deleting storage volume snapshot '%s': %v", name, err) + } + + return nil +} diff --git a/builtin/providers/opc/resource_storage_volume_snapshot_test.go b/builtin/providers/opc/resource_storage_volume_snapshot_test.go new file mode 100644 index 000000000..9d4b08ee0 --- /dev/null +++ b/builtin/providers/opc/resource_storage_volume_snapshot_test.go @@ -0,0 +1,88 @@ +package opc + +import ( + "fmt" + "testing" + + "github.com/hashicorp/go-oracle-terraform/compute" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccOPCStorageVolumeSnapshot_basic(t *testing.T) { + snapshotName := "opc_compute_storage_volume_snapshot.test" + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: opcResourceCheck(snapshotName, testAccCheckStorageVolumeSnapshotDestroyed), + Steps: []resource.TestStep{ + { + Config: testAccStorageVolumeSnapshot_basic(rInt), + Check: resource.ComposeTestCheckFunc(opcResourceCheck(snapshotName, testAccCheckStorageVolumeSnapshotExists), + resource.TestCheckResourceAttr(snapshotName, "name", fmt.Sprintf("test-acc-stor-vol-%d", rInt)), + resource.TestCheckResourceAttr(snapshotName, "parent_volume_bootable", "false"), + resource.TestCheckResourceAttr(snapshotName, "collocated", "true"), + resource.TestCheckResourceAttr(snapshotName, "size", "5"), + ), + }, + }, + }) +} + +func testAccCheckStorageVolumeSnapshotExists(state *OPCResourceState) error { + client := state.Client.StorageVolumeSnapshots() + snapshotName := state.Attributes["name"] + + input := &compute.GetStorageVolumeSnapshotInput{ + Name: snapshotName, + } + + info, err := client.GetStorageVolumeSnapshot(input) + if err != nil { + return fmt.Errorf("Error retrieving state of snapshot '%s': %v", snapshotName, err) + } + + if info == nil { + return fmt.Errorf("No info found for snapshot '%s'", snapshotName) + } + + return nil +} + +func testAccCheckStorageVolumeSnapshotDestroyed(state *OPCResourceState) error { + client := state.Client.StorageVolumeSnapshots() + snapshotName := state.Attributes["name"] + + input := &compute.GetStorageVolumeSnapshotInput{ + Name: snapshotName, + } + info, err := client.GetStorageVolumeSnapshot(input) + if err != nil { + return fmt.Errorf("Error retrieving state of snapshot '%s': %v", snapshotName, err) + } + + if info != nil { + return fmt.Errorf("Snapshot '%s' still exists", snapshotName) + } + + return nil +} + +func testAccStorageVolumeSnapshot_basic(rInt int) string { + return fmt.Sprintf(` +resource "opc_compute_storage_volume" "foo" { + name = "test-acc-stor-vol-%d" + description = "testAccStorageVolumeSnapshot_basic" + size = 5 +} + +resource "opc_compute_storage_volume_snapshot" "test" { + name = "test-acc-stor-vol-%d" + description = "storage volume snapshot" + collocated = true + volume_name = "${opc_compute_storage_volume.foo.name}" +} +`, rInt, rInt) +} diff --git a/builtin/providers/opc/resource_storage_volume_test.go b/builtin/providers/opc/resource_storage_volume_test.go index 187e3c772..fc157ef94 100644 --- a/builtin/providers/opc/resource_storage_volume_test.go +++ b/builtin/providers/opc/resource_storage_volume_test.go @@ -116,6 +116,29 @@ func TestAccOPCStorageVolume_Bootable(t *testing.T) { }) } +func TestAccOPCStorageVolume_FromSnapshot(t *testing.T) { + volumeResourceName := "opc_compute_storage_volume.test" + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: opcResourceCheck(volumeResourceName, testAccCheckStorageVolumeDestroyed), + Steps: []resource.TestStep{ + { + Config: testAccStorageVolumeFromSnapshot(rInt), + Check: resource.ComposeTestCheckFunc( + opcResourceCheck(volumeResourceName, testAccCheckStorageVolumeExists), + resource.TestCheckResourceAttr(volumeResourceName, "name", fmt.Sprintf("test-acc-stor-vol-final-%d", rInt)), + resource.TestCheckResourceAttrSet(volumeResourceName, "snapshot"), + resource.TestCheckResourceAttrSet(volumeResourceName, "snapshot_id"), + resource.TestCheckResourceAttr(volumeResourceName, "size", "5"), + ), + }, + }, + }) +} + func testAccCheckStorageVolumeExists(state *OPCResourceState) error { sv := state.Client.StorageVolumes() volumeName := state.Attributes["name"] @@ -204,3 +227,29 @@ resource "opc_compute_storage_volume" "test" { size = 2048 } ` + +func testAccStorageVolumeFromSnapshot(rInt int) string { + return fmt.Sprintf(` +// Initial Storage Volume to create snapshot with +resource "opc_compute_storage_volume" "foo" { + name = "test-acc-stor-vol-%d" + description = "Acc Test intermediary storage volume for snapshot" + size = 5 +} + +resource "opc_compute_storage_volume_snapshot" "foo" { + description = "testing-acc" + name = "test-acc-stor-snapshot-%d" + collocated = true + volume_name = "${opc_compute_storage_volume.foo.name}" +} + +// Create storage volume from snapshot +resource "opc_compute_storage_volume" "test" { + name = "test-acc-stor-vol-final-%d" + description = "storage volume from snapshot" + size = 5 + snapshot_id = "${opc_compute_storage_volume_snapshot.foo.snapshot_id}" +} +`, rInt, rInt, rInt) +} diff --git a/vendor/github.com/hashicorp/go-oracle-terraform/compute/ip_address_associations.go b/vendor/github.com/hashicorp/go-oracle-terraform/compute/ip_address_associations.go new file mode 100644 index 000000000..335ada558 --- /dev/null +++ b/vendor/github.com/hashicorp/go-oracle-terraform/compute/ip_address_associations.go @@ -0,0 +1,152 @@ +package compute + +const ( + IPAddressAssociationDescription = "ip address association" + IPAddressAssociationContainerPath = "/network/v1/ipassociation/" + IPAddressAssociationResourcePath = "/network/v1/ipassociation" +) + +type IPAddressAssociationsClient struct { + ResourceClient +} + +// IPAddressAssociations() returns an IPAddressAssociationsClient that can be used to access the +// necessary CRUD functions for IP Address Associations. +func (c *Client) IPAddressAssociations() *IPAddressAssociationsClient { + return &IPAddressAssociationsClient{ + ResourceClient: ResourceClient{ + Client: c, + ResourceDescription: IPAddressAssociationDescription, + ContainerPath: IPAddressAssociationContainerPath, + ResourceRootPath: IPAddressAssociationResourcePath, + }, + } +} + +// IPAddressAssociationInfo contains the exported fields necessary to hold all the information about an +// IP Address Association +type IPAddressAssociationInfo struct { + // The name of the NAT IP address reservation. + IPAddressReservation string `json:"ipAddressReservation"` + // Name of the virtual NIC associated with this NAT IP reservation. + Vnic string `json:"vnic"` + // The name of the IP Address Association + Name string `json:"name"` + // Description of the IP Address Association + Description string `json:"description"` + // Slice of tags associated with the IP Address Association + Tags []string `json:"tags"` + // Uniform Resource Identifier for the IP Address Association + Uri string `json:"uri"` +} + +type CreateIPAddressAssociationInput struct { + // The name of the IP Address Association to create. Object names can only contain alphanumeric, + // underscore, dash, and period characters. Names are case-sensitive. + // Required + Name string `json:"name"` + + // The name of the NAT IP address reservation. + // Optional + IPAddressReservation string `json:"ipAddressReservation,omitempty"` + + // Name of the virtual NIC associated with this NAT IP reservation. + // Optional + Vnic string `json:"vnic,omitempty"` + + // Description of the IPAddressAssociation + // Optional + Description string `json:"description"` + + // String slice of tags to apply to the IP Address Association object + // Optional + Tags []string `json:"tags"` +} + +// Create a new IP Address Association from an IPAddressAssociationsClient and an input struct. +// Returns a populated Info struct for the IP Address Association, and any errors +func (c *IPAddressAssociationsClient) CreateIPAddressAssociation(input *CreateIPAddressAssociationInput) (*IPAddressAssociationInfo, error) { + input.Name = c.getQualifiedName(input.Name) + input.IPAddressReservation = c.getQualifiedName(input.IPAddressReservation) + input.Vnic = c.getQualifiedName(input.Vnic) + + var ipInfo IPAddressAssociationInfo + if err := c.createResource(&input, &ipInfo); err != nil { + return nil, err + } + + return c.success(&ipInfo) +} + +type GetIPAddressAssociationInput struct { + // The name of the IP Address Association to query for. Case-sensitive + // Required + Name string `json:"name"` +} + +// Returns a populated IPAddressAssociationInfo struct from an input struct +func (c *IPAddressAssociationsClient) GetIPAddressAssociation(input *GetIPAddressAssociationInput) (*IPAddressAssociationInfo, error) { + input.Name = c.getQualifiedName(input.Name) + + var ipInfo IPAddressAssociationInfo + if err := c.getResource(input.Name, &ipInfo); err != nil { + return nil, err + } + + return c.success(&ipInfo) +} + +// UpdateIPAddressAssociationInput defines what to update in a ip address association +type UpdateIPAddressAssociationInput struct { + // The name of the IP Address Association to create. Object names can only contain alphanumeric, + // underscore, dash, and period characters. Names are case-sensitive. + // Required + Name string `json:"name"` + + // The name of the NAT IP address reservation. + // Optional + IPAddressReservation string `json:"ipAddressReservation,omitempty"` + + // Name of the virtual NIC associated with this NAT IP reservation. + // Optional + Vnic string `json:"vnic,omitempty"` + + // Description of the IPAddressAssociation + // Optional + Description string `json:"description"` + + // String slice of tags to apply to the IP Address Association object + // Optional + Tags []string `json:"tags"` +} + +// UpdateIPAddressAssociation update the ip address association +func (c *IPAddressAssociationsClient) UpdateIPAddressAssociation(updateInput *UpdateIPAddressAssociationInput) (*IPAddressAssociationInfo, error) { + updateInput.Name = c.getQualifiedName(updateInput.Name) + updateInput.IPAddressReservation = c.getQualifiedName(updateInput.IPAddressReservation) + updateInput.Vnic = c.getQualifiedName(updateInput.Vnic) + var ipInfo IPAddressAssociationInfo + if err := c.updateResource(updateInput.Name, updateInput, &ipInfo); err != nil { + return nil, err + } + + return c.success(&ipInfo) +} + +type DeleteIPAddressAssociationInput struct { + // The name of the IP Address Association to query for. Case-sensitive + // Required + Name string `json:"name"` +} + +func (c *IPAddressAssociationsClient) DeleteIPAddressAssociation(input *DeleteIPAddressAssociationInput) error { + return c.deleteResource(input.Name) +} + +// Unqualifies any qualified fields in the IPAddressAssociationInfo struct +func (c *IPAddressAssociationsClient) success(info *IPAddressAssociationInfo) (*IPAddressAssociationInfo, error) { + c.unqualify(&info.Name) + c.unqualify(&info.Vnic) + c.unqualify(&info.IPAddressReservation) + return info, nil +} diff --git a/vendor/github.com/hashicorp/go-oracle-terraform/compute/storage_volume_snapshots.go b/vendor/github.com/hashicorp/go-oracle-terraform/compute/storage_volume_snapshots.go new file mode 100644 index 000000000..f958d2107 --- /dev/null +++ b/vendor/github.com/hashicorp/go-oracle-terraform/compute/storage_volume_snapshots.go @@ -0,0 +1,250 @@ +package compute + +import ( + "fmt" + "strings" +) + +const ( + StorageVolumeSnapshotDescription = "storage volume snapshot" + StorageVolumeSnapshotContainerPath = "/storage/snapshot/" + StorageVolumeSnapshotResourcePath = "/storage/snapshot" + + WaitForSnapshotCreateTimeout = 1200 + WaitForSnapshotDeleteTimeout = 1500 + + // Collocated Snapshot Property + SnapshotPropertyCollocated = "/oracle/private/storage/snapshot/collocated" +) + +// StorageVolumeSnapshotClient is a client for the Storage Volume Snapshot functions of the Compute API. +type StorageVolumeSnapshotClient struct { + ResourceClient +} + +func (c *Client) StorageVolumeSnapshots() *StorageVolumeSnapshotClient { + return &StorageVolumeSnapshotClient{ + ResourceClient: ResourceClient{ + Client: c, + ResourceDescription: StorageVolumeSnapshotDescription, + ContainerPath: StorageVolumeSnapshotContainerPath, + ResourceRootPath: StorageVolumeSnapshotResourcePath, + }, + } +} + +// StorageVolumeSnapshotInfo represents the information retrieved from the service about a storage volume snapshot +type StorageVolumeSnapshotInfo struct { + // Account to use for snapshots + Account string `json:"account"` + + // Description of the snapshot + Description string `json:"description"` + + // The name of the machine image that's used in the boot volume from which this snapshot is taken + MachineImageName string `json:"machineimage_name"` + + // Name of the snapshot + Name string `json:"name"` + + // String indicating whether the parent volume is bootable or not + ParentVolumeBootable string `json:"parent_volume_bootable"` + + // Platform the snapshot is compatible with + Platform string `json:"platform"` + + // String determining whether the snapshot is remote or collocated + Property string `json:"property"` + + // The size of the snapshot in GB + Size string `json:"size"` + + // The ID of the snapshot. Generated by the server + SnapshotID string `json:"snapshot_id"` + + // The timestamp of the storage snapshot + SnapshotTimestamp string `json:"snapshot_timestamp"` + + // Timestamp for when the operation started + StartTimestamp string `json:"start_timestamp"` + + // Status of the snapshot + Status string `json:"status"` + + // Status Detail of the storage snapshot + StatusDetail string `json:"status_detail"` + + // Indicates the time that the current view of the storage volume snapshot was generated. + StatusTimestamp string `json:"status_timestamp"` + + // Array of tags for the snapshot + Tags []string `json:"tags,omitempty"` + + // Uniform Resource Identifier + URI string `json:"uri"` + + // Name of the parent storage volume for the snapshot + Volume string `json:"volume"` +} + +// CreateStorageVolumeSnapshotInput represents the body of an API request to create a new storage volume snapshot +type CreateStorageVolumeSnapshotInput struct { + // Description of the snapshot + // Optional + Description string `json:"description,omitempty"` + + // Name of the snapshot + // Optional, will be generated if not specified + Name string `json:"name,omitempty"` + + // Whether or not the parent volume is bootable + // Optional + ParentVolumeBootable string `json:"parent_volume_bootable,omitempty"` + + // Whether collocated or remote + // Optional, will be remote if unspecified + Property string `json:"property,omitempty"` + + // Array of tags for the snapshot + // Optional + Tags []string `json:"tags,omitempty"` + + // Name of the volume to create the snapshot from + // Required + Volume string `json:"volume"` + + // Timeout (in seconds) to wait for snapshot to be completed. Will use default if unspecified + Timeout int +} + +// CreateStorageVolumeSnapshot creates a snapshot based on the supplied information struct +func (c *StorageVolumeSnapshotClient) CreateStorageVolumeSnapshot(input *CreateStorageVolumeSnapshotInput) (*StorageVolumeSnapshotInfo, error) { + if input.Name != "" { + input.Name = c.getQualifiedName(input.Name) + } + input.Volume = c.getQualifiedName(input.Volume) + + var storageSnapshotInfo StorageVolumeSnapshotInfo + if err := c.createResource(&input, &storageSnapshotInfo); err != nil { + return nil, err + } + + timeout := WaitForSnapshotCreateTimeout + if input.Timeout != 0 { + timeout = input.Timeout + } + + // The name of the snapshot could have been generated. Use the response name as input + return c.waitForStorageSnapshotAvailable(storageSnapshotInfo.Name, timeout) +} + +// GetStorageVolumeSnapshotInput represents the body of an API request to get information on a storage volume snapshot +type GetStorageVolumeSnapshotInput struct { + // Name of the snapshot + Name string `json:"name"` +} + +// GetStorageVolumeSnapshot makes an API request to populate information on a storage volume snapshot +func (c *StorageVolumeSnapshotClient) GetStorageVolumeSnapshot(input *GetStorageVolumeSnapshotInput) (*StorageVolumeSnapshotInfo, error) { + var storageSnapshot StorageVolumeSnapshotInfo + input.Name = c.getQualifiedName(input.Name) + if err := c.getResource(input.Name, &storageSnapshot); err != nil { + if WasNotFoundError(err) { + return nil, nil + } + + return nil, err + } + return c.success(&storageSnapshot) +} + +// DeleteStorageVolumeSnapshotInput represents the body of an API request to delete a storage volume snapshot +type DeleteStorageVolumeSnapshotInput struct { + // Name of the snapshot to delete + Name string `json:"name"` + + // Timeout in seconds to wait for deletion, will use default if unspecified + Timeout int +} + +// DeleteStoragevolumeSnapshot makes an API request to delete a storage volume snapshot +func (c *StorageVolumeSnapshotClient) DeleteStorageVolumeSnapshot(input *DeleteStorageVolumeSnapshotInput) error { + input.Name = c.getQualifiedName(input.Name) + + if err := c.deleteResource(input.Name); err != nil { + return err + } + + timeout := WaitForSnapshotDeleteTimeout + if input.Timeout != 0 { + timeout = input.Timeout + } + + return c.waitForStorageSnapshotDeleted(input.Name, timeout) +} + +func (c *StorageVolumeSnapshotClient) success(result *StorageVolumeSnapshotInfo) (*StorageVolumeSnapshotInfo, error) { + c.unqualify(&result.Name) + c.unqualify(&result.Volume) + + sizeInGigaBytes, err := sizeInGigaBytes(result.Size) + if err != nil { + return nil, err + } + result.Size = sizeInGigaBytes + + return result, nil +} + +// Waits for a storage snapshot to become available +func (c *StorageVolumeSnapshotClient) waitForStorageSnapshotAvailable(name string, timeout int) (*StorageVolumeSnapshotInfo, error) { + var result *StorageVolumeSnapshotInfo + + err := c.waitFor( + fmt.Sprintf("storage volume snapshot %s to become available", c.getQualifiedName(name)), + timeout, + func() (bool, error) { + req := &GetStorageVolumeSnapshotInput{ + Name: name, + } + res, err := c.GetStorageVolumeSnapshot(req) + if err != nil { + return false, err + } + + if res != nil { + result = res + if strings.ToLower(result.Status) == "completed" { + return true, nil + } else if strings.ToLower(result.Status) == "error" { + return false, fmt.Errorf("Snapshot '%s' failed to create successfully. Status: %s Status Detail: %s", result.Name, result.Status, result.StatusDetail) + } + } + + return false, nil + }) + + return result, err +} + +// Waits for a storage snapshot to be deleted +func (c *StorageVolumeSnapshotClient) waitForStorageSnapshotDeleted(name string, timeout int) error { + return c.waitFor( + fmt.Sprintf("storage volume snapshot %s to be deleted", c.getQualifiedName(name)), + timeout, + func() (bool, error) { + req := &GetStorageVolumeSnapshotInput{ + Name: name, + } + res, err := c.GetStorageVolumeSnapshot(req) + if res == nil { + return true, nil + } + + if err != nil { + return false, err + } + + return res == nil, nil + }) +} diff --git a/vendor/github.com/hashicorp/go-oracle-terraform/compute/storage_volumes.go b/vendor/github.com/hashicorp/go-oracle-terraform/compute/storage_volumes.go index c40c354a9..7193a6c24 100644 --- a/vendor/github.com/hashicorp/go-oracle-terraform/compute/storage_volumes.go +++ b/vendor/github.com/hashicorp/go-oracle-terraform/compute/storage_volumes.go @@ -188,6 +188,7 @@ type GetStorageVolumeInput struct { func (c *StorageVolumeClient) success(result *StorageVolumeInfo) (*StorageVolumeInfo, error) { c.unqualify(&result.Name) + c.unqualify(&result.Snapshot) sizeInMegaBytes, err := sizeInGigaBytes(result.Size) if err != nil { diff --git a/vendor/vendor.json b/vendor/vendor.json index 63c77ed33..0f8bd50ef 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1970,10 +1970,10 @@ "revision": "d30f09973e19c1dfcd120b2d9c4f168e68d6b5d5" }, { - "checksumSHA1": "QKusHEboSl00AnORqkjv0gZEhqw=", + "checksumSHA1": "mVLpbxsm+8TlXKgkezrh3c5I7+4=", "path": "github.com/hashicorp/go-oracle-terraform/compute", - "revision": "15f277fb824b7af18c6bef8d30d84174154f989b", - "revisionTime": "2017-04-05T20:02:51Z" + "revision": "381402af3554bcca5fa7eeda94d47003e6ba7ee7", + "revisionTime": "2017-04-06T04:33:22Z" }, { "checksumSHA1": "DzK7lYwHt5Isq5Zf73cnQqBO2LI=", diff --git a/website/source/docs/providers/opc/r/opc_compute_storage_volume.html.markdown b/website/source/docs/providers/opc/r/opc_compute_storage_volume.html.markdown index 7fa308ff5..c4a682f41 100644 --- a/website/source/docs/providers/opc/r/opc_compute_storage_volume.html.markdown +++ b/website/source/docs/providers/opc/r/opc_compute_storage_volume.html.markdown @@ -51,6 +51,9 @@ The following arguments are supported: * `storage_type` - (Optional) - The Type of Storage to provision. Possible values are `/oracle/public/storage/latency` or `/oracle/public/storage/default`. Defaults to `/oracle/public/storage/default`. * `bootable` - (Optional) A `bootable` block as defined below. * `tags` - (Optional) Comma-separated strings that tag the storage volume. +* `snapshot` - (Optional) Name of the storage volume snapshot if this storage volume is a clone. +* `snapshot_account` - (Optional) Account of the parent snapshot from which the storage volume is restored. +* `snapshot_id` - (Optional) Id of the parent snapshot from which the storage volume is restored or cloned. `bootable` supports the following: * `image_list` - (Required) Defines an image list. diff --git a/website/source/docs/providers/opc/r/opc_compute_storage_volume_snapshot.html.markdown b/website/source/docs/providers/opc/r/opc_compute_storage_volume_snapshot.html.markdown new file mode 100644 index 000000000..13ba265a5 --- /dev/null +++ b/website/source/docs/providers/opc/r/opc_compute_storage_volume_snapshot.html.markdown @@ -0,0 +1,59 @@ +--- +layout: "opc" +page_title: "Oracle: opc_compute_storage_volume_snapshot" +sidebar_current: "docs-opc-resource-storage-volume-snapshot" +description: |- + Creates and manages a storage volume snapshot in an OPC identity domain. +--- + +# opc\_compute\_storage\_volume_snapshot + +The ``opc_compute_storage_volume_snapshot`` resource creates and manages a storage volume snapshot in an OPC identity domain. + +## Example Usage + +``` +resource "opc_compute_storage_volume_snapshot" "test" { + name = "storageVolume1" + description = "Description for the Storage Volume" + tags = ["bar", "foo"] + collocated = true + volume_name = "${opc_compute_storage_volume.foo.name}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `volume_name` (Required) The name of the storage volume to create the snapshot from. +* `description` (Optional) The description of the storage volume snapshot. +* `name` (Optional) The name of the storage volume snapshot. Will be generated if unspecified. +* `parent_volume_bootable` (Optional) A string value of whether or not the parent volume is 'bootable' or not. Defaults to `"false"`. +* `collocated` (Optional) Boolean specifying whether the snapshot is collocated or remote. Defaults to `false`. +* `tags` - (Optional) Comma-separated strings that tag the storage volume. + +## Attributes Reference + +In addition to the attributes above, the following attributes are exported: + +* `account` - Account to use for snapshots. +* `machine_image_name` - The name of the machine image that's used in the boot volume from which this snapshot is taken. +* `size` - The size of the snapshot in GB. +* `property` - Where the snapshot is stored, whether collocated, or in the Oracle Storage Cloud Service instance. +* `platform` - The OS platform this snapshot is compatible with +* `snapshot_timestamp` - Timestamp of the storage snapshot, generated by storage server. The snapshot will contain data written to the original volume before this time. +* `snapshot_id` - The Oracle ID of the snapshot. +* `start_timestamp` - Timestamp when the snapshot was started. +* `status` - Status of the snapshot. +* `status_detail` - Details about the latest state of the storage volume snapshot. +* `status_timestamp` - Indicates the time that the current view of the storage volume snapshot was generated. +* `uri` - Uniform Resource Identifier + +## Import + +Storage Volume Snapshot's can be imported using the `resource name`, e.g. + +``` +terraform import opc_compute_storage_volume_snapshot.volume1 example +``` diff --git a/website/source/layouts/opc.erb b/website/source/layouts/opc.erb index f27a27e8b..0bc3e3ab4 100644 --- a/website/source/layouts/opc.erb +++ b/website/source/layouts/opc.erb @@ -85,6 +85,9 @@ > opc_compute_storage_volume + > + opc_compute_storage_volume_snapshot + > opc_compute_vnic_set