From bddd75c2611a86a00de16689cdd8ed9ea367ffe9 Mon Sep 17 00:00:00 2001 From: Ujjwal Kumar Date: Mon, 12 Jul 2021 12:58:01 +0530 Subject: [PATCH] added provider changes for snapshots and reflecting changes on instance and volumes --- examples/ibm-is-ng/main.tf | 129 ++++- ...ource_ibm_is_instance_volume_attachment.go | 168 ++++++ ..._ibm_is_instance_volume_attachment_test.go | 83 +++ ...urce_ibm_is_instance_volume_attachments.go | 167 ++++++ ...ibm_is_instance_volume_attachments_test.go | 81 +++ ibm/data_source_ibm_is_snapshot.go | 218 ++++++++ ibm/data_source_ibm_is_snapshot_test.go | 91 +++ ibm/data_source_ibm_is_snapshots.go | 182 ++++++ ibm/data_source_ibm_is_snapshots_test.go | 58 ++ ibm/data_source_ibm_is_volume.go | 9 + ibm/provider.go | 9 + ibm/resource_ibm_is_instance.go | 516 +++++++++++++++-- ibm/resource_ibm_is_instance_test.go | 112 +++- ...ource_ibm_is_instance_volume_attachment.go | 528 ++++++++++++++++++ ..._ibm_is_instance_volume_attachment_test.go | 142 +++++ ibm/resource_ibm_is_snapshot.go | 477 ++++++++++++++++ ibm/resource_ibm_is_snapshot_test.go | 178 ++++++ ibm/resource_ibm_is_volume.go | 38 +- ...s_instance_volume_attachment.html.markdown | 48 ++ ..._instance_volume_attachments.html.markdown | 48 ++ website/docs/d/is_snapshot.html.markdown | 85 +++ website/docs/d/is_snapshots.html.markdown | 39 ++ website/docs/d/is_volume.html.markdown | 1 + website/docs/r/is_instance.html.markdown | 52 +- ...s_instance_volume_attachment.html.markdown | 162 ++++++ website/docs/r/is_snapshot.html.markdown | 92 +++ website/docs/r/is_volume.html.markdown | 4 +- 27 files changed, 3641 insertions(+), 76 deletions(-) create mode 100644 ibm/data_source_ibm_is_instance_volume_attachment.go create mode 100644 ibm/data_source_ibm_is_instance_volume_attachment_test.go create mode 100644 ibm/data_source_ibm_is_instance_volume_attachments.go create mode 100644 ibm/data_source_ibm_is_instance_volume_attachments_test.go create mode 100644 ibm/data_source_ibm_is_snapshot.go create mode 100644 ibm/data_source_ibm_is_snapshot_test.go create mode 100644 ibm/data_source_ibm_is_snapshots.go create mode 100644 ibm/data_source_ibm_is_snapshots_test.go create mode 100644 ibm/resource_ibm_is_instance_volume_attachment.go create mode 100644 ibm/resource_ibm_is_instance_volume_attachment_test.go create mode 100644 ibm/resource_ibm_is_snapshot.go create mode 100644 ibm/resource_ibm_is_snapshot_test.go create mode 100644 website/docs/d/is_instance_volume_attachment.html.markdown create mode 100644 website/docs/d/is_instance_volume_attachments.html.markdown create mode 100644 website/docs/d/is_snapshot.html.markdown create mode 100644 website/docs/d/is_snapshots.html.markdown create mode 100644 website/docs/r/is_instance_volume_attachment.html.markdown create mode 100644 website/docs/r/is_snapshot.html.markdown diff --git a/examples/ibm-is-ng/main.tf b/examples/ibm-is-ng/main.tf index 27240209995..3abfb947916 100644 --- a/examples/ibm-is-ng/main.tf +++ b/examples/ibm-is-ng/main.tf @@ -36,15 +36,15 @@ resource "ibm_is_instance_template" "instancetemplate1" { name = "testbootvol" delete_volume_on_instance_delete = true } - volume_attachments { - delete_volume_on_instance_delete = true - name = "volatt-01" - volume_prototype { - iops = 3000 - profile = "general-purpose" - capacity = 200 - } - } + volume_attachments { + delete_volume_on_instance_delete = true + name = "volatt-01" + volume_prototype { + iops = 3000 + profile = "general-purpose" + capacity = 200 + } + } } resource "ibm_is_instance_template" "instancetemplate2" { @@ -432,15 +432,120 @@ data "ibm_is_dedicated_host" "dhost" { host_group = data.ibm_is_dedicated_host_group.dgroup.id } +resource "ibm_is_volume" "vol3" { + name = "vol3" + profile = "10iops-tier" + zone = var.zone1 +} + +// creating an instance with volumes +resource "ibm_is_instance" "instance4" { + name = "instance4" + image = var.image + profile = var.profile + + volumes = [ ibm_is_volume.vol3.id ] + + primary_network_interface { + subnet = ibm_is_subnet.subnet1.id + } + + vpc = ibm_is_vpc.vpc1.id + zone = var.zone1 + keys = [ibm_is_ssh_key.sshkey.id] +} + +// creating a snapshot from boot volume +resource "ibm_is_snapshot" "b_snapshot" { + name = "my-snapshot-boot" + source_volume = ibm_is_instance.instance4.volume_attachments[0].volume_id +} + +// creating a snapshot from data volume +resource "ibm_is_snapshot" "d_snapshot" { + name = "my-snapshot-data" + source_volume = ibm_is_instance.instance4.volume_attachments[1].volume_id +} + +// data source for snapshot by name +data "ibm_is_snapshot" "ds_snapshot" { + name = "my-snapshot-boot" +} + +// data source for snapshots +data "ibm_is_snapshots" "ds_snapshots" { +} + +// restoring a boot volume from snapshot in a new instance +resource "ibm_is_instance" "instance5" { + name = "instance5" + profile = var.profile + boot_volume { + name = "boot-restore" + snapshot = ibm_is_snapshot.b_snapshot.id + } + auto_delete_volume = true + primary_network_interface { + subnet = ibm_is_subnet.subnet2.id + } + vpc = ibm_is_vpc.vpc2.id + zone = "us-south-2" + keys = [ibm_is_ssh_key.sshkey.id] +} + +// creating a volume +resource "ibm_is_volume" "vol5" { + name = "vol5" + profile = "10iops-tier" + zone = "us-south-2" +} + +// creating a volume attachment on an existing instance using an existing volume +resource "ibm_is_instance_volume_attachment" "att1" { + instance = ibm_is_instance.instance5.id + volume = ibm_is_volume.vol5.id + name = "vol-att-1" + delete_volume_on_attachment_delete = false + delete_volume_on_instance_delete = false +} + +// creating a volume attachment on an existing instance using a new volume +resource "ibm_is_instance_volume_attachment" "att2" { + instance = ibm_is_instance.instance5.id + name = "vol-att-2" + profile = "general-purpose" + snapshot = ibm_is_snapshot.d_snapshot.id + delete_volume_on_instance_delete = true + delete_volume_on_attachment_delete = true + volume_name = "vol4-restore" +} + +// data source for volume attachment +data "ibm_is_instance_volume_attachment" "ds_vol_att" { + instance = ibm_is_instance.instance5.id + name = ibm_is_instance_volume_attachment.att2.name +} + +// data source for volume attachments +data "ibm_is_instance_volume_attachment" "ds_vol_atts" { + instance = ibm_is_instance.instance5.id +} + +// creating an instance using an existing instance template +resource "ibm_is_instance" "instance6" { + name = "instance4" + instance_template = ibm_is_instance_template.instancetemplate1.id +} + resource "ibm_is_image" "image1" { - href = var.image_cos_url - name = "my-img-1" + href = var.image_cos_url + name = "my-img-1" operating_system = var.image_operating_system } resource "ibm_is_image" "image2" { source_volume = data.ibm_is_instance.instance1.volume_attachments.0.volume_id - name = "my-img-1" + name = "my-img-1" } data "ibm_is_image" "dsimage" { diff --git a/ibm/data_source_ibm_is_instance_volume_attachment.go b/ibm/data_source_ibm_is_instance_volume_attachment.go new file mode 100644 index 00000000000..22b4431195d --- /dev/null +++ b/ibm/data_source_ibm_is_instance_volume_attachment.go @@ -0,0 +1,168 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + + "github.com/IBM/vpc-go-sdk/vpcv1" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + isInstanceVolumeAttDevice = "device" + isInstanceVolumeAttHref = "href" + isInstanceVolumeAttStatus = "status" + isInstanceVolumeAttType = "type" + isInstanceVolumeAttVolumeReference = "volume_reference" + isInstanceVolumeAttVolumeReferenceCrn = "volume_crn" + isInstanceVolumeAttVolumeReferenceDeleted = "volume_deleted" + isInstanceVolumeAttVolumeReferenceHref = "volume_href" + isInstanceVolumeAttVolumeReferenceId = "volume_id" + isInstanceVolumeAttVolumeReferenceName = "volume_name" +) + +func dataSourceIBMISInstanceVolumeAttachment() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIBMISInstanceVolumeAttachmentRead, + + Schema: map[string]*schema.Schema{ + + isInstanceId: { + Type: schema.TypeString, + Required: true, + Description: "Instance id", + }, + isInstanceVolAttName: { + Type: schema.TypeString, + Required: true, + Description: "The user-defined name for this volume attachment.", + }, + + isInstanceVolumeDeleteOnInstanceDelete: { + Type: schema.TypeBool, + Computed: true, + Description: "If set to true, when deleting the instance the volume will also be deleted.", + }, + + isInstanceVolumeAttDevice: { + Type: schema.TypeString, + Computed: true, + Description: "A unique identifier for the device which is exposed to the instance operating system", + }, + + isInstanceVolumeAttHref: { + Type: schema.TypeString, + Computed: true, + Description: "The URL for this volume attachment", + }, + + isInstanceVolAttId: { + Type: schema.TypeString, + Computed: true, + Description: "The unique identifier for this volume attachment", + }, + + isInstanceVolumeAttStatus: { + Type: schema.TypeString, + Computed: true, + Description: "The status of this volume attachment, one of [ attached, attaching, deleting, detaching ]", + }, + + isInstanceVolumeAttType: { + Type: schema.TypeString, + Computed: true, + Description: "The type of volume attachment one of [ boot, data ]", + }, + + isInstanceVolumeAttVolumeReference: { + Type: schema.TypeList, + Computed: true, + Description: "The attached volume", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + isInstanceVolumeAttVolumeReferenceId: { + Type: schema.TypeString, + Computed: true, + Description: "The unique identifier for this volume", + }, + isInstanceVolumeAttVolumeReferenceName: { + Type: schema.TypeString, + Computed: true, + Description: "The unique user-defined name for this volume", + }, + isInstanceVolumeAttVolumeReferenceCrn: { + Type: schema.TypeString, + Computed: true, + Description: "The CRN for this volume", + }, + isInstanceVolumeAttVolumeReferenceDeleted: { + Type: schema.TypeString, + Computed: true, + Description: "Link to documentation about deleted resources", + }, + isInstanceVolumeAttVolumeReferenceHref: { + Type: schema.TypeString, + Computed: true, + Description: "The URL for this volume", + }, + }, + }, + }, + }, + } +} + +func dataSourceIBMISInstanceVolumeAttachmentRead(d *schema.ResourceData, meta interface{}) error { + instanceId := d.Get(isInstanceId).(string) + name := d.Get(isInstanceName).(string) + err := instanceVolumeAttachmentGetByName(d, meta, instanceId, name) + if err != nil { + return err + } + return nil +} + +func instanceVolumeAttachmentGetByName(d *schema.ResourceData, meta interface{}, instanceId, name string) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + allrecs := []vpcv1.VolumeAttachment{} + listInstanceVolumeAttOptions := &vpcv1.ListInstanceVolumeAttachmentsOptions{ + InstanceID: &instanceId, + } + volumeAtts, response, err := sess.ListInstanceVolumeAttachments(listInstanceVolumeAttOptions) + if err != nil { + return fmt.Errorf("Error fetching Instance volume attachments %s\n%s", err, response) + } + allrecs = append(allrecs, volumeAtts.VolumeAttachments...) + for _, volumeAtt := range allrecs { + if *volumeAtt.Name == name { + d.SetId(makeTerraformVolAttID(instanceId, *volumeAtt.ID)) + d.Set(isInstanceVolAttName, *volumeAtt.Name) + d.Set(isInstanceVolumeDeleteOnInstanceDelete, *volumeAtt.DeleteVolumeOnInstanceDelete) + d.Set(isInstanceVolumeAttDevice, *volumeAtt.Device.ID) + d.Set(isInstanceVolumeAttHref, *volumeAtt.Href) + d.Set(isInstanceVolAttId, *volumeAtt.ID) + d.Set(isInstanceVolumeAttStatus, *volumeAtt.Status) + d.Set(isInstanceVolumeAttType, *volumeAtt.Type) + volList := make([]map[string]interface{}, 0) + if volumeAtt.Volume != nil { + currentVol := map[string]interface{}{} + currentVol[isInstanceVolumeAttVolumeReferenceId] = *volumeAtt.Volume.ID + currentVol[isInstanceVolumeAttVolumeReferenceName] = *volumeAtt.Volume.Name + currentVol[isInstanceVolumeAttVolumeReferenceCrn] = *volumeAtt.Volume.CRN + if volumeAtt.Volume.Deleted != nil { + currentVol[isInstanceVolumeAttVolumeReferenceDeleted] = *volumeAtt.Volume.Deleted.MoreInfo + } + currentVol[isInstanceVolumeAttVolumeReferenceHref] = *volumeAtt.Volume.Href + volList = append(volList, currentVol) + } + d.Set(isInstanceVolumeAttVolumeReference, volList) + return nil + } + } + return fmt.Errorf("No Instance volume attachment found with name %s on instance %s", name, instanceId) +} diff --git a/ibm/data_source_ibm_is_instance_volume_attachment_test.go b/ibm/data_source_ibm_is_instance_volume_attachment_test.go new file mode 100644 index 00000000000..5d85433608e --- /dev/null +++ b/ibm/data_source_ibm_is_instance_volume_attachment_test.go @@ -0,0 +1,83 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccIBMISInstanceVolumeAttDataSource_basic(t *testing.T) { + + vpcname := fmt.Sprintf("tf-vpc-%d", acctest.RandIntRange(10, 100)) + name := fmt.Sprintf("tf-instnace-%d", acctest.RandIntRange(10, 100)) + subnetname := fmt.Sprintf("tf-subnet-%d", acctest.RandIntRange(10, 100)) + publicKey := strings.TrimSpace(` +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR +`) + sshname := fmt.Sprintf("tf-ssh-%d", acctest.RandIntRange(10, 100)) + resName := fmt.Sprintf("data.ibm_is_instance_volume_attachment.ds_vol_att") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckIBMISInstanceVolumeAttachmentDataSourceConfig(vpcname, subnetname, sshname, publicKey, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + resName, "name"), + resource.TestCheckResourceAttr( + resName, "type", "boot"), + resource.TestCheckResourceAttrSet( + resName, "href"), + ), + }, + }, + }) +} + +func testAccCheckIBMISInstanceVolumeAttachmentDataSourceConfig(vpcname, subnetname, sshname, publicKey, name string) string { + return fmt.Sprintf(` + resource "ibm_is_vpc" "testacc_vpc" { + name = "%s" + } + + resource "ibm_is_subnet" "testacc_subnet" { + name = "%s" + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + total_ipv4_address_count = 16 + } + + resource "ibm_is_ssh_key" "testacc_sshkey" { + name = "%s" + public_key = "%s" + } + + resource "ibm_is_instance" "testacc_instance" { + name = "%s" + image = "%s" + profile = "%s" + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } + } + data "ibm_is_instance_volume_attachment" "ds_vol_att" { + instance = ibm_is_instance.testacc_instance.id + name = ibm_is_instance.testacc_instance.volume_attachments.0.name + } + + `, vpcname, subnetname, ISZoneName, sshname, publicKey, name, isImage, instanceProfileName, ISZoneName) +} diff --git a/ibm/data_source_ibm_is_instance_volume_attachments.go b/ibm/data_source_ibm_is_instance_volume_attachments.go new file mode 100644 index 00000000000..8e4844af944 --- /dev/null +++ b/ibm/data_source_ibm_is_instance_volume_attachments.go @@ -0,0 +1,167 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "time" + + "github.com/IBM/vpc-go-sdk/vpcv1" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceIBMISInstanceVolumeAttachments() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIBMISInstanceVolumeAttachmentsRead, + + Schema: map[string]*schema.Schema{ + isInstanceId: { + Type: schema.TypeString, + Required: true, + Description: "Instance id", + }, + + isInstanceVolumeAttachments: { + Type: schema.TypeList, + Description: "List of volume attachments on an instance", + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + isInstanceVolumeDeleteOnInstanceDelete: { + Type: schema.TypeBool, + Computed: true, + Description: "If set to true, when deleting the instance the volume will also be deleted.", + }, + isInstanceVolAttName: { + Type: schema.TypeString, + Computed: true, + Description: "The user-defined name for this volume attachment.", + }, + + isInstanceVolumeAttDevice: { + Type: schema.TypeString, + Computed: true, + Description: "A unique identifier for the device which is exposed to the instance operating system", + }, + + isInstanceVolumeAttHref: { + Type: schema.TypeString, + Computed: true, + Description: "The URL for this volume attachment", + }, + + isInstanceVolAttId: { + Type: schema.TypeString, + Computed: true, + Description: "The unique identifier for this volume attachment", + }, + + isInstanceVolumeAttStatus: { + Type: schema.TypeString, + Computed: true, + Description: "The status of this volume attachment, one of [ attached, attaching, deleting, detaching ]", + }, + + isInstanceVolumeAttType: { + Type: schema.TypeString, + Computed: true, + Description: "The type of volume attachment one of [ boot, data ]", + }, + isInstanceVolumeAttVolumeReferenceId: { + Type: schema.TypeString, + Computed: true, + Description: "The unique identifier for this volume", + }, + isInstanceVolumeAttVolumeReferenceName: { + Type: schema.TypeString, + Computed: true, + Description: "The unique user-defined name for this volume", + }, + isInstanceVolumeAttVolumeReferenceCrn: { + Type: schema.TypeString, + Computed: true, + Description: "The CRN for this volume", + }, + isInstanceVolumeAttVolumeReferenceDeleted: { + Type: schema.TypeString, + Computed: true, + Description: "Link to documentation about deleted resources", + }, + isInstanceVolumeAttVolumeReferenceHref: { + Type: schema.TypeString, + Computed: true, + Description: "The URL for this volume", + }, + isInstanceVolumeAttVolumeReference: { + Type: schema.TypeList, + Computed: true, + Description: "The attached volume", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{}, + }, + }, + }, + }, + }, + }, + } +} + +func dataSourceIBMISInstanceVolumeAttachmentsRead(d *schema.ResourceData, meta interface{}) error { + instanceId := d.Get(isInstanceId).(string) + + err := instanceGetVolumeAttachments(d, meta, instanceId) + if err != nil { + return err + } + + return nil +} + +func instanceGetVolumeAttachments(d *schema.ResourceData, meta interface{}, instanceId string) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + allrecs := []vpcv1.VolumeAttachment{} + listInstanceVolumeAttOptions := &vpcv1.ListInstanceVolumeAttachmentsOptions{ + InstanceID: &instanceId, + } + volumeAtts, response, err := sess.ListInstanceVolumeAttachments(listInstanceVolumeAttOptions) + if err != nil { + return fmt.Errorf("Error Fetching Instance volume attachments %s\n%s", err, response) + } + allrecs = append(allrecs, volumeAtts.VolumeAttachments...) + volAttList := make([]map[string]interface{}, 0) + for _, volumeAtt := range allrecs { + currentVolAtt := map[string]interface{}{} + currentVolAtt[isInstanceVolAttName] = *volumeAtt.Name + currentVolAtt[isInstanceVolumeDeleteOnInstanceDelete] = *volumeAtt.DeleteVolumeOnInstanceDelete + currentVolAtt[isInstanceVolumeAttDevice] = *volumeAtt.Device.ID + currentVolAtt[isInstanceVolumeAttHref] = *volumeAtt.Href + currentVolAtt[isInstanceVolAttId] = *volumeAtt.ID + currentVolAtt[isInstanceVolumeAttStatus] = *volumeAtt.Status + currentVolAtt[isInstanceVolumeAttType] = *volumeAtt.Type + + if volumeAtt.Volume != nil { + currentVolAtt[isInstanceVolumeAttVolumeReferenceId] = *volumeAtt.Volume.ID + currentVolAtt[isInstanceVolumeAttVolumeReferenceName] = *volumeAtt.Volume.Name + currentVolAtt[isInstanceVolumeAttVolumeReferenceCrn] = *volumeAtt.Volume.CRN + if volumeAtt.Volume.Deleted != nil { + currentVolAtt[isInstanceVolumeAttVolumeReferenceDeleted] = *volumeAtt.Volume.Deleted.MoreInfo + } + currentVolAtt[isInstanceVolumeAttVolumeReferenceHref] = *volumeAtt.Volume.Href + } + + volAttList = append(volAttList, currentVolAtt) + } + d.SetId(dataSourceIBMISInstanceVolumeAttachmentsID(d)) + d.Set(isInstanceVolumeAttachments, volAttList) + return nil +} + +// dataSourceIBMISInstanceVolumeAttachmentsID returns a reasonable ID for a Instance volume attachments list. +func dataSourceIBMISInstanceVolumeAttachmentsID(d *schema.ResourceData) string { + return time.Now().UTC().String() +} diff --git a/ibm/data_source_ibm_is_instance_volume_attachments_test.go b/ibm/data_source_ibm_is_instance_volume_attachments_test.go new file mode 100644 index 00000000000..b2207492382 --- /dev/null +++ b/ibm/data_source_ibm_is_instance_volume_attachments_test.go @@ -0,0 +1,81 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccIBMISInstanceVolumeAttsDataSource_basic(t *testing.T) { + + vpcname := fmt.Sprintf("tf-vpc-%d", acctest.RandIntRange(10, 100)) + name := fmt.Sprintf("tf-instnace-%d", acctest.RandIntRange(10, 100)) + subnetname := fmt.Sprintf("tf-subnet-%d", acctest.RandIntRange(10, 100)) + publicKey := strings.TrimSpace(` +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR +`) + sshname := fmt.Sprintf("tf-ssh-%d", acctest.RandIntRange(10, 100)) + resName := fmt.Sprintf("data.ibm_is_instance_volume_attachments.ds_vol_atts") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckIBMISInstanceVolumeAttsDataSourceConfig(vpcname, subnetname, sshname, publicKey, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + resName, "volume_attachments.0.name"), + resource.TestCheckResourceAttrSet( + resName, "volume_attachments.0.href"), + ), + }, + }, + }) +} + +func testAccCheckIBMISInstanceVolumeAttsDataSourceConfig(vpcname, subnetname, sshname, publicKey, name string) string { + return fmt.Sprintf(` + resource "ibm_is_vpc" "testacc_vpc" { + name = "%s" + } + + resource "ibm_is_subnet" "testacc_subnet" { + name = "%s" + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + total_ipv4_address_count = 16 + } + + resource "ibm_is_ssh_key" "testacc_sshkey" { + name = "%s" + public_key = "%s" + } + + resource "ibm_is_instance" "testacc_instance" { + name = "%s" + image = "%s" + profile = "%s" + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } + } + data "ibm_is_instance_volume_attachments" "ds_vol_atts" { + instance = ibm_is_instance.testacc_instance.id + } + + `, vpcname, subnetname, ISZoneName, sshname, publicKey, name, isImage, instanceProfileName, ISZoneName) +} diff --git a/ibm/data_source_ibm_is_snapshot.go b/ibm/data_source_ibm_is_snapshot.go new file mode 100644 index 00000000000..d52f7132e00 --- /dev/null +++ b/ibm/data_source_ibm_is_snapshot.go @@ -0,0 +1,218 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + + "github.com/IBM/vpc-go-sdk/vpcv1" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceSnapshot() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIBMISSnapshotRead, + + Schema: map[string]*schema.Schema{ + "identifier": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{isSnapshotName, "identifier"}, + Description: "Snapshot identifier", + ValidateFunc: InvokeDataSourceValidator("ibm_is_snapshot", "identifier"), + }, + + isSnapshotName: { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{isSnapshotName, "identifier"}, + ValidateFunc: InvokeDataSourceValidator("ibm_is_snapshot", isSnapshotName), + Description: "Snapshot name", + }, + + isSnapshotResourceGroup: { + Type: schema.TypeString, + Computed: true, + Description: "Resource group info", + }, + + isSnapshotSourceVolume: { + Type: schema.TypeString, + Computed: true, + Description: "Snapshot source volume id", + }, + isSnapshotSourceImage: { + Type: schema.TypeString, + Computed: true, + Description: "If present, the image id from which the data on this volume was most directly provisioned.", + }, + + isSnapshotOperatingSystem: { + Type: schema.TypeString, + Computed: true, + Description: "The globally unique name for the operating system included in this image", + }, + + isSnapshotBootable: { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates if a boot volume attachment can be created with a volume created from this snapshot", + }, + + isSnapshotLCState: { + Type: schema.TypeString, + Computed: true, + Description: "Snapshot lifecycle state", + }, + isSnapshotCRN: { + Type: schema.TypeString, + Computed: true, + Description: "The crn of the resource", + }, + isSnapshotEncryption: { + Type: schema.TypeString, + Computed: true, + Description: "Encryption type of the snapshot", + }, + isSnapshotHref: { + Type: schema.TypeString, + Computed: true, + Description: "URL for the snapshot", + }, + + isSnapshotMinCapacity: { + Type: schema.TypeInt, + Computed: true, + Description: "Minimum capacity of the snapshot", + }, + isSnapshotResourceType: { + Type: schema.TypeString, + Computed: true, + Description: "The resource type of the snapshot", + }, + + isSnapshotSize: { + Type: schema.TypeInt, + Computed: true, + Description: "The size of the snapshot", + }, + }, + } +} + +func dataSourceIBMISSnapshotValidator() *ResourceValidator { + validateSchema := make([]ValidateSchema, 1) + validateSchema = append(validateSchema, + ValidateSchema{ + Identifier: "identifier", + ValidateFunctionIdentifier: ValidateNoZeroValues, + Type: TypeString}) + + validateSchema = append(validateSchema, + ValidateSchema{ + Identifier: isSnapshotName, + ValidateFunctionIdentifier: ValidateNoZeroValues, + Type: TypeString}) + + ibmISSnapshotDataSourceValidator := ResourceValidator{ResourceName: "ibm_is_snapshot", Schema: validateSchema} + return &ibmISSnapshotDataSourceValidator +} + +func dataSourceIBMISSnapshotRead(d *schema.ResourceData, meta interface{}) error { + name := d.Get(isSnapshotName).(string) + id := d.Get("identifier").(string) + err := snapshotGetByNameOrID(d, meta, name, id) + if err != nil { + return err + } + return nil +} + +func snapshotGetByNameOrID(d *schema.ResourceData, meta interface{}, name, id string) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + if name != "" { + start := "" + allrecs := []vpcv1.Snapshot{} + for { + listSnapshotOptions := &vpcv1.ListSnapshotsOptions{} + if start != "" { + listSnapshotOptions.Start = &start + } + snapshots, response, err := sess.ListSnapshots(listSnapshotOptions) + if err != nil { + return fmt.Errorf("Error Fetching snapshots %s\n%s", err, response) + } + start = GetNext(snapshots.Next) + allrecs = append(allrecs, snapshots.Snapshots...) + if start == "" { + break + } + } + for _, snapshot := range allrecs { + if *snapshot.Name == name || *snapshot.ID == id { + d.SetId(*snapshot.ID) + d.Set(isSnapshotName, *snapshot.Name) + d.Set(isSnapshotHref, *snapshot.Href) + d.Set(isSnapshotCRN, *snapshot.CRN) + d.Set(isSnapshotMinCapacity, *snapshot.MinimumCapacity) + d.Set(isSnapshotSize, *snapshot.Size) + d.Set(isSnapshotEncryption, *snapshot.Encryption) + d.Set(isSnapshotLCState, *snapshot.LifecycleState) + d.Set(isSnapshotResourceType, *snapshot.ResourceType) + d.Set(isSnapshotBootable, *snapshot.Bootable) + if snapshot.ResourceGroup != nil && snapshot.ResourceGroup.ID != nil { + d.Set(isSnapshotResourceGroup, *snapshot.ResourceGroup.ID) + } + if snapshot.SourceVolume != nil && snapshot.SourceVolume.ID != nil { + d.Set(isSnapshotSourceVolume, *snapshot.SourceVolume.ID) + } + if snapshot.SourceImage != nil && snapshot.SourceImage.ID != nil { + d.Set(isSnapshotSourceImage, *snapshot.SourceImage.ID) + } + if snapshot.OperatingSystem != nil && snapshot.OperatingSystem.Name != nil { + d.Set(isSnapshotOperatingSystem, *snapshot.OperatingSystem.Name) + } + return nil + } + } + return fmt.Errorf("No snapshot found with name %s", name) + } else { + getSnapshotOptions := &vpcv1.GetSnapshotOptions{ + ID: &id, + } + snapshot, response, err := sess.GetSnapshot(getSnapshotOptions) + if err != nil { + return fmt.Errorf("Error fetching snapshot %s\n%s", err, response) + } + if (response != nil && response.StatusCode == 404) || snapshot == nil { + return fmt.Errorf("No snapshot found with id %s", id) + } + d.SetId(*snapshot.ID) + d.Set(isSnapshotName, *snapshot.Name) + d.Set(isSnapshotHref, *snapshot.Href) + d.Set(isSnapshotCRN, *snapshot.CRN) + d.Set(isSnapshotMinCapacity, *snapshot.MinimumCapacity) + d.Set(isSnapshotSize, *snapshot.Size) + d.Set(isSnapshotEncryption, *snapshot.Encryption) + d.Set(isSnapshotLCState, *snapshot.LifecycleState) + d.Set(isSnapshotResourceType, *snapshot.ResourceType) + d.Set(isSnapshotBootable, *snapshot.Bootable) + if snapshot.ResourceGroup != nil && snapshot.ResourceGroup.ID != nil { + d.Set(isSnapshotResourceGroup, *snapshot.ResourceGroup.ID) + } + if snapshot.SourceVolume != nil && snapshot.SourceVolume.ID != nil { + d.Set(isSnapshotSourceVolume, *snapshot.SourceVolume.ID) + } + if snapshot.SourceImage != nil && snapshot.SourceImage.ID != nil { + d.Set(isSnapshotSourceImage, *snapshot.SourceImage.ID) + } + if snapshot.OperatingSystem != nil && snapshot.OperatingSystem.Name != nil { + d.Set(isSnapshotOperatingSystem, *snapshot.OperatingSystem.Name) + } + return nil + } +} diff --git a/ibm/data_source_ibm_is_snapshot_test.go b/ibm/data_source_ibm_is_snapshot_test.go new file mode 100644 index 00000000000..d76fd5e66c0 --- /dev/null +++ b/ibm/data_source_ibm_is_snapshot_test.go @@ -0,0 +1,91 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccIBMISSnapshotDatasource_basic(t *testing.T) { + var snapshot string + snpName := "data.ibm_is_snapshot.ds_snapshot" + vpcname := fmt.Sprintf("tf-vpc-%d", acctest.RandIntRange(10, 100)) + name := fmt.Sprintf("tf-instnace-%d", acctest.RandIntRange(10, 100)) + subnetname := fmt.Sprintf("tf-subnet-%d", acctest.RandIntRange(10, 100)) + publicKey := strings.TrimSpace(` +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR +`) + sshname := fmt.Sprintf("tf-ssh-%d", acctest.RandIntRange(10, 100)) + volname := fmt.Sprintf("tf-vol-%d", acctest.RandIntRange(10, 100)) + name1 := fmt.Sprintf("tfsnapshotuat-%d", acctest.RandIntRange(10, 100)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIBMISSnapshotDestroy, + Steps: []resource.TestStep{ + { + Config: testDSCheckIBMISSnapshotConfig(vpcname, subnetname, sshname, publicKey, volname, name, name1), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMISSnapshotExists("ibm_is_snapshot.testacc_snapshot", snapshot), + resource.TestCheckResourceAttr( + "ibm_is_snapshot.testacc_snapshot", "name", name1), + resource.TestCheckResourceAttrSet(snpName, "delatable"), + resource.TestCheckResourceAttrSet(snpName, "href"), + resource.TestCheckResourceAttrSet(snpName, "crn"), + resource.TestCheckResourceAttrSet(snpName, "lifecycle_state"), + resource.TestCheckResourceAttrSet(snpName, "encryption"), + ), + }, + }, + }) +} + +func testDSCheckIBMISSnapshotConfig(vpcname, subnetname, sshname, publicKey, volname, name, sname string) string { + return fmt.Sprintf(` + resource "ibm_is_vpc" "testacc_vpc" { + name = "%s" + } + + resource "ibm_is_subnet" "testacc_subnet" { + name = "%s" + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + total_ipv4_address_count = 16 + } + + resource "ibm_is_ssh_key" "testacc_sshkey" { + name = "%s" + public_key = "%s" + } + + resource "ibm_is_instance" "testacc_instance" { + name = "%s" + image = "%s" + profile = "%s" + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } + } + resource "ibm_is_snapshot" "testacc_snapshot" { + name = "%s" + source_volume = ibm_is_instance.testacc_instance.volume_attachments[0].volume_id + } + data "ibm_is_snapshot" "ds_snapshot" { + depends_on = [ibm_is_snapshot.testacc_snapshot] + name = "%s" + } +`, vpcname, subnetname, ISZoneName, sshname, publicKey, name, isImage, instanceProfileName, ISZoneName, sname, sname) +} diff --git a/ibm/data_source_ibm_is_snapshots.go b/ibm/data_source_ibm_is_snapshots.go new file mode 100644 index 00000000000..ca868391786 --- /dev/null +++ b/ibm/data_source_ibm_is_snapshots.go @@ -0,0 +1,182 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "time" + + "github.com/IBM/vpc-go-sdk/vpcv1" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + isSnapshots = "snapshots" + isSnapshotId = "id" +) + +func dataSourceSnapshots() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIBMISSnapshotsRead, + + Schema: map[string]*schema.Schema{ + + isSnapshots: { + Type: schema.TypeList, + Description: "List of snapshots", + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + isSnapshotId: { + Type: schema.TypeString, + Computed: true, + }, + + isSnapshotName: { + Type: schema.TypeString, + Computed: true, + Description: "Snapshot name", + }, + + isSnapshotResourceGroup: { + Type: schema.TypeString, + Computed: true, + Description: "Resource group info", + }, + + isSnapshotSourceVolume: { + Type: schema.TypeString, + Computed: true, + Description: "Snapshot source volume", + }, + isSnapshotSourceImage: { + Type: schema.TypeString, + Computed: true, + Description: "If present, the image id from which the data on this volume was most directly provisioned.", + }, + + isSnapshotOperatingSystem: { + Type: schema.TypeString, + Computed: true, + Description: "The globally unique name for the operating system included in this image", + }, + + isSnapshotLCState: { + Type: schema.TypeString, + Computed: true, + Description: "Snapshot lifecycle state", + }, + isSnapshotCRN: { + Type: schema.TypeString, + Computed: true, + Description: "The crn of the resource", + }, + isSnapshotEncryption: { + Type: schema.TypeString, + Computed: true, + Description: "Encryption type of the snapshot", + }, + isSnapshotHref: { + Type: schema.TypeString, + Computed: true, + Description: "URL for the snapshot", + }, + + isSnapshotBootable: { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates if a boot volume attachment can be created with a volume created from this snapshot", + }, + + isSnapshotMinCapacity: { + Type: schema.TypeInt, + Computed: true, + Description: "Minimum capacity of the snapshot", + }, + isSnapshotResourceType: { + Type: schema.TypeString, + Computed: true, + Description: "The resource type of the snapshot", + }, + + isSnapshotSize: { + Type: schema.TypeInt, + Computed: true, + Description: "The size of the snapshot", + }, + }, + }, + }, + }, + } +} + +func dataSourceIBMISSnapshotsRead(d *schema.ResourceData, meta interface{}) error { + err := getSnapshots(d, meta) + if err != nil { + return err + } + return nil +} + +func getSnapshots(d *schema.ResourceData, meta interface{}) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + start := "" + allrecs := []vpcv1.Snapshot{} + for { + listSnapshotOptions := &vpcv1.ListSnapshotsOptions{} + if start != "" { + listSnapshotOptions.Start = &start + } + snapshots, response, err := sess.ListSnapshots(listSnapshotOptions) + if err != nil { + return fmt.Errorf("Error fetching snapshots %s\n%s", err, response) + } + start = GetNext(snapshots.Next) + allrecs = append(allrecs, snapshots.Snapshots...) + if start == "" { + break + } + } + + snapshotsInfo := make([]map[string]interface{}, 0) + for _, snapshot := range allrecs { + l := map[string]interface{}{ + isSnapshotId: *snapshot.ID, + isSnapshotName: *snapshot.Name, + isSnapshotHref: *snapshot.Href, + isSnapshotCRN: *snapshot.CRN, + isSnapshotMinCapacity: *snapshot.MinimumCapacity, + isSnapshotSize: *snapshot.Size, + isSnapshotEncryption: *snapshot.Encryption, + isSnapshotLCState: *snapshot.LifecycleState, + isSnapshotResourceType: *snapshot.ResourceType, + isSnapshotBootable: *snapshot.Bootable, + } + if snapshot.ResourceGroup != nil && snapshot.ResourceGroup.ID != nil { + l[isSnapshotResourceGroup] = *snapshot.ResourceGroup.ID + } + if snapshot.SourceVolume != nil && snapshot.SourceVolume.ID != nil { + l[isSnapshotSourceVolume] = *snapshot.SourceVolume.ID + } + if snapshot.SourceImage != nil && snapshot.SourceImage.ID != nil { + l[isSnapshotSourceImage] = *snapshot.SourceImage.ID + } + if snapshot.OperatingSystem != nil && snapshot.OperatingSystem.Name != nil { + l[isSnapshotOperatingSystem] = *snapshot.OperatingSystem.Name + } + snapshotsInfo = append(snapshotsInfo, l) + } + d.SetId(dataSourceIBMISSnapshotsID(d)) + d.Set(isSnapshots, snapshotsInfo) + return nil +} + +// dataSourceIBMISSnapshotsID returns a reasonable ID for the snapshot list. +func dataSourceIBMISSnapshotsID(d *schema.ResourceData) string { + return time.Now().UTC().String() +} diff --git a/ibm/data_source_ibm_is_snapshots_test.go b/ibm/data_source_ibm_is_snapshots_test.go new file mode 100644 index 00000000000..fc9b33bd5ac --- /dev/null +++ b/ibm/data_source_ibm_is_snapshots_test.go @@ -0,0 +1,58 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccIBMISSnapshotsDatasource_basic(t *testing.T) { + var snapshot string + snpName := "data.ibm_is_snapshots.ds_snapshot" + vpcname := fmt.Sprintf("tf-vpc-%d", acctest.RandIntRange(10, 100)) + name := fmt.Sprintf("tf-instnace-%d", acctest.RandIntRange(10, 100)) + subnetname := fmt.Sprintf("tf-subnet-%d", acctest.RandIntRange(10, 100)) + publicKey := strings.TrimSpace(` +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR +`) + sshname := fmt.Sprintf("tf-ssh-%d", acctest.RandIntRange(10, 100)) + volname := fmt.Sprintf("tf-vol-%d", acctest.RandIntRange(10, 100)) + name1 := fmt.Sprintf("tfsnapshotuat-%d", acctest.RandIntRange(10, 100)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIBMISSnapshotDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMISSnapshotConfig(vpcname, subnetname, sshname, publicKey, volname, name, name1), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMISSnapshotExists("ibm_is_snapshot.testacc_snapshot", snapshot), + resource.TestCheckResourceAttr( + "ibm_is_snapshot.testacc_snapshot", "name", name1), + ), + }, + { + Config: testDSCheckIBMISSnapshotsConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(snpName, "snapshots.0.delatable"), + resource.TestCheckResourceAttrSet(snpName, "snapshots.0.href"), + resource.TestCheckResourceAttrSet(snpName, "snapshots.0.crn"), + resource.TestCheckResourceAttrSet(snpName, "snapshots.0.lifecycle_state"), + resource.TestCheckResourceAttrSet(snpName, "snapshots.0.encryption"), + ), + }, + }, + }) +} + +func testDSCheckIBMISSnapshotsConfig() string { + return fmt.Sprintf(` + data "ibm_is_snapshots" "ds_snapshot" { +}`) +} diff --git a/ibm/data_source_ibm_is_volume.go b/ibm/data_source_ibm_is_volume.go index 5d7ad0c4dfa..c17d2e67f84 100644 --- a/ibm/data_source_ibm_is_volume.go +++ b/ibm/data_source_ibm_is_volume.go @@ -101,6 +101,12 @@ func dataSourceIBMISVolume() *schema.Resource { Description: "Tags for the volume instance", }, + isVolumeSourceSnapshot: { + Type: schema.TypeString, + Computed: true, + Description: "Identifier of the snapshot from which this volume was cloned", + }, + ResourceControllerURL: { Type: schema.TypeString, Computed: true, @@ -195,6 +201,9 @@ func volumeGet(d *schema.ResourceData, meta interface{}, name string) error { if vol.EncryptionKey != nil { d.Set(isVolumeEncryptionKey, vol.EncryptionKey.CRN) } + if vol.SourceSnapshot != nil { + d.Set(isVolumeSourceSnapshot, *vol.SourceSnapshot.ID) + } d.Set(isVolumeIops, *vol.Iops) d.Set(isVolumeCapacity, *vol.Capacity) d.Set(isVolumeCrn, *vol.CRN) diff --git a/ibm/provider.go b/ibm/provider.go index 24cab05921f..738b5062247 100644 --- a/ibm/provider.go +++ b/ibm/provider.go @@ -270,6 +270,8 @@ func Provider() *schema.Provider { "ibm_is_instances": dataSourceIBMISInstances(), "ibm_is_instance_disk": dataSourceIbmIsInstanceDisk(), "ibm_is_instance_disks": dataSourceIbmIsInstanceDisks(), + "ibm_is_instance_volume_attachment": dataSourceIBMISInstanceVolumeAttachment(), + "ibm_is_instance_volume_attachments": dataSourceIBMISInstanceVolumeAttachments(), "ibm_is_lb": dataSourceIBMISLB(), "ibm_is_lb_profiles": dataSourceIBMISLbProfiles(), "ibm_is_lbs": dataSourceIBMISLBS(), @@ -284,6 +286,8 @@ func Provider() *schema.Provider { "ibm_is_security_group": dataSourceIBMISSecurityGroup(), "ibm_is_security_group_target": dataSourceIBMISSecurityGroupTarget(), "ibm_is_security_group_targets": dataSourceIBMISSecurityGroupTargets(), + "ibm_is_snapshot": dataSourceSnapshot(), + "ibm_is_snapshots": dataSourceSnapshots(), "ibm_is_volume": dataSourceIBMISVolume(), "ibm_is_volume_profile": dataSourceIBMISVolumeProfile(), "ibm_is_volume_profiles": dataSourceIBMISVolumeProfiles(), @@ -503,6 +507,7 @@ func Provider() *schema.Provider { "ibm_is_instance_group_manager": resourceIBMISInstanceGroupManager(), "ibm_is_instance_group_manager_policy": resourceIBMISInstanceGroupManagerPolicy(), "ibm_is_instance_group_manager_action": resourceIBMISInstanceGroupManagerAction(), + "ibm_is_instance_volume_attachment": resourceIBMISInstanceVolumeAttachment(), "ibm_is_virtual_endpoint_gateway": resourceIBMISEndpointGateway(), "ibm_is_virtual_endpoint_gateway_ip": resourceIBMISEndpointGatewayIP(), "ibm_is_instance_template": resourceIBMISInstanceTemplate(), @@ -525,6 +530,7 @@ func Provider() *schema.Provider { "ibm_is_subnet_reserved_ip": resourceIBMISReservedIP(), "ibm_is_subnet_network_acl_attachment": resourceIBMISSubnetNetworkACLAttachment(), "ibm_is_ssh_key": resourceIBMISSSHKey(), + "ibm_is_snapshot": resourceIBMSnapshot(), "ibm_is_volume": resourceIBMISVolume(), "ibm_is_vpn_gateway": resourceIBMISVPNGateway(), "ibm_is_vpn_gateway_connection": resourceIBMISVPNGatewayConnection(), @@ -691,6 +697,7 @@ func Validator() ValidatorDict { "ibm_is_image": resourceIBMISImageValidator(), "ibm_is_instance": resourceIBMISInstanceValidator(), "ibm_is_instance_disk_management": resourceIBMISInstanceDiskManagementValidator(), + "ibm_is_instance_volume_attachment": resourceIBMISInstanceVolumeAttachmentValidator(), "ibm_is_ipsec_policy": resourceIBMISIPSECValidator(), "ibm_is_lb_listener_policy_rule": resourceIBMISLBListenerPolicyRuleValidator(), "ibm_is_lb_listener_policy": resourceIBMISLBListenerPolicyValidator(), @@ -703,6 +710,7 @@ func Validator() ValidatorDict { "ibm_is_security_group_target": resourceIBMISSecurityGroupTargetValidator(), "ibm_is_security_group_rule": resourceIBMISSecurityGroupRuleValidator(), "ibm_is_security_group": resourceIBMISSecurityGroupValidator(), + "ibm_is_snapshot": resourceIBMISSnapshotValidator(), "ibm_is_ssh_key": resourceIBMISSHKeyValidator(), "ibm_is_subnet": resourceIBMISSubnetValidator(), "ibm_is_subnet_reserved_ip": resourceIBMISSubnetReservedIPValidator(), @@ -731,6 +739,7 @@ func Validator() ValidatorDict { }, DataSourceValidatorDictionary: map[string]*ResourceValidator{ "ibm_is_subnet": dataSourceIBMISSubnetValidator(), + "ibm_is_snapshot": dataSourceIBMISSnapshotValidator(), "ibm_dl_offering_speeds": datasourceIBMDLOfferingSpeedsValidator(), "ibm_dl_routers": datasourceIBMDLRoutersValidator(), "ibm_is_vpc": dataSourceIBMISVpcValidator(), diff --git a/ibm/resource_ibm_is_instance.go b/ibm/resource_ibm_is_instance.go index 80b11f4c675..73f719f94ea 100644 --- a/ibm/resource_ibm_is_instance.go +++ b/ibm/resource_ibm_is_instance.go @@ -38,16 +38,10 @@ const ( isInstanceVPC = "vpc" isInstanceZone = "zone" isInstanceBootVolume = "boot_volume" - isInstanceVolAttName = "name" - isInstanceVolAttVolume = "volume" + isInstanceVolumeSnapshot = "snapshot" + isInstanceSourceTemplate = "instance_template" isInstanceVolAttVolAutoDelete = "auto_delete_volume" - isInstanceVolAttVolCapacity = "capacity" - isInstanceVolAttVolIops = "iops" - isInstanceVolAttVolName = "name" isInstanceVolAttVolBillingTerm = "billing_term" - isInstanceVolAttVolEncryptionKey = "encryption_key" - isInstanceVolAttVolType = "type" - isInstanceVolAttVolProfile = "profile" isInstanceImage = "image" isInstanceCPU = "vcpu" isInstanceCPUArch = "architecture" @@ -78,11 +72,11 @@ const ( isInstanceStatusRunning = "running" isInstanceStatusFailed = "failed" - isInstanceBootName = "name" - isInstanceBootSize = "size" - isInstanceBootIOPS = "iops" - isInstanceBootEncryption = "encryption" - isInstanceBootProfile = "profile" + isInstanceBootAttachmentName = "name" + isInstanceBootSize = "size" + isInstanceBootIOPS = "iops" + isInstanceBootEncryption = "encryption" + isInstanceBootProfile = "profile" isInstanceVolumeAttachments = "volume_attachments" isInstanceVolumeAttaching = "attaching" @@ -155,31 +149,41 @@ func resourceIBMISInstance() *schema.Resource { ValidateFunc: InvokeValidator("ibm_is_instance", isInstanceName), Description: "Instance name", }, - isInstanceVPC: { Type: schema.TypeString, ForceNew: true, - Required: true, + Optional: true, + Computed: true, Description: "VPC id", }, + isInstanceSourceTemplate: { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + AtLeastOneOf: []string{isInstanceImage, isInstanceSourceTemplate, "boot_volume.0.snapshot"}, + ConflictsWith: []string{"boot_volume.0.snapshot"}, + Description: "Id of the instance template", + }, isInstanceZone: { Type: schema.TypeString, ForceNew: true, - Required: true, + Computed: true, + Optional: true, Description: "Zone name", }, isInstanceProfile: { Type: schema.TypeString, ForceNew: false, - Required: true, + Computed: true, + Optional: true, Description: "Profile info", }, isInstanceKeys: { Type: schema.TypeSet, - Required: true, + Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, DiffSuppressFunc: applyOnce, @@ -235,7 +239,8 @@ func resourceIBMISInstance() *schema.Resource { Type: schema.TypeList, MinItems: 1, MaxItems: 1, - Required: true, + Optional: true, + Computed: true, Description: "Primary Network interface info", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -332,10 +337,14 @@ func resourceIBMISInstance() *schema.Resource { }, isInstanceImage: { - Type: schema.TypeString, - ForceNew: true, - Required: true, - Description: "image name", + Type: schema.TypeString, + ForceNew: true, + Computed: true, + Optional: true, + ConflictsWith: []string{"boot_volume.0.snapshot"}, + AtLeastOneOf: []string{isInstanceImage, isInstanceSourceTemplate, "boot_volume.0.snapshot"}, + RequiredWith: []string{isInstanceZone, isInstancePrimaryNetworkInterface, isInstanceKeys, isInstanceVPC, isInstanceProfile}, + Description: "image id", }, isInstanceBootVolume: { @@ -346,11 +355,20 @@ func resourceIBMISInstance() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - isInstanceBootName: { + isInstanceBootAttachmentName: { Type: schema.TypeString, Optional: true, Computed: true, }, + + isInstanceVolumeSnapshot: { + Type: schema.TypeString, + RequiredWith: []string{isInstanceZone, isInstancePrimaryNetworkInterface, isInstanceProfile, isInstanceKeys, isInstanceVPC}, + AtLeastOneOf: []string{isInstanceImage, isInstanceSourceTemplate, "boot_volume.0.snapshot"}, + ConflictsWith: []string{isInstanceImage, isInstanceSourceTemplate}, + Optional: true, + ForceNew: true, + }, isInstanceBootEncryption: { Type: schema.TypeString, Optional: true, @@ -602,9 +620,9 @@ func classicInstanceCreate(d *schema.ResourceData, meta interface{}, profile, na if boot, ok := d.GetOk(isInstanceBootVolume); ok { bootvol := boot.([]interface{})[0].(map[string]interface{}) var volTemplate = &vpcclassicv1.VolumePrototypeInstanceByImageContext{} - name, ok := bootvol[isInstanceBootName] + name, ok := bootvol[isInstanceBootAttachmentName] namestr := name.(string) - if ok { + if namestr != "" && ok { volTemplate.Name = &namestr } enc, ok := bootvol[isInstanceBootEncryption] @@ -754,13 +772,13 @@ func classicInstanceCreate(d *schema.ResourceData, meta interface{}, profile, na err = UpdateTagsUsingCRN(oldList, newList, meta, *instance.CRN) if err != nil { log.Printf( - "Error on create of resource vpc instance (%s) tags: %s", d.Id(), err) + "Error on create of resource instance (%s) tags: %s", d.Id(), err) } } return nil } -func instanceCreate(d *schema.ResourceData, meta interface{}, profile, name, vpcID, zone, image string) error { +func instanceCreateByImage(d *schema.ResourceData, meta interface{}, profile, name, vpcID, zone, image string) error { sess, err := vpcClient(meta) if err != nil { return err @@ -800,9 +818,9 @@ func instanceCreate(d *schema.ResourceData, meta interface{}, profile, name, vpc if boot, ok := d.GetOk(isInstanceBootVolume); ok { bootvol := boot.([]interface{})[0].(map[string]interface{}) var volTemplate = &vpcv1.VolumePrototypeInstanceByImageContext{} - name, ok := bootvol[isInstanceBootName] + name, ok := bootvol[isInstanceBootAttachmentName] namestr := name.(string) - if ok { + if namestr != "" && ok { volTemplate.Name = &namestr } enc, ok := bootvol[isInstanceBootEncryption] @@ -815,11 +833,212 @@ func instanceCreate(d *schema.ResourceData, meta interface{}, profile, name, vpc volcap := 100 volcapint64 := int64(volcap) volprof := "general-purpose" + volTemplate.Profile = &vpcv1.VolumeProfileIdentity{ + Name: &volprof, + } volTemplate.Capacity = &volcapint64 + deletebool := true + instanceproto.BootVolumeAttachment = &vpcv1.VolumeAttachmentPrototypeInstanceByImageContext{ + DeleteVolumeOnInstanceDelete: &deletebool, + Volume: volTemplate, + } + + } + + if primnicintf, ok := d.GetOk(isInstancePrimaryNetworkInterface); ok { + primnic := primnicintf.([]interface{})[0].(map[string]interface{}) + subnetintf, _ := primnic[isInstanceNicSubnet] + subnetintfstr := subnetintf.(string) + var primnicobj = &vpcv1.NetworkInterfacePrototype{} + primnicobj.Subnet = &vpcv1.SubnetIdentity{ + ID: &subnetintfstr, + } + name, _ := primnic[isInstanceNicName] + namestr := name.(string) + if namestr != "" { + primnicobj.Name = &namestr + } + ipv4, _ := primnic[isInstanceNicPrimaryIpv4Address] + ipv4str := ipv4.(string) + if ipv4str != "" { + primnicobj.PrimaryIpv4Address = &ipv4str + } + allowIPSpoofing, ok := primnic[isInstanceNicAllowIPSpoofing] + allowIPSpoofingbool := allowIPSpoofing.(bool) + if ok { + primnicobj.AllowIPSpoofing = &allowIPSpoofingbool + } + secgrpintf, ok := primnic[isInstanceNicSecurityGroups] + if ok { + secgrpSet := secgrpintf.(*schema.Set) + if secgrpSet.Len() != 0 { + var secgrpobjs = make([]vpcv1.SecurityGroupIdentityIntf, secgrpSet.Len()) + for i, secgrpIntf := range secgrpSet.List() { + secgrpIntfstr := secgrpIntf.(string) + secgrpobjs[i] = &vpcv1.SecurityGroupIdentity{ + ID: &secgrpIntfstr, + } + } + primnicobj.SecurityGroups = secgrpobjs + } + } + instanceproto.PrimaryNetworkInterface = primnicobj + } + + if nicsintf, ok := d.GetOk(isInstanceNetworkInterfaces); ok { + nics := nicsintf.([]interface{}) + var intfs []vpcv1.NetworkInterfacePrototype + for _, resource := range nics { + nic := resource.(map[string]interface{}) + nwInterface := &vpcv1.NetworkInterfacePrototype{} + subnetintf, _ := nic[isInstanceNicSubnet] + subnetintfstr := subnetintf.(string) + nwInterface.Subnet = &vpcv1.SubnetIdentity{ + ID: &subnetintfstr, + } + name, ok := nic[isInstanceNicName] + namestr := name.(string) + if ok && namestr != "" { + nwInterface.Name = &namestr + } + ipv4, _ := nic[isInstanceNicPrimaryIpv4Address] + ipv4str := ipv4.(string) + if ipv4str != "" { + nwInterface.PrimaryIpv4Address = &ipv4str + } + allowIPSpoofing, ok := nic[isInstanceNicAllowIPSpoofing] + allowIPSpoofingbool := allowIPSpoofing.(bool) + if ok { + nwInterface.AllowIPSpoofing = &allowIPSpoofingbool + } + secgrpintf, ok := nic[isInstanceNicSecurityGroups] + if ok { + secgrpSet := secgrpintf.(*schema.Set) + if secgrpSet.Len() != 0 { + var secgrpobjs = make([]vpcv1.SecurityGroupIdentityIntf, secgrpSet.Len()) + for i, secgrpIntf := range secgrpSet.List() { + secgrpIntfstr := secgrpIntf.(string) + secgrpobjs[i] = &vpcv1.SecurityGroupIdentity{ + ID: &secgrpIntfstr, + } + } + nwInterface.SecurityGroups = secgrpobjs + } + } + intfs = append(intfs, *nwInterface) + } + instanceproto.NetworkInterfaces = intfs + } + + keySet := d.Get(isInstanceKeys).(*schema.Set) + if keySet.Len() != 0 { + keyobjs := make([]vpcv1.KeyIdentityIntf, keySet.Len()) + for i, key := range keySet.List() { + keystr := key.(string) + keyobjs[i] = &vpcv1.KeyIdentity{ + ID: &keystr, + } + } + instanceproto.Keys = keyobjs + } + + if userdata, ok := d.GetOk(isInstanceUserData); ok { + userdatastr := userdata.(string) + instanceproto.UserData = &userdatastr + } + + if grp, ok := d.GetOk(isInstanceResourceGroup); ok { + grpstr := grp.(string) + instanceproto.ResourceGroup = &vpcv1.ResourceGroupIdentity{ + ID: &grpstr, + } + + } + + options := &vpcv1.CreateInstanceOptions{ + InstancePrototype: instanceproto, + } + + instance, response, err := sess.CreateInstance(options) + if err != nil { + log.Printf("[DEBUG] Instance err %s\n%s", err, response) + return err + } + d.SetId(*instance.ID) + + log.Printf("[INFO] Instance : %s", *instance.ID) + d.Set(isInstanceStatus, instance.Status) + + _, err = isWaitForInstanceAvailable(sess, d.Id(), d.Timeout(schema.TimeoutCreate), d) + if err != nil { + return err + } + + v := os.Getenv("IC_ENV_TAGS") + if _, ok := d.GetOk(isInstanceTags); ok || v != "" { + oldList, newList := d.GetChange(isInstanceTags) + err = UpdateTagsUsingCRN(oldList, newList, meta, *instance.CRN) + if err != nil { + log.Printf( + "Error on create of resource instance (%s) tags: %s", d.Id(), err) + } + } + return nil +} + +func instanceCreateByTemplate(d *schema.ResourceData, meta interface{}, profile, name, vpcID, zone, image, template string) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + instanceproto := &vpcv1.InstancePrototypeInstanceBySourceTemplate{ + SourceTemplate: &vpcv1.InstanceTemplateIdentity{ + ID: &template, + }, + Name: &name, + } + + if profile != "" { + instanceproto.Profile = &vpcv1.InstanceProfileIdentity{ + Name: &profile, + } + } + if vpcID != "" { + instanceproto.VPC = &vpcv1.VPCIdentity{ + ID: &vpcID, + } + } + if zone != "" { + instanceproto.Zone = &vpcv1.ZoneIdentity{ + Name: &zone, + } + } + + if boot, ok := d.GetOk(isInstanceBootVolume); ok { + bootvol := boot.([]interface{})[0].(map[string]interface{}) + var volTemplate = &vpcv1.VolumePrototypeInstanceByImageContext{} + name, ok := bootvol[isInstanceBootAttachmentName] + namestr := name.(string) + if namestr != "" && ok { + volTemplate.Name = &namestr + } + enc, ok := bootvol[isInstanceBootEncryption] + encstr := enc.(string) + if ok && encstr != "" { + volTemplate.EncryptionKey = &vpcv1.EncryptionKeyIdentity{ + CRN: &encstr, + } + } + volcap := 100 + volcapint64 := int64(volcap) + volprof := "general-purpose" + volTemplate.Profile = &vpcv1.VolumeProfileIdentity{ Name: &volprof, } + volTemplate.Capacity = &volcapint64 deletebool := true + instanceproto.BootVolumeAttachment = &vpcv1.VolumeAttachmentPrototypeInstanceByImageContext{ DeleteVolumeOnInstanceDelete: &deletebool, Volume: volTemplate, @@ -961,7 +1180,202 @@ func instanceCreate(d *schema.ResourceData, meta interface{}, profile, name, vpc err = UpdateTagsUsingCRN(oldList, newList, meta, *instance.CRN) if err != nil { log.Printf( - "Error on create of resource vpc instance (%s) tags: %s", d.Id(), err) + "Error on create of resource instance (%s) tags: %s", d.Id(), err) + } + } + return nil +} + +func instanceCreateByVolume(d *schema.ResourceData, meta interface{}, profile, name, vpcID, zone string) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + instanceproto := &vpcv1.InstancePrototypeInstanceByVolume{ + Zone: &vpcv1.ZoneIdentity{ + Name: &zone, + }, + Profile: &vpcv1.InstanceProfileIdentity{ + Name: &profile, + }, + Name: &name, + VPC: &vpcv1.VPCIdentity{ + ID: &vpcID, + }, + } + if boot, ok := d.GetOk(isInstanceBootVolume); ok { + bootvol := boot.([]interface{})[0].(map[string]interface{}) + var volTemplate = &vpcv1.VolumeAttachmentVolumePrototypeInstanceByVolumeContext{} + + name, ok := bootvol[isInstanceBootAttachmentName] + namestr := name.(string) + if namestr != "" && ok { + volTemplate.Name = &namestr + } + enc, ok := bootvol[isInstanceBootEncryption] + encstr := enc.(string) + if ok && encstr != "" { + volTemplate.EncryptionKey = &vpcv1.EncryptionKeyIdentity{ + CRN: &encstr, + } + } + volcap := 100 + volcapint64 := int64(volcap) + volTemplate.Capacity = &volcapint64 + volprof := "general-purpose" + volTemplate.Profile = &vpcv1.VolumeProfileIdentity{ + Name: &volprof, + } + snapshotId, ok := bootvol[isInstanceVolumeSnapshot] + snapshotIdStr := snapshotId.(string) + if snapshotIdStr != "" && ok { + volTemplate.SourceSnapshot = &vpcv1.SnapshotIdentity{ + ID: &snapshotIdStr, + } + } + deletebool := true + instanceproto.BootVolumeAttachment = &vpcv1.VolumeAttachmentPrototypeInstanceByVolumeContext{ + DeleteVolumeOnInstanceDelete: &deletebool, + Volume: volTemplate, + } + } + + if primnicintf, ok := d.GetOk(isInstancePrimaryNetworkInterface); ok { + primnic := primnicintf.([]interface{})[0].(map[string]interface{}) + subnetintf, _ := primnic[isInstanceNicSubnet] + subnetintfstr := subnetintf.(string) + var primnicobj = &vpcv1.NetworkInterfacePrototype{} + primnicobj.Subnet = &vpcv1.SubnetIdentity{ + ID: &subnetintfstr, + } + name, _ := primnic[isInstanceNicName] + namestr := name.(string) + if namestr != "" { + primnicobj.Name = &namestr + } + ipv4, _ := primnic[isInstanceNicPrimaryIpv4Address] + ipv4str := ipv4.(string) + if ipv4str != "" { + primnicobj.PrimaryIpv4Address = &ipv4str + } + allowIPSpoofing, ok := primnic[isInstanceNicAllowIPSpoofing] + allowIPSpoofingbool := allowIPSpoofing.(bool) + if ok { + primnicobj.AllowIPSpoofing = &allowIPSpoofingbool + } + secgrpintf, ok := primnic[isInstanceNicSecurityGroups] + if ok { + secgrpSet := secgrpintf.(*schema.Set) + if secgrpSet.Len() != 0 { + var secgrpobjs = make([]vpcv1.SecurityGroupIdentityIntf, secgrpSet.Len()) + for i, secgrpIntf := range secgrpSet.List() { + secgrpIntfstr := secgrpIntf.(string) + secgrpobjs[i] = &vpcv1.SecurityGroupIdentity{ + ID: &secgrpIntfstr, + } + } + primnicobj.SecurityGroups = secgrpobjs + } + } + instanceproto.PrimaryNetworkInterface = primnicobj + } + + if nicsintf, ok := d.GetOk(isInstanceNetworkInterfaces); ok { + nics := nicsintf.([]interface{}) + var intfs []vpcv1.NetworkInterfacePrototype + for _, resource := range nics { + nic := resource.(map[string]interface{}) + nwInterface := &vpcv1.NetworkInterfacePrototype{} + subnetintf, _ := nic[isInstanceNicSubnet] + subnetintfstr := subnetintf.(string) + nwInterface.Subnet = &vpcv1.SubnetIdentity{ + ID: &subnetintfstr, + } + name, ok := nic[isInstanceNicName] + namestr := name.(string) + if ok && namestr != "" { + nwInterface.Name = &namestr + } + ipv4, _ := nic[isInstanceNicPrimaryIpv4Address] + ipv4str := ipv4.(string) + if ipv4str != "" { + nwInterface.PrimaryIpv4Address = &ipv4str + } + allowIPSpoofing, ok := nic[isInstanceNicAllowIPSpoofing] + allowIPSpoofingbool := allowIPSpoofing.(bool) + if ok { + nwInterface.AllowIPSpoofing = &allowIPSpoofingbool + } + secgrpintf, ok := nic[isInstanceNicSecurityGroups] + if ok { + secgrpSet := secgrpintf.(*schema.Set) + if secgrpSet.Len() != 0 { + var secgrpobjs = make([]vpcv1.SecurityGroupIdentityIntf, secgrpSet.Len()) + for i, secgrpIntf := range secgrpSet.List() { + secgrpIntfstr := secgrpIntf.(string) + secgrpobjs[i] = &vpcv1.SecurityGroupIdentity{ + ID: &secgrpIntfstr, + } + } + nwInterface.SecurityGroups = secgrpobjs + } + } + intfs = append(intfs, *nwInterface) + } + instanceproto.NetworkInterfaces = intfs + } + + keySet := d.Get(isInstanceKeys).(*schema.Set) + if keySet.Len() != 0 { + keyobjs := make([]vpcv1.KeyIdentityIntf, keySet.Len()) + for i, key := range keySet.List() { + keystr := key.(string) + keyobjs[i] = &vpcv1.KeyIdentity{ + ID: &keystr, + } + } + instanceproto.Keys = keyobjs + } + + if userdata, ok := d.GetOk(isInstanceUserData); ok { + userdatastr := userdata.(string) + instanceproto.UserData = &userdatastr + } + + if grp, ok := d.GetOk(isInstanceResourceGroup); ok { + grpstr := grp.(string) + instanceproto.ResourceGroup = &vpcv1.ResourceGroupIdentity{ + ID: &grpstr, + } + + } + + options := &vpcv1.CreateInstanceOptions{ + InstancePrototype: instanceproto, + } + + instance, response, err := sess.CreateInstance(options) + if err != nil { + log.Printf("[DEBUG] Instance err %s\n%s", err, response) + return err + } + d.SetId(*instance.ID) + + log.Printf("[INFO] Instance : %s", *instance.ID) + d.Set(isInstanceStatus, instance.Status) + + _, err = isWaitForInstanceAvailable(sess, d.Id(), d.Timeout(schema.TimeoutCreate), d) + if err != nil { + return err + } + + v := os.Getenv("IC_ENV_TAGS") + if _, ok := d.GetOk(isInstanceTags); ok || v != "" { + oldList, newList := d.GetChange(isInstanceTags) + err = UpdateTagsUsingCRN(oldList, newList, meta, *instance.CRN) + if err != nil { + log.Printf( + "Error on create of resource instance (%s) tags: %s", d.Id(), err) } } return nil @@ -978,6 +1392,8 @@ func resourceIBMisInstanceCreate(d *schema.ResourceData, meta interface{}) error vpcID := d.Get(isInstanceVPC).(string) zone := d.Get(isInstanceZone).(string) image := d.Get(isInstanceImage).(string) + snapshot := d.Get("boot_volume.0.snapshot").(string) + template := d.Get(isInstanceSourceTemplate).(string) if userDetails.generation == 1 { err := classicInstanceCreate(d, meta, profile, name, vpcID, zone, image) @@ -985,9 +1401,21 @@ func resourceIBMisInstanceCreate(d *schema.ResourceData, meta interface{}) error return err } } else { - err := instanceCreate(d, meta, profile, name, vpcID, zone, image) - if err != nil { - return err + if snapshot != "" { + err := instanceCreateByVolume(d, meta, profile, name, vpcID, zone) + if err != nil { + return err + } + } else if template != "" { + err := instanceCreateByTemplate(d, meta, profile, name, vpcID, zone, image, template) + if err != nil { + return err + } + } else { + err := instanceCreateByImage(d, meta, profile, name, vpcID, zone, image) + if err != nil { + return err + } } } @@ -1279,7 +1707,7 @@ func classicInstanceGet(d *schema.ResourceData, meta interface{}, id string) err if instance.BootVolumeAttachment != nil { bootVolList := make([]map[string]interface{}, 0) bootVol := map[string]interface{}{} - bootVol[isInstanceBootName] = *instance.BootVolumeAttachment.Name + bootVol[isInstanceBootAttachmentName] = *instance.BootVolumeAttachment.Name // getvolattoptions := &vpcclassicv1.GetVolumeAttachmentOptions{ // InstanceID: &ID, // ID: instance.BootVolumeAttachment.Volume.ID, @@ -1301,7 +1729,7 @@ func classicInstanceGet(d *schema.ResourceData, meta interface{}, id string) err tags, err := GetTagsUsingCRN(meta, *instance.CRN) if err != nil { log.Printf( - "Error on get of resource vpc Instance (%s) tags: %s", d.Id(), err) + "Error on get of resource Instance (%s) tags: %s", d.Id(), err) } d.Set(isInstanceTags, tags) @@ -1334,8 +1762,9 @@ func instanceGet(d *schema.ResourceData, meta interface{}, id string) error { d.SetId("") return nil } - return fmt.Errorf("Error Getting Instance: %s\n%s", err, response) + return fmt.Errorf("Error getting Instance: %s\n%s", err, response) } + d.Set(isInstanceName, *instance.Name) if instance.Profile != nil { d.Set(isInstanceProfile, *instance.Profile.Name) @@ -1447,11 +1876,12 @@ func instanceGet(d *schema.ResourceData, meta interface{}, id string) error { } d.Set(isInstanceVolumeAttachments, volList) } + if instance.BootVolumeAttachment != nil { bootVolList := make([]map[string]interface{}, 0) bootVol := map[string]interface{}{} if instance.BootVolumeAttachment.Volume != nil { - bootVol[isInstanceBootName] = *instance.BootVolumeAttachment.Volume.Name + bootVol[isInstanceBootAttachmentName] = *instance.BootVolumeAttachment.Volume.Name options := &vpcv1.GetVolumeOptions{ ID: instance.BootVolumeAttachment.Volume.ID, } @@ -1474,7 +1904,7 @@ func instanceGet(d *schema.ResourceData, meta interface{}, id string) error { tags, err := GetTagsUsingCRN(meta, *instance.CRN) if err != nil { log.Printf( - "Error on get of resource vpc Instance (%s) tags: %s", d.Id(), err) + "Error on get of resource Instance (%s) tags: %s", d.Id(), err) } d.Set(isInstanceTags, tags) @@ -1724,7 +2154,7 @@ func classicInstanceUpdate(d *schema.ResourceData, meta interface{}) error { err = UpdateTagsUsingCRN(oldList, newList, meta, *instance.CRN) if err != nil { log.Printf( - "Error on update of resource vpc Instance (%s) tags: %s", d.Id(), err) + "Error on update of resource Instance (%s) tags: %s", d.Id(), err) } } return nil @@ -1743,7 +2173,6 @@ func instanceUpdate(d *schema.ResourceData, meta interface{}) error { remove := expandStringList(ov.Difference(nv).List()) add := expandStringList(nv.Difference(ov).List()) - var volautoDelete bool if volumeautodeleteIntf, ok := d.GetOk(isInstanceVolAttVolAutoDelete); ok && volumeautodeleteIntf != nil { volautoDelete = volumeautodeleteIntf.(bool) @@ -2062,7 +2491,7 @@ func instanceUpdate(d *schema.ResourceData, meta interface{}) error { err = UpdateTagsUsingCRN(oldList, newList, meta, *instance.CRN) if err != nil { log.Printf( - "Error on update of resource vpc Instance (%s) tags: %s", d.Id(), err) + "Error on update of resource Instance (%s) tags: %s", d.Id(), err) } } return nil @@ -2214,7 +2643,6 @@ func instanceDelete(d *schema.ResourceData, meta interface{}, id string) error { if err != nil { return fmt.Errorf("Error Listing volume attachments to the instance: %s\n%s", err, response) } - for _, vol := range vols.VolumeAttachments { if *vol.Type == "data" { delvolattoptions := &vpcv1.DeleteInstanceVolumeAttachmentOptions{ @@ -2514,7 +2942,7 @@ func isClassicInstanceVolumeRefreshFunc(instanceC *vpcclassicv1.VpcClassicV1, id } func isWaitForInstanceVolumeAttached(instanceC *vpcv1.VpcV1, d *schema.ResourceData, id, volID string) (interface{}, error) { - log.Printf("Waiting for instance volume (%s) to be attched.", id) + log.Printf("Waiting for instance (%s) volume (%s) to be attached.", id, volID) stateConf := &resource.StateChangeConf{ Pending: []string{isInstanceVolumeAttaching}, diff --git a/ibm/resource_ibm_is_instance_test.go b/ibm/resource_ibm_is_instance_test.go index b91ccba8b39..d504eab4349 100644 --- a/ibm/resource_ibm_is_instance_test.go +++ b/ibm/resource_ibm_is_instance_test.go @@ -277,6 +277,45 @@ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVE }) } +func TestAccIBMISInstanceSnapshotRestore_basic(t *testing.T) { + var instance, instanceRestore string + vpcname := fmt.Sprintf("tf-vpc-%d", acctest.RandIntRange(10, 100)) + name := fmt.Sprintf("tf-instnace-%d", acctest.RandIntRange(10, 100)) + subnetname := fmt.Sprintf("tf-subnet-%d", acctest.RandIntRange(10, 100)) + publicKey := strings.TrimSpace(` +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR +`) + sshname := fmt.Sprintf("tf-ssh-%d", acctest.RandIntRange(10, 100)) + snapshot := fmt.Sprintf("tf-snapshot-%d", acctest.RandIntRange(10, 100)) + vsiRestore := fmt.Sprintf("tf-instancerestore-%d", acctest.RandIntRange(10, 100)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIBMISInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMISInstanceSnapshotRestoreConfig(vpcname, subnetname, sshname, publicKey, name, snapshot, vsiRestore), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMISInstanceExists("ibm_is_instance.testacc_instance", instance), + testAccCheckIBMISInstanceExists("ibm_is_instance.testacc_instance_restore", instanceRestore), + resource.TestCheckResourceAttr( + "ibm_is_instance.testacc_instance", "name", name), + resource.TestCheckResourceAttr( + "ibm_is_instance.testacc_instance", "zone", ISZoneName), + resource.TestCheckResourceAttr( + "ibm_is_snapshot.testacc_snapshot", "name", snapshot), + resource.TestCheckResourceAttr( + "ibm_is_instance.testacc_instance_restore", "zone", ISZoneName), + resource.TestCheckResourceAttr( + "ibm_is_instance.testacc_instance_restore", "name", vsiRestore), + resource.TestCheckResourceAttr( + "ibm_is_instance.testacc_instance_restore", "boot_volume.0.name", "boot-restore"), + ), + }, + }, + }) +} + func testAccCheckIBMISInstanceDestroy(s *terraform.State) error { userDetails, _ := testAccProvider.Meta().(ClientSession).BluemixUserDetails() @@ -386,6 +425,64 @@ func testAccCheckIBMISInstanceConfig(vpcname, subnetname, sshname, publicKey, na } }`, vpcname, subnetname, ISZoneName, ISCIDR, sshname, publicKey, name, isImage, instanceProfileName, ISZoneName) } +func testAccCheckIBMISInstanceSnapshotRestoreConfig(vpcname, subnetname, sshname, publicKey, name, snapshot, insRestore string) string { + return fmt.Sprintf(` + resource "ibm_is_vpc" "testacc_vpc" { + name = "%s" + } + + resource "ibm_is_subnet" "testacc_subnet" { + name = "%s" + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + total_ipv4_address_count = 16 + } + + resource "ibm_is_ssh_key" "testacc_sshkey" { + name = "%s" + public_key = "%s" + } + + resource "ibm_is_instance" "testacc_instance" { + name = "%s" + image = "%s" + profile = "%s" + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } + } + resource "ibm_is_snapshot" "testacc_snapshot" { + name = "%s" + source_volume = ibm_is_instance.testacc_instance.volume_attachments[0].volume_id + } + + resource "ibm_is_instance" "testacc_instance_restore" { + name = "%s" + profile = "%s" + boot_volume { + name = "boot-restore" + snapshot = ibm_is_snapshot.testacc_snapshot.id + } + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } + } + `, vpcname, subnetname, ISZoneName, sshname, publicKey, name, isImage, instanceProfileName, ISZoneName, snapshot, insRestore, instanceProfileName, ISZoneName) +} func testAccCheckIBMISInstanceConfigWithProfile(vpcname, subnetname, sshname, publicKey, name, isInstanceProfileName string) string { return fmt.Sprintf(` @@ -558,7 +655,6 @@ func testAccCheckIBMISInstancePlacement(vpcname, subnetname, sshname, publicKey, name = "%s" profile = "10iops-tier" zone = "%s" - # capacity= 200 } resource "ibm_is_instance" "testacc_instance" { @@ -576,7 +672,7 @@ func testAccCheckIBMISInstancePlacement(vpcname, subnetname, sshname, publicKey, } data "ibm_is_dedicated_host" "dhost"{ host_group = ibm_is_instance.testacc_instance.dedicated_host_group - name = "%s" + name = "%s" } `, vpcname, subnetname, ISZoneName, ISCIDR, sshname, publicKey, volName, ISZoneName, name, isImage, instanceProfileName, dedicatedHostGroupID, ISZoneName, dedicatedHostName) @@ -604,7 +700,6 @@ func testAccCheckIBMISInstanceVolumeAutoDelete(vpcname, subnetname, sshname, pub name = "%s" profile = "10iops-tier" zone = "%s" - # capacity= 200 } resource "ibm_is_instance" "testacc_instance" { @@ -614,11 +709,11 @@ func testAccCheckIBMISInstanceVolumeAutoDelete(vpcname, subnetname, sshname, pub primary_network_interface { subnet = ibm_is_subnet.testacc_subnet.id } - vpc = ibm_is_vpc.testacc_vpc.id - zone = "%s" - keys = [ibm_is_ssh_key.testacc_sshkey.id] - volumes = [ibm_is_volume.storage.id] - auto_delete_volume = true + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + volumes = [ibm_is_volume.storage.id] + auto_delete_volume = true }`, vpcname, subnetname, ISZoneName, ISCIDR, sshname, publicKey, volName, ISZoneName, name, isImage, instanceProfileName, ISZoneName) } @@ -644,7 +739,6 @@ func testAccCheckIBMISInstanceVolumeUpdate(vpcname, subnetname, sshname, publicK name = "%s" profile = "10iops-tier" zone = "%s" - # capacity= 200 } resource "ibm_is_instance" "testacc_instance" { diff --git a/ibm/resource_ibm_is_instance_volume_attachment.go b/ibm/resource_ibm_is_instance_volume_attachment.go new file mode 100644 index 00000000000..abd96b3fad3 --- /dev/null +++ b/ibm/resource_ibm_is_instance_volume_attachment.go @@ -0,0 +1,528 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/IBM/vpc-go-sdk/vpcv1" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + isInstanceId = "instance" + isInstanceVolAttVol = "volume" + isInstanceVolAttId = "volume_attachment_id" + isInstanceVolAttIops = "volume_iops" + isInstanceExistingVolume = "existing" + isInstanceVolAttName = "name" + isInstanceVolAttVolume = "volume" + isInstanceVolumeDeleteOnInstanceDelete = "delete_volume_on_instance_delete" + isInstanceVolumeDeleteOnAttachmentDelete = "delete_volume_on_attachment_delete" + isInstanceVolCapacity = "capacity" + isInstanceVolIops = "iops" + isInstanceVolEncryptionKey = "encryption_key" + isInstanceVolProfile = "profile" +) + +func resourceIBMISInstanceVolumeAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceIBMisInstanceVolumeAttachmentCreate, + Read: resourceIBMisInstanceVolumeAttachmentRead, + Update: resourceIBMisInstanceVolumeAttachmentUpdate, + Delete: resourceIBMisInstanceVolumeAttachmentDelete, + Exists: resourceIBMisInstanceVolumeAttachmentExists, + Importer: &schema.ResourceImporter{}, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + isInstanceId: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: InvokeValidator("ibm_is_instance_volume_attachment", isInstanceId), + Description: "Instance id", + }, + isInstanceVolAttId: { + Type: schema.TypeString, + Computed: true, + Description: "The unique identifier for this volume attachment", + }, + + isInstanceVolAttName: { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The user-defined name for this volume attachment.", + }, + + isInstanceVolumeDeleteOnInstanceDelete: { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "If set to true, when deleting the instance the volume will also be deleted.", + }, + isInstanceVolumeDeleteOnAttachmentDelete: { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "If set to true, when deleting the attachment, the volume will also be deleted.", + }, + isInstanceVolAttVol: { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{isInstanceVolIops, isInstanceVolumeAttVolumeReferenceName, isInstanceVolProfile, isInstanceVolCapacity, isInstanceVolumeSnapshot}, + ValidateFunc: InvokeValidator("ibm_is_instance_volume_attachment", isInstanceName), + Description: "Instance id", + }, + + isInstanceVolIops: { + Type: schema.TypeInt, + Computed: true, + Optional: true, + ForceNew: true, + Description: "The maximum I/O operations per second (IOPS) for the volume.", + }, + + isInstanceVolumeAttVolumeReferenceName: { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The unique user-defined name for this volume", + }, + + isInstanceVolProfile: { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Description: "The globally unique name for the volume profile to use for this volume.", + }, + isInstanceVolCapacity: { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + AtLeastOneOf: []string{isInstanceVolAttVol, isInstanceVolCapacity, isInstanceVolumeSnapshot}, + ConflictsWith: []string{isInstanceVolAttVol}, + ValidateFunc: InvokeValidator("ibm_is_instance_volume_attachment", isInstanceVolCapacity), + Description: "The capacity of the volume in gigabytes. The specified minimum and maximum capacity values for creating or updating volumes may expand in the future.", + }, + isInstanceVolEncryptionKey: { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Description: "The CRN of the [Key Protect Root Key](https://cloud.ibm.com/docs/key-protect?topic=key-protect-getting-started-tutorial) or [Hyper Protect Crypto Service Root Key](https://cloud.ibm.com/docs/hs-crypto?topic=hs-crypto-get-started) for this resource.", + }, + isInstanceVolumeSnapshot: { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + AtLeastOneOf: []string{isInstanceVolAttVol, isInstanceVolCapacity, isInstanceVolumeSnapshot}, + ConflictsWith: []string{isInstanceVolAttVol}, + Description: "The snapshot of the volume to be attached", + }, + isInstanceVolumeAttVolumeReferenceCrn: { + Type: schema.TypeString, + Computed: true, + Description: "The CRN for this volume", + }, + isInstanceVolumeAttVolumeReferenceDeleted: { + Type: schema.TypeString, + Computed: true, + Description: "Link to documentation about deleted resources", + }, + isInstanceVolumeAttVolumeReferenceHref: { + Type: schema.TypeString, + Computed: true, + Description: "The URL for this volume", + }, + + isInstanceVolumeAttDevice: { + Type: schema.TypeString, + Computed: true, + Description: "A unique identifier for the device which is exposed to the instance operating system", + }, + + isInstanceVolumeAttHref: { + Type: schema.TypeString, + Computed: true, + Description: "The URL for this volume attachment", + }, + + isInstanceVolumeAttStatus: { + Type: schema.TypeString, + Computed: true, + Description: "The status of this volume attachment, one of [ attached, attaching, deleting, detaching ]", + }, + + isInstanceVolumeAttType: { + Type: schema.TypeString, + Computed: true, + Description: "The type of volume attachment one of [ boot, data ]", + }, + }, + } +} + +func resourceIBMISInstanceVolumeAttachmentValidator() *ResourceValidator { + + validateSchema := make([]ValidateSchema, 1) + validateSchema = append(validateSchema, + ValidateSchema{ + Identifier: isInstanceId, + ValidateFunctionIdentifier: ValidateNoZeroValues, + Type: TypeString}) + validateSchema = append(validateSchema, + ValidateSchema{ + Identifier: isInstanceVolAttName, + ValidateFunctionIdentifier: ValidateRegexpLen, + Type: TypeString, + Required: true, + Regexp: `^([a-z]|[a-z][-a-z0-9]*[a-z0-9])$`, + MinValueLength: 1, + MaxValueLength: 63}) + + validateSchema = append(validateSchema, + ValidateSchema{ + Identifier: isInstanceVolCapacity, + ValidateFunctionIdentifier: IntBetween, + Type: TypeInt, + MinValue: "10", + MaxValue: "2000"}) + + ibmISInstanceVolumeAttachmentValidator := ResourceValidator{ResourceName: "ibm_is_instance_volume_attachment", Schema: validateSchema} + return &ibmISInstanceVolumeAttachmentValidator +} + +func instanceVolAttachmentCreate(d *schema.ResourceData, meta interface{}, instanceId string) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + + instanceVolAttproto := &vpcv1.CreateInstanceVolumeAttachmentOptions{ + InstanceID: &instanceId, + } + volumeIdStr := "" + if volumeId, ok := d.GetOk(isInstanceVolAttVol); ok { + volumeIdStr = volumeId.(string) + } + if volumeIdStr != "" { + var volProtoVol = &vpcv1.VolumeAttachmentPrototypeVolumeVolumeIdentity{} + volProtoVol.ID = &volumeIdStr + instanceVolAttproto.Volume = volProtoVol + } else { + var volProtoVol = &vpcv1.VolumeAttachmentPrototypeVolumeVolumePrototypeInstanceContext{} + if volname, ok := d.GetOk(isInstanceVolumeAttVolumeReferenceName); ok { + volnamestr := volname.(string) + volProtoVol.Name = &volnamestr + } + + volProfileStr := "general-purpose" + if volProfile, ok := d.GetOk(isInstanceVolProfile); ok { + volProfileStr = volProfile.(string) + volProtoVol.Profile = &vpcv1.VolumeProfileIdentity{ + Name: &volProfileStr, + } + } else { + volProtoVol.Profile = &vpcv1.VolumeProfileIdentity{ + Name: &volProfileStr, + } + } + volSnapshotStr := "" + if volSnapshot, ok := d.GetOk(isInstanceVolumeSnapshot); ok { + volSnapshotStr = volSnapshot.(string) + volProtoVol.SourceSnapshot = &vpcv1.SnapshotIdentity{ + ID: &volSnapshotStr, + } + } + var snapCapacity int64 + if volSnapshotStr != "" { + snapshotGet, _, err := sess.GetSnapshot(&vpcv1.GetSnapshotOptions{ + ID: &volSnapshotStr, + }) + if err != nil { + return fmt.Errorf("Error while getting snapshot details %q for instance %s: %q", volSnapshotStr, d.Id(), err) + } + snapCapacity = int64(int(*snapshotGet.MinimumCapacity)) + } + var volCapacityInt int64 + if volCapacity, ok := d.GetOk(isInstanceVolCapacity); ok { + volCapacityInt = int64(volCapacity.(int)) + if volCapacityInt != 0 && volCapacityInt > snapCapacity { + volProtoVol.Capacity = &volCapacityInt + } + } + var iops int64 + if volIops, ok := d.GetOk(isInstanceVolIops); ok { + iops = int64(volIops.(int)) + if iops != 0 { + volProtoVol.Iops = &iops + } + } + instanceVolAttproto.Volume = volProtoVol + } + + if autoDelete, ok := d.GetOk(isInstanceVolumeDeleteOnInstanceDelete); ok { + autoDeleteBool := autoDelete.(bool) + instanceVolAttproto.DeleteVolumeOnInstanceDelete = &autoDeleteBool + } + if name, ok := d.GetOk(isInstanceVolAttName); ok { + namestr := name.(string) + instanceVolAttproto.Name = &namestr + } + + instanceVolAtt, response, err := sess.CreateInstanceVolumeAttachment(instanceVolAttproto) + if err != nil { + log.Printf("[DEBUG] Instance volume attachment create err %s\n%s", err, response) + return fmt.Errorf("Error while attaching volume for instance %s: %q", instanceId, err) + } + _, err = isWaitForInstanceVolumeAttached(sess, d, instanceId, *instanceVolAtt.ID) + if err != nil { + return err + } + d.SetId(makeTerraformVolAttID(instanceId, *instanceVolAtt.ID)) + log.Printf("[INFO] Instance (%s) volume attachment : %s", instanceId, *instanceVolAtt.ID) + return nil +} + +func resourceIBMisInstanceVolumeAttachmentCreate(d *schema.ResourceData, meta interface{}) error { + instanceId := d.Get(isInstanceId).(string) + err := instanceVolAttachmentCreate(d, meta, instanceId) + if err != nil { + return err + } + return resourceIBMisInstanceVolumeAttachmentRead(d, meta) +} + +func resourceIBMisInstanceVolumeAttachmentRead(d *schema.ResourceData, meta interface{}) error { + instanceID, id, err := parseVolAttTerraformID(d.Id()) + if err != nil { + return err + } + err = instanceVolumeAttachmentGet(d, meta, instanceID, id) + if err != nil { + return err + } + return nil +} + +func instanceVolumeAttachmentGet(d *schema.ResourceData, meta interface{}, instanceId, id string) error { + instanceC, err := vpcClient(meta) + if err != nil { + return err + } + + getinsVolAttOptions := &vpcv1.GetInstanceVolumeAttachmentOptions{ + InstanceID: &instanceId, + ID: &id, + } + volumeAtt, response, err := instanceC.GetInstanceVolumeAttachment(getinsVolAttOptions) + if err != nil { + if response != nil && response.StatusCode == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error getting Instance volume attachment : %s\n%s", err, response) + } + d.Set(isInstanceId, instanceId) + + if volumeAtt.Volume != nil { + d.Set(isInstanceVolumeAttVolumeReferenceName, *volumeAtt.Volume.Name) + d.Set(isInstanceVolumeAttVolumeReferenceCrn, *volumeAtt.Volume.CRN) + if volumeAtt.Volume.Deleted != nil { + d.Set(isInstanceVolumeAttVolumeReferenceDeleted, *volumeAtt.Volume.Deleted.MoreInfo) + } + d.Set(isInstanceVolumeAttVolumeReferenceHref, *volumeAtt.Volume.Href) + } + d.Set(isInstanceVolumeDeleteOnInstanceDelete, *volumeAtt.DeleteVolumeOnInstanceDelete) + d.Set(isInstanceVolAttName, *volumeAtt.Name) + d.Set(isInstanceVolumeAttDevice, *volumeAtt.Device.ID) + d.Set(isInstanceVolumeAttHref, *volumeAtt.Href) + d.Set(isInstanceVolAttId, *volumeAtt.ID) + d.Set(isInstanceVolumeAttStatus, *volumeAtt.Status) + d.Set(isInstanceVolumeAttType, *volumeAtt.Type) + + volId := *volumeAtt.Volume.ID + getVolOptions := &vpcv1.GetVolumeOptions{ + ID: &volId, + } + volumeDetail, _, err := instanceC.GetVolume(getVolOptions) + if err != nil || volumeDetail == nil { + return fmt.Errorf("Error while getting volume details of volume %s ", id) + } + + d.Set(isInstanceVolAttVol, *volumeDetail.ID) + d.Set(isInstanceVolIops, *volumeDetail.Iops) + d.Set(isInstanceVolProfile, *volumeDetail.Profile.Name) + d.Set(isInstanceVolCapacity, *volumeDetail.Capacity) + if volumeDetail.EncryptionKey != nil { + d.Set(isInstanceVolEncryptionKey, *volumeDetail.EncryptionKey.CRN) + } + if volumeDetail.SourceSnapshot != nil { + d.Set(isInstanceVolumeSnapshot, *volumeDetail.SourceSnapshot.ID) + } + return nil +} + +func instanceVolAttUpdate(d *schema.ResourceData, meta interface{}) error { + instanceC, err := vpcClient(meta) + if err != nil { + return err + } + instanceId, id, err := parseVolAttTerraformID(d.Id()) + if err != nil { + return err + } + updateInstanceVolAttOptions := &vpcv1.UpdateInstanceVolumeAttachmentOptions{ + InstanceID: &instanceId, + ID: &id, + } + flag := false + volAttPatchModel := &vpcv1.VolumeAttachmentPatch{} + if d.HasChange(isInstanceVolumeDeleteOnInstanceDelete) { + autoDelete := d.Get(isInstanceVolumeDeleteOnInstanceDelete).(bool) + volAttPatchModel.DeleteVolumeOnInstanceDelete = &autoDelete + flag = true + } + + if d.HasChange(isInstanceVolAttName) { + name := d.Get(isInstanceVolAttName).(string) + volAttPatchModel.Name = &name + flag = true + } + if flag { + volAttPatchModelAsPatch, err := volAttPatchModel.AsPatch() + if err != nil || volAttPatchModelAsPatch == nil { + return fmt.Errorf("Error Instance volume attachment (%s) as patch : %s", id, err) + } + updateInstanceVolAttOptions.VolumeAttachmentPatch = volAttPatchModelAsPatch + + instanceVolAttUpdate, response, err := instanceC.UpdateInstanceVolumeAttachment(updateInstanceVolAttOptions) + if err != nil || instanceVolAttUpdate == nil { + log.Printf("[DEBUG] Instance volume attachment creation err %s\n%s", err, response) + return err + } + } + return nil +} + +func resourceIBMisInstanceVolumeAttachmentUpdate(d *schema.ResourceData, meta interface{}) error { + + err := instanceVolAttUpdate(d, meta) + if err != nil { + return err + } + return resourceIBMisInstanceVolumeAttachmentRead(d, meta) +} + +func instanceVolAttDelete(d *schema.ResourceData, meta interface{}, instanceId, id, volId string, volDelete bool) error { + instanceC, err := vpcClient(meta) + if err != nil { + return err + } + deleteInstanceVolAttOptions := &vpcv1.DeleteInstanceVolumeAttachmentOptions{ + InstanceID: &instanceId, + ID: &id, + } + _, err = instanceC.DeleteInstanceVolumeAttachment(deleteInstanceVolAttOptions) + _, err = isWaitForInstanceVolumeDetached(instanceC, d, instanceId, id) + if err != nil { + return fmt.Errorf("Error while deleting volume attachment (%s) from instance (%s) : %q", id, instanceId, err) + } + if volDelete { + deleteVolumeOptions := &vpcv1.DeleteVolumeOptions{ + ID: &volId, + } + response, err := instanceC.DeleteVolume(deleteVolumeOptions) + if err != nil { + return fmt.Errorf("Error while deleting volume : %s\n%s", err, response) + } + _, err = isWaitForVolumeDeleted(instanceC, volId, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return err + } + } + return nil +} + +func resourceIBMisInstanceVolumeAttachmentDelete(d *schema.ResourceData, meta interface{}) error { + instanceId, id, err := parseVolAttTerraformID(d.Id()) + if err != nil { + return err + } + volDelete := false + if volDeleteOk, ok := d.GetOk(isInstanceVolumeDeleteOnAttachmentDelete); ok { + volDelete = volDeleteOk.(bool) + } + volId := "" + if volIdOk, ok := d.GetOk(isInstanceVolAttVol); ok { + volId = volIdOk.(string) + } + err = instanceVolAttDelete(d, meta, instanceId, id, volId, volDelete) + if err != nil { + return err + } + d.SetId("") + return nil +} + +func resourceIBMisInstanceVolumeAttachmentExists(d *schema.ResourceData, meta interface{}) (bool, error) { + + instanceId, id, err := parseVolAttTerraformID(d.Id()) + if err != nil { + return false, err + } + exists, err := instanceVolAttExists(d, meta, instanceId, id) + return exists, err +} + +func instanceVolAttExists(d *schema.ResourceData, meta interface{}, instanceId, id string) (bool, error) { + instanceC, err := vpcClient(meta) + if err != nil { + return false, err + } + getinsvolattOptions := &vpcv1.GetInstanceVolumeAttachmentOptions{ + InstanceID: &instanceId, + ID: &id, + } + _, response, err := instanceC.GetInstanceVolumeAttachment(getinsvolattOptions) + if err != nil { + if response != nil && response.StatusCode == 404 { + return false, nil + } + return false, fmt.Errorf("Error getting Instance volume attachment: %s\n%s", err, response) + } + return true, nil +} + +func makeTerraformVolAttID(id1, id2 string) string { + // Include both instance id and volume attachment to create a unique Terraform id. As a bonus, + // we can extract the instance id as needed for API calls such as READ. + return fmt.Sprintf("%s/%s", id1, id2) +} + +func parseVolAttTerraformID(s string) (string, string, error) { + segments := strings.Split(s, "/") + if len(segments) != 2 { + return "", "", fmt.Errorf("invalid terraform Id %s (incorrect number of segments)", s) + } + if segments[0] == "" || segments[1] == "" { + return "", "", fmt.Errorf("invalid terraform Id %s (one or more empty segments)", s) + } + return segments[0], segments[1], nil +} diff --git a/ibm/resource_ibm_is_instance_volume_attachment_test.go b/ibm/resource_ibm_is_instance_volume_attachment_test.go new file mode 100644 index 00000000000..74e88daa4bd --- /dev/null +++ b/ibm/resource_ibm_is_instance_volume_attachment_test.go @@ -0,0 +1,142 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "errors" + "fmt" + "strings" + "testing" + + "github.com/IBM/vpc-go-sdk/vpcv1" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccIBMISInstanceVolumeAttachment_basic(t *testing.T) { + var instanceVolAtt string + vpcname := fmt.Sprintf("tf-vpc-%d", acctest.RandIntRange(10, 100)) + name := fmt.Sprintf("tf-instnace-%d", acctest.RandIntRange(10, 100)) + subnetname := fmt.Sprintf("tf-subnet-%d", acctest.RandIntRange(10, 100)) + publicKey := strings.TrimSpace(` +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR +`) + sshname := fmt.Sprintf("tf-ssh-%d", acctest.RandIntRange(10, 100)) + attName := fmt.Sprintf("tf-volatt-%d", acctest.RandIntRange(10, 100)) + autoDelete := true + volName := fmt.Sprintf("tf-vol-%d", acctest.RandIntRange(10, 100)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIBMISInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMISInstanceVolumeAttachmentConfig(vpcname, subnetname, sshname, publicKey, name, attName, volName, autoDelete), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMISInstanceVolumeAttachmentExists("ibm_is_instance_volume_attachment.testacc_att", instanceVolAtt), + resource.TestCheckResourceAttr( + "ibm_is_instance_volume_attachment.testacc_att", "name", attName), + resource.TestCheckResourceAttr( + "ibm_is_instance_volume_attachment.testacc_att", "delete_volume_on_instance_delete", fmt.Sprintf("%t", autoDelete)), + resource.TestCheckResourceAttr( + "ibm_is_instance_volume_attachment.testacc_att", "capacity", "20"), + ), + }, + }, + }) +} + +func testAccCheckIBMISInstanceVolumeAttachmentDestroy(s *terraform.State) error { + + instanceC, _ := testAccProvider.Meta().(ClientSession).VpcV1API() + for _, rs := range s.RootModule().Resources { + if rs.Type != "ibm_is_instance_volume_attachment" { + continue + } + getinsvolAttOptions := &vpcv1.GetInstanceVolumeAttachmentOptions{ + ID: &rs.Primary.ID, + } + _, _, err := instanceC.GetInstanceVolumeAttachment(getinsvolAttOptions) + + if err == nil { + return fmt.Errorf("instance volume attachment still exists: %s", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckIBMISInstanceVolumeAttachmentExists(n string, instanceVolAtt string) 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 errors.New("No Record ID is set") + } + instanceId, id, err := parseVolAttTerraformID(rs.Primary.ID) + instanceC, _ := testAccProvider.Meta().(ClientSession).VpcV1API() + getinsVolAttOptions := &vpcv1.GetInstanceVolumeAttachmentOptions{ + ID: &id, + InstanceID: &instanceId, + } + foundins, _, err := instanceC.GetInstanceVolumeAttachment(getinsVolAttOptions) + if err != nil { + return err + } + instanceVolAtt = *foundins.ID + return nil + } +} + +func testAccCheckIBMISInstanceVolumeAttachmentConfig(vpcname, subnetname, sshname, publicKey, name, attName, volName string, autoDelete bool) string { + return fmt.Sprintf(` + resource "ibm_is_vpc" "testacc_vpc" { + name = "%s" + } + + resource "ibm_is_subnet" "testacc_subnet" { + name = "%s" + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + total_ipv4_address_count = 16 + } + + resource "ibm_is_ssh_key" "testacc_sshkey" { + name = "%s" + public_key = "%s" + } + + resource "ibm_is_instance" "testacc_instance" { + name = "%s" + image = "%s" + profile = "%s" + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } + } + resource "ibm_is_instance_volume_attachment" "testacc_att" { + instance = ibm_is_instance.testacc_instance.id + + name = "%s" + profile = "general-purpose" + capacity = "20" + + delete_volume_on_instance_delete = %t + volume_name = "%s" + } + + `, vpcname, subnetname, ISZoneName, sshname, publicKey, name, isImage, instanceProfileName, ISZoneName, attName, autoDelete, volName) +} diff --git a/ibm/resource_ibm_is_snapshot.go b/ibm/resource_ibm_is_snapshot.go new file mode 100644 index 00000000000..8d0fe56d5dc --- /dev/null +++ b/ibm/resource_ibm_is_snapshot.go @@ -0,0 +1,477 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/IBM/vpc-go-sdk/vpcv1" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + isSnapshotName = "name" + isSnapshotResourceGroup = "resource_group" + isSnapshotSourceVolume = "source_volume" + isSnapshotSourceImage = "source_image" + isSnapshotUserTags = "user_tags" + isSnapshotCRN = "crn" + isSnapshotHref = "href" + isSnapshotEncryption = "encryption" + isSnapshotEncryptionKey = "encryption_key" + isSnapshotOperatingSystem = "operating_system" + isSnapshotLCState = "lifecycle_state" + isSnapshotMinCapacity = "minimum_capacity" + isSnapshotResourceType = "resource_type" + isSnapshotSize = "size" + isSnapshotBootable = "bootable" + isSnapshotDeleting = "deleting" + isSnapshotDeleted = "deleted" + isSnapshotAvailable = "stable" + isSnapshotFailed = "failed" + isSnapshotPending = "pending" + isSnapshotSuspended = "suspended" + isSnapshotUpdating = "updating" + isSnapshotWaiting = "waiting" +) + +func resourceIBMSnapshot() *schema.Resource { + return &schema.Resource{ + Create: resourceIBMISSnapshotCreate, + Read: resourceIBMISSnapshotRead, + Update: resourceIBMISSnapshotUpdate, + Delete: resourceIBMISSnapshotDelete, + Exists: resourceIBMISSnapshotExists, + Importer: &schema.ResourceImporter{}, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + CustomizeDiff: customdiff.Sequence( + func(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { + return resourceTagsCustomizeDiff(diff) + }, + ), + + Schema: map[string]*schema.Schema{ + + isSnapshotName: { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: InvokeValidator("ibm_is_snapshot", isSnapshotName), + Description: "Snapshot name", + }, + + isSnapshotResourceGroup: { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Description: "Resource group info", + }, + + isSnapshotSourceVolume: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Snapshot source volume", + }, + + isSnapshotSourceImage: { + Type: schema.TypeString, + Computed: true, + Description: "If present, the image id from which the data on this volume was most directly provisioned.", + }, + + isSnapshotOperatingSystem: { + Type: schema.TypeString, + Computed: true, + Description: "The globally unique name for the operating system included in this image", + }, + + isSnapshotBootable: { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates if a boot volume attachment can be created with a volume created from this snapshot", + }, + + isSnapshotLCState: { + Type: schema.TypeString, + Computed: true, + Description: "Snapshot lifecycle state", + }, + isSnapshotCRN: { + Type: schema.TypeString, + Computed: true, + Description: "The crn of the resource", + }, + isSnapshotEncryption: { + Type: schema.TypeString, + Computed: true, + Description: "Encryption type of the snapshot", + }, + isSnapshotEncryptionKey: { + Type: schema.TypeString, + Computed: true, + Description: "A reference to the root key used to wrap the data encryption key for the source volume.", + }, + + isSnapshotHref: { + Type: schema.TypeString, + Computed: true, + Description: "URL for the snapshot", + }, + + isSnapshotMinCapacity: { + Type: schema.TypeInt, + Computed: true, + Description: "Minimum capacity of the snapshot", + }, + isSnapshotResourceType: { + Type: schema.TypeString, + Computed: true, + Description: "The resource type of the snapshot", + }, + + isSnapshotSize: { + Type: schema.TypeInt, + Computed: true, + Description: "The size of the snapshot", + }, + }, + } +} + +func resourceIBMISSnapshotValidator() *ResourceValidator { + + validateSchema := make([]ValidateSchema, 1) + validateSchema = append(validateSchema, + ValidateSchema{ + Identifier: isSnapshotName, + ValidateFunctionIdentifier: ValidateRegexpLen, + Type: TypeString, + Required: true, + Regexp: `^([a-z]|[a-z][-a-z0-9]*[a-z0-9])$`, + MinValueLength: 1, + MaxValueLength: 63}) + ibmISSnapshotResourceValidator := ResourceValidator{ResourceName: "ibm_is_snapshot", Schema: validateSchema} + return &ibmISSnapshotResourceValidator +} + +func resourceIBMISSnapshotCreate(d *schema.ResourceData, meta interface{}) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + options := &vpcv1.CreateSnapshotOptions{} + if snapshotName, ok := d.GetOk(isSnapshotName); ok { + name := snapshotName.(string) + options.Name = &name + } + if sourceVolume, ok := d.GetOk(isSnapshotSourceVolume); ok { + sv := sourceVolume.(string) + options.SourceVolume = &vpcv1.VolumeIdentity{ + ID: &sv, + } + } + if grp, ok := d.GetOk(isVPCResourceGroup); ok { + rg := grp.(string) + options.ResourceGroup = &vpcv1.ResourceGroupIdentity{ + ID: &rg, + } + } + + log.Printf("[DEBUG] Snapshot create") + + snapshot, response, err := sess.CreateSnapshot(options) + if err != nil || snapshot == nil { + return fmt.Errorf("Error creating Snapshot %s\n%s", err, response) + } + + d.SetId(*snapshot.ID) + log.Printf("[INFO] Snapshot : %s", *snapshot.ID) + + _, err = isWaitForSnapshotAvailable(sess, d.Id(), d.Timeout(schema.TimeoutCreate)) + + if err != nil { + return err + } + + return resourceIBMISSnapshotRead(d, meta) +} + +func isWaitForSnapshotAvailable(sess *vpcv1.VpcV1, id string, timeout time.Duration) (interface{}, error) { + log.Printf("Waiting for Snapshot (%s) to be available.", id) + + stateConf := &resource.StateChangeConf{ + Pending: []string{isSnapshotPending}, + Target: []string{isSnapshotAvailable, isSnapshotFailed}, + Refresh: isSnapshotRefreshFunc(sess, id), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 10 * time.Second, + } + + return stateConf.WaitForState() +} + +func isSnapshotRefreshFunc(sess *vpcv1.VpcV1, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + getSnapshotOptions := &vpcv1.GetSnapshotOptions{ + ID: &id, + } + snapshot, response, err := sess.GetSnapshot(getSnapshotOptions) + if err != nil { + return nil, isSnapshotFailed, fmt.Errorf("Error getting Snapshot : %s\n%s", err, response) + } + + if *snapshot.LifecycleState == isSnapshotAvailable { + return snapshot, *snapshot.LifecycleState, nil + } else if *snapshot.LifecycleState == isSnapshotFailed { + return snapshot, *snapshot.LifecycleState, fmt.Errorf("Snapshot (%s) went into failed state during the operation \n [WARNING] Running terraform apply again will remove the tainted snapshot and attempt to create the snapshot again replacing the previous configuration", *snapshot.ID) + } + + return snapshot, isSnapshotPending, nil + } +} + +func resourceIBMISSnapshotRead(d *schema.ResourceData, meta interface{}) error { + id := d.Id() + err := snapshotGet(d, meta, id) + if err != nil { + return err + } + return nil +} + +func snapshotGet(d *schema.ResourceData, meta interface{}, id string) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + getSnapshotOptions := &vpcv1.GetSnapshotOptions{ + ID: &id, + } + snapshot, response, err := sess.GetSnapshot(getSnapshotOptions) + if err != nil { + if response != nil && response.StatusCode == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error getting Snapshot : %s\n%s", err, response) + } + + d.SetId(*snapshot.ID) + d.Set(isSnapshotName, *snapshot.Name) + d.Set(isSnapshotHref, *snapshot.Href) + d.Set(isSnapshotCRN, *snapshot.CRN) + d.Set(isSnapshotMinCapacity, *snapshot.MinimumCapacity) + d.Set(isSnapshotSize, *snapshot.Size) + d.Set(isSnapshotEncryption, *snapshot.Encryption) + d.Set(isSnapshotLCState, *snapshot.LifecycleState) + d.Set(isSnapshotResourceType, *snapshot.ResourceType) + d.Set(isSnapshotBootable, *snapshot.Bootable) + if snapshot.ResourceGroup != nil && snapshot.ResourceGroup.ID != nil { + d.Set(isSnapshotResourceGroup, *snapshot.ResourceGroup.ID) + } + if snapshot.SourceVolume != nil && snapshot.SourceVolume.ID != nil { + d.Set(isSnapshotSourceVolume, *snapshot.SourceVolume.ID) + } + + if snapshot.SourceImage != nil && snapshot.SourceImage.ID != nil { + d.Set(isSnapshotSourceImage, *snapshot.SourceImage.ID) + } + + if snapshot.OperatingSystem != nil && snapshot.OperatingSystem.Name != nil { + d.Set(isSnapshotOperatingSystem, *snapshot.OperatingSystem.Name) + } + return nil +} + +func resourceIBMISSnapshotUpdate(d *schema.ResourceData, meta interface{}) error { + id := d.Id() + + name := "" + hasChanged := false + + if d.HasChange(isSnapshotName) { + name = d.Get(isSnapshotName).(string) + hasChanged = true + } + err := snapshotUpdate(d, meta, id, name, hasChanged) + if err != nil { + return err + } + return resourceIBMISSnapshotRead(d, meta) +} + +func snapshotUpdate(d *schema.ResourceData, meta interface{}, id, name string, hasChanged bool) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + if d.HasChange(isSnapshotName) { + updateSnapshotOptions := &vpcv1.UpdateSnapshotOptions{ + ID: &id, + } + snapshotPatchModel := &vpcv1.SnapshotPatch{ + Name: &name, + } + snapshotPatch, err := snapshotPatchModel.AsPatch() + if err != nil { + return fmt.Errorf("Error calling asPatch for SnapshotPatch: %s", err) + } + updateSnapshotOptions.SnapshotPatch = snapshotPatch + _, response, err := sess.UpdateSnapshot(updateSnapshotOptions) + if err != nil { + return fmt.Errorf("Error updating Snapshot : %s\n%s", err, response) + } + _, err = isWaitForSnapshotUpdate(sess, d.Id(), d.Timeout(schema.TimeoutCreate)) + if err != nil { + return err + } + } + return nil +} + +func isWaitForSnapshotUpdate(sess *vpcv1.VpcV1, id string, timeout time.Duration) (interface{}, error) { + log.Printf("Waiting for Snapshot (%s) to be available.", id) + + stateConf := &resource.StateChangeConf{ + Pending: []string{isSnapshotUpdating}, + Target: []string{isSnapshotAvailable, isSnapshotFailed}, + Refresh: isSnapshotUpdateRefreshFunc(sess, id), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 10 * time.Second, + } + return stateConf.WaitForState() +} + +func isSnapshotUpdateRefreshFunc(sess *vpcv1.VpcV1, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + getSnapshotOptions := &vpcv1.GetSnapshotOptions{ + ID: &id, + } + snapshot, response, err := sess.GetSnapshot(getSnapshotOptions) + if err != nil { + return nil, isSnapshotFailed, fmt.Errorf("Error getting Snapshot : %s\n%s", err, response) + } + + if *snapshot.LifecycleState == isSnapshotAvailable || *snapshot.LifecycleState == isSnapshotFailed { + return snapshot, *snapshot.LifecycleState, nil + } else if *snapshot.LifecycleState == isSnapshotFailed { + return snapshot, *snapshot.LifecycleState, fmt.Errorf("Snapshot (%s) went into failed state during the operation \n [WARNING] Running terraform apply again will remove the tainted snapshot and attempt to create the snapshot again replacing the previous configuration", *snapshot.ID) + } + + return snapshot, isSnapshotUpdating, nil + } +} + +func resourceIBMISSnapshotDelete(d *schema.ResourceData, meta interface{}) error { + id := d.Id() + err := snapshotDelete(d, meta, id) + if err != nil { + return err + } + d.SetId("") + return nil +} + +func snapshotDelete(d *schema.ResourceData, meta interface{}, id string) error { + sess, err := vpcClient(meta) + if err != nil { + return err + } + + getSnapshotOptions := &vpcv1.GetSnapshotOptions{ + ID: &id, + } + _, response, err := sess.GetSnapshot(getSnapshotOptions) + if err != nil { + if response != nil && response.StatusCode == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error getting Snapshot (%s): %s\n%s", id, err, response) + } + + deleteSnapshotOptions := &vpcv1.DeleteSnapshotOptions{ + ID: &id, + } + response, err = sess.DeleteSnapshot(deleteSnapshotOptions) + if err != nil { + return fmt.Errorf("Error deleting Snapshot : %s\n%s", err, response) + } + _, err = isWaitForSnapshotDeleted(sess, id, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return err + } + d.SetId("") + return nil +} + +func isWaitForSnapshotDeleted(sess *vpcv1.VpcV1, id string, timeout time.Duration) (interface{}, error) { + log.Printf("Waiting for Snapshot (%s) to be deleted.", id) + + stateConf := &resource.StateChangeConf{ + Pending: []string{isSnapshotDeleting}, + Target: []string{isSnapshotDeleted, isSnapshotFailed}, + Refresh: isSnapshotDeleteRefreshFunc(sess, id), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 10 * time.Second, + } + + return stateConf.WaitForState() +} + +func isSnapshotDeleteRefreshFunc(sess *vpcv1.VpcV1, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Refresh function for Snapshot delete.") + getSnapshotOptions := &vpcv1.GetSnapshotOptions{ + ID: &id, + } + snapshot, response, err := sess.GetSnapshot(getSnapshotOptions) + if err != nil { + if response != nil && response.StatusCode == 404 { + return snapshot, isSnapshotDeleted, nil + } + return nil, isSnapshotFailed, fmt.Errorf("The Snapshot %s failed to delete: %s\n%s", id, err, response) + } + return snapshot, *snapshot.LifecycleState, nil + } +} + +func resourceIBMISSnapshotExists(d *schema.ResourceData, meta interface{}) (bool, error) { + id := d.Id() + exists, err := snapshotExists(d, meta, id) + return exists, err +} + +func snapshotExists(d *schema.ResourceData, meta interface{}, id string) (bool, error) { + sess, err := vpcClient(meta) + if err != nil { + return false, err + } + getSnapshotOptions := &vpcv1.GetSnapshotOptions{ + ID: &id, + } + _, response, err := sess.GetSnapshot(getSnapshotOptions) + if err != nil { + if response != nil && response.StatusCode == 404 { + return false, nil + } + return false, fmt.Errorf("Error getting Snapshot: %s\n%s", err, response) + } + return true, nil +} diff --git a/ibm/resource_ibm_is_snapshot_test.go b/ibm/resource_ibm_is_snapshot_test.go new file mode 100644 index 00000000000..04d38400f01 --- /dev/null +++ b/ibm/resource_ibm_is_snapshot_test.go @@ -0,0 +1,178 @@ +// Copyright IBM Corp. 2017, 2021 All Rights Reserved. +// Licensed under the Mozilla Public License v2.0 + +package ibm + +import ( + "errors" + "fmt" + "strings" + "testing" + + "github.com/IBM/vpc-go-sdk/vpcv1" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccIBMISSnapshot_basic(t *testing.T) { + var snapshot string + vpcname := fmt.Sprintf("tf-vpc-%d", acctest.RandIntRange(10, 100)) + name := fmt.Sprintf("tf-instnace-%d", acctest.RandIntRange(10, 100)) + subnetname := fmt.Sprintf("tf-subnet-%d", acctest.RandIntRange(10, 100)) + publicKey := strings.TrimSpace(` +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR +`) + sshname := fmt.Sprintf("tf-ssh-%d", acctest.RandIntRange(10, 100)) + volname := fmt.Sprintf("tf-vol-%d", acctest.RandIntRange(10, 100)) + name1 := fmt.Sprintf("tfsnapshotuat-%d", acctest.RandIntRange(10, 100)) + name2 := fmt.Sprintf("tfsnapshotuat-%d", acctest.RandIntRange(10, 100)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIBMISSnapshotDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMISSnapshotConfig(vpcname, subnetname, sshname, publicKey, volname, name, name1), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMISSnapshotExists("ibm_is_snapshot.testacc_snapshot", snapshot), + resource.TestCheckResourceAttr( + "ibm_is_snapshot.testacc_snapshot", "name", name1), + ), + }, + { + Config: testAccCheckIBMISSnapshotConfigUpdate(vpcname, subnetname, sshname, publicKey, volname, name, name2), + Check: resource.ComposeTestCheckFunc( + testAccCheckIBMISSnapshotExists("ibm_is_snapshot.testacc_snapshot", snapshot), + resource.TestCheckResourceAttr( + "ibm_is_snapshot.testacc_snapshot", "name", name2), + ), + }, + }, + }) +} + +func testAccCheckIBMISSnapshotDestroy(s *terraform.State) error { + sess, _ := testAccProvider.Meta().(ClientSession).VpcV1API() + for _, rs := range s.RootModule().Resources { + if rs.Type != "ibm_is_snapshot" { + continue + } + + getSnapshotoptions := &vpcv1.GetSnapshotOptions{ + ID: &rs.Primary.ID, + } + snapshot, _, err := sess.GetSnapshot(getSnapshotoptions) + + if err == nil && *snapshot.LifecycleState != "deleted" { + return fmt.Errorf("snapshot still exists: %s", rs.Primary.ID) + } + } + return nil +} + +func testAccCheckIBMISSnapshotExists(n, sID string) 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 errors.New("No Record ID is set") + } + sess, _ := testAccProvider.Meta().(ClientSession).VpcV1API() + getSnapshotoptions := &vpcv1.GetSnapshotOptions{ + ID: &rs.Primary.ID, + } + snapshotID, _, err := sess.GetSnapshot(getSnapshotoptions) + if err != nil { + return err + } + sID = *snapshotID.ID + return nil + } +} + +func testAccCheckIBMISSnapshotConfig(vpcname, subnetname, sshname, publicKey, volname, name, sname string) string { + return fmt.Sprintf(` + resource "ibm_is_vpc" "testacc_vpc" { + name = "%s" + } + + resource "ibm_is_subnet" "testacc_subnet" { + name = "%s" + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + total_ipv4_address_count = 16 + } + + resource "ibm_is_ssh_key" "testacc_sshkey" { + name = "%s" + public_key = "%s" + } + + resource "ibm_is_instance" "testacc_instance" { + name = "%s" + image = "%s" + profile = "%s" + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } + } + resource "ibm_is_snapshot" "testacc_snapshot" { + name = "%s" + source_volume = ibm_is_instance.testacc_instance.volume_attachments[0].volume_id +}`, vpcname, subnetname, ISZoneName, sshname, publicKey, name, isImage, instanceProfileName, ISZoneName, sname) + +} + +func testAccCheckIBMISSnapshotConfigUpdate(vpcname, subnetname, sshname, publicKey, volname, name, sname string) string { + return fmt.Sprintf(` + resource "ibm_is_vpc" "testacc_vpc" { + name = "%s" + } + + resource "ibm_is_subnet" "testacc_subnet" { + name = "%s" + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + total_ipv4_address_count = 16 + } + + resource "ibm_is_ssh_key" "testacc_sshkey" { + name = "%s" + public_key = "%s" + } + + resource "ibm_is_instance" "testacc_instance" { + name = "%s" + image = "%s" + profile = "%s" + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "%s" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } + } + resource "ibm_is_snapshot" "testacc_snapshot" { + name = "%s" + source_volume = ibm_is_instance.testacc_instance.volume_attachments[0].volume_id + } +`, vpcname, subnetname, ISZoneName, sshname, publicKey, name, isImage, instanceProfileName, ISZoneName, sname) + +} diff --git a/ibm/resource_ibm_is_volume.go b/ibm/resource_ibm_is_volume.go index 667ee8e344b..3c9a699bb3f 100644 --- a/ibm/resource_ibm_is_volume.go +++ b/ibm/resource_ibm_is_volume.go @@ -35,6 +35,8 @@ const ( isVolumeProvisioning = "provisioning" isVolumeProvisioningDone = "done" isVolumeResourceGroup = "resource_group" + isVolumeSourceSnapshot = "source_snapshot" + isVolumeDeleteAllSnapshots = "delete_all_snapshots" ) func resourceIBMISVolume() *schema.Resource { @@ -140,6 +142,16 @@ func resourceIBMISVolume() *schema.Resource { }, }, + isVolumeSourceSnapshot: { + Type: schema.TypeString, + Computed: true, + Description: "Identifier of the snapshot from which this volume was cloned", + }, + isVolumeDeleteAllSnapshots: { + Type: schema.TypeBool, + Optional: true, + Description: "Deletes all snapshots created from this volume", + }, isVolumeTags: { Type: schema.TypeSet, Optional: true, @@ -325,6 +337,9 @@ func volGet(d *schema.ResourceData, meta interface{}, id string) error { d.Set(isVolumeIops, *vol.Iops) d.Set(isVolumeCapacity, *vol.Capacity) d.Set(isVolumeCrn, *vol.CRN) + if vol.SourceSnapshot != nil { + d.Set(isVolumeSourceSnapshot, *vol.SourceSnapshot.ID) + } d.Set(isVolumeStatus, *vol.Status) //set the status reasons if vol.StatusReasons != nil { @@ -365,24 +380,33 @@ func resourceIBMISVolumeUpdate(d *schema.ResourceData, meta interface{}) error { id := d.Id() name := "" hasChanged := false + delete := false + + if delete_all_snapshots, ok := d.GetOk(isVolumeDeleteAllSnapshots); ok && delete_all_snapshots.(bool) { + delete = true + } if d.HasChange(isVolumeName) { name = d.Get(isVolumeName).(string) hasChanged = true } - err := volUpdate(d, meta, id, name, hasChanged) + err := volUpdate(d, meta, id, name, hasChanged, delete) if err != nil { return err } return resourceIBMISVolumeRead(d, meta) } -func volUpdate(d *schema.ResourceData, meta interface{}, id, name string, hasChanged bool) error { +func volUpdate(d *schema.ResourceData, meta interface{}, id, name string, hasChanged, delete bool) error { sess, err := vpcClient(meta) if err != nil { return err } + if delete { + deleteAllSnapshots(sess, id) + } + if d.HasChange(isVolumeTags) { options := &vpcv1.GetVolumeOptions{ ID: &id, @@ -581,3 +605,13 @@ func isVolumeRefreshFunc(client *vpcv1.VpcV1, id string) resource.StateRefreshFu return vol, isVolumeProvisioning, nil } } + +func deleteAllSnapshots(sess *vpcv1.VpcV1, id string) error { + delete_all_snapshots := new(vpcv1.DeleteSnapshotsOptions) + delete_all_snapshots.SourceVolumeID = &id + response, err := sess.DeleteSnapshots(delete_all_snapshots) + if err != nil { + return fmt.Errorf("Error deleting snapshots from volume %s\n%s", err, response) + } + return nil +} diff --git a/website/docs/d/is_instance_volume_attachment.html.markdown b/website/docs/d/is_instance_volume_attachment.html.markdown new file mode 100644 index 00000000000..a3d276faa7b --- /dev/null +++ b/website/docs/d/is_instance_volume_attachment.html.markdown @@ -0,0 +1,48 @@ +--- +subcategory: "VPC infrastructure" +layout: "ibm" +page_title: "IBM : Instance Volume Attachment" +description: |- + Manages IBM Cloud infrastructure instance volume attachment. +--- + +# ibm_is_instance_volume_attachment +Retrieve information of an existing IBM Cloud Infrastructure instance volume attachment as a read-only data source. For more information, about VPC virtual server instances, see [Managing virtual server instances](https://cloud.ibm.com/docs/vpc?topic=vpc-managing-virtual-server-instances). + + +## Example usage + +```terraform + +data "ibm_is_instance_volume_attachment" "ds_vsi_va" { + instance = "xx-x-x-x-xxxxx + name = "test-volume" +} + +``` + +## Argument reference +Review the argument references that you can specify for your data source. + +- `name` - (Required, String) The name of the volume attachment. +- `instance` - (Required, String) The id of the instance. + +## Attribute reference +In addition to all argument reference list, you can access the following attribute references after your data source is created. + +- `delete_volume_on_instance_delete` - (Boolean) If set to true, when deleting the instance the volume will also be deleted. +- `device`- (String) A unique identifier for the device which is exposed to the instance operating system. +- `href` - (String) The URL for this volume attachment. +- `id` - (String) The ID of the instance volume attachment. The ID is composed of `/`. +- `name`- (String) The user-defined name for this volume attachment. +- `status` - (String) The status of this volume attachment [ attached, attaching, deleting, detaching ]. +- `type` - (String) The type of volume attachment [ boot, data ]. +- `volume_attachment_id` - (String) The unique identifier for this volume attachment. +- `volume` - (List) The attached volume. + + Nested scheme for `volume`: + - `crn` - (String) The CRN for this volume. + - `deleted` - (String) If present, this property indicates the referenced resource has been deleted and provides some supplementary information. + - `href` - (String) The URL for this volume. + - `id` - (String) The unique identifier for this volume. + - `name` - (String) The unique user-defined name for this volume. diff --git a/website/docs/d/is_instance_volume_attachments.html.markdown b/website/docs/d/is_instance_volume_attachments.html.markdown new file mode 100644 index 00000000000..49adf41f02f --- /dev/null +++ b/website/docs/d/is_instance_volume_attachments.html.markdown @@ -0,0 +1,48 @@ +--- +subcategory: "VPC infrastructure" +layout: "ibm" +page_title: "IBM : Instance Volume Attachments" +description: |- + Manages IBM Cloud infrastructure image. +--- + +# ibm_is_instance_volume_attachments +Retrieve information of an existing IBM Cloud Infrastructure instance volume attachments as a read-only data source. For more information, about VPC virtual server instances, see [Managing virtual server instances](https://cloud.ibm.com/docs/vpc?topic=vpc-managing-virtual-server-instances). + + +## Example usage + +```terraform + +data "ibm_is_instance_volume_attachments" "ds_vsi_vas" { + instance = "xx-x-x-x-xxxxx +} + +``` + +## Argument reference +Review the argument references that you can specify for your data source. + +- `instance` - (Required, String) The id of the instance. + +## Attribute reference +In addition to all argument reference list, you can access the following attribute references after your data source is created. + +- `volume_attachments`- (List of Object) A list of volume attachments on an instance. + + Nested scheme for `volume_attachments`: + - `delete_volume_on_instance_delete` - (Boolean) If set to true, when deleting the instance the volume will also be deleted. + - `device`- (String) A unique identifier for the device which is exposed to the instance operating system. + - `href` - (String) The URL for this volume attachment. + - `name`- (String) The user-defined name for this volume attachment. + - `status` - (String) The status of this volume attachment [ attached, attaching, deleting, detaching ]. + - `type` - (String) The type of volume attachment [ boot, data ]. + - `volume_attachment_id` - (String) The unique identifier for this volume attachment. + - `volume` - (List) The attached volume. + + Nested scheme for `volume`: + - `crn` - (String) The CRN for this volume. + - `deleted` - (String) If present, this property indicates the referenced resource has been deleted and provides some supplementary information. + - `href` - (String) The URL for this volume. + - `id` - (String) The unique identifier for this volume. + - `name` - (String) The unique user-defined name for this volume. diff --git a/website/docs/d/is_snapshot.html.markdown b/website/docs/d/is_snapshot.html.markdown new file mode 100644 index 00000000000..1f136881841 --- /dev/null +++ b/website/docs/d/is_snapshot.html.markdown @@ -0,0 +1,85 @@ +--- +subcategory: "VPC infrastructure" +layout: "ibm" +page_title: "IBM : snapshot" +description: |- + Reads IBM Cloud snapshots. +--- +# ibm\_is_snapshot + +Import the details of existing IBM Cloud Infrastructure snapshot as a read-only data source. You can then reference the fields of the data source in other resources within the same configuration using interpolation syntax.For more information, about infrastructure snapshots, see [viewing snapshots](https://cloud.ibm.com/docs/vpc?topic=vpc-snapshots-vpc-view). + + +## Example Usage + +```terraform +resource "ibm_is_vpc" "testacc_vpc" { + name = "testvpc" +} + +resource "ibm_is_subnet" "testacc_subnet" { + name = "testsubnet" + vpc = ibm_is_vpc.testacc_vpc.id + zone = "us-south-2" + total_ipv4_address_count = 16 +} + +resource "ibm_is_ssh_key" "testacc_sshkey" { + name = "testssh" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR" +} + +resource "ibm_is_instance" "testacc_instance" { + name = "testvsi" + image = "xxxxx-xxxxx-xxxxx-xxxxxx" + profile = "bx2-2x8" + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "us-south-2" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } +} +resource "ibm_is_snapshot" "testacc_snapshot" { + name = "testsnapshot" + source_volume = ibm_is_instance.testacc_instance.volume_attachments[0].volume_id +} + +data "ibm_is_snapshot" "ds_snapshot1" { + identifier = ibm_is_snapshot.testacc_snapshot.id +} + +``` + +```terraform + +data "ibm_is_snapshot" "ds_snapshot2" { + name = ibm_is_snapshot.testacc_snapshot.name +} + +``` + + +## Argument reference +Review the argument references that you can specify for your data source. + +- `identifier` - (Optional, String) The unique identifier for this snapshot. +- `name` - (Optional, String) The name of the snapshot. + +## Attribute reference +In addition to all argument reference list, you can access the following attribute reference after your data source is created. + +- `bootable` - (Bool) Indicates if a boot volume attachment can be created with a volume created from this snapshot. +- `crn` - (String) The CRN for this snapshot. +- `encryption` - (String) The type of encryption used on the source volume(One of [ provider_managed, user_managed ]). +- `href` - (String) The URL for this snapshot. +- `lifecycle_state` - (String) The lifecycle state of this snapshot. Supported values are **deleted**, **deleting**, **failed**, **pending**, **stable**, **updating**, **waiting**, **suspended**. +- `minimum_capacity` - (Integer) The minimum capacity of a volume created from this snapshot. When a snapshot is created, this will be set to the capacity of the source_volume. +- `operating_system` - (String) The globally unique name for the operating system included in this image. +- `resource_type` - (String) The resource type. +- `size` - (Integer) The size of this snapshot rounded up to the next gigabyte. +- `source_image` - (String) If present, the unique identifier for the image from which the data on this volume was most directly provisioned. diff --git a/website/docs/d/is_snapshots.html.markdown b/website/docs/d/is_snapshots.html.markdown new file mode 100644 index 00000000000..838c7ae5cf5 --- /dev/null +++ b/website/docs/d/is_snapshots.html.markdown @@ -0,0 +1,39 @@ +--- +subcategory: "VPC infrastructure" +layout: "ibm" +page_title: "IBM : snapshots" +description: |- + Reads IBM Cloud snapshots. +--- +# ibm\_is_snapshots + +Import the details of an existing IBM Cloud Infrastructure snapshot collection as a read-only data source. You can then reference the fields of the data source in other resources within the same configuration using interpolation syntax, see [viewing snapshots](https://cloud.ibm.com/docs/vpc?topic=vpc-snapshots-vpc-view). + + +## Example Usage + +```terraform + +data "ibm_is_snapshots" "ds_snapshots" { +} + +``` + +## Attribute reference +In addition to all argument reference list, you can access the following attribute references after your data source is created. + +- `snapshots` - (List) List of snapshots in the IBM Cloud Infrastructure. + + Nested scheme for `snapshots`: + - `id` - (String) The unique identifier for this snapshot. + - `bootable` - (Bool) Indicates if a boot volume attachment can be created with a volume created from this snapshot. + - `crn` - (String) The CRN for this snapshot. + - `encryption` - (String) The type of encryption used on the source volume(One of [ **provider_managed**, **user_managed** ]). + - `href` - (String) The URL for this snapshot. + - `lifecycle_state` - (String) The lifecycle state of this snapshot. Supported values are **deleted**, **deleting**, **failed**, **pending**, **stable**, **updating**, **waiting**, **suspended**. + - `minimum_capacity` - (Integer) The minimum capacity of a volume created from this snapshot. When a snapshot is created, this will be set to the capacity of the source_volume. + - `operating_system` - (String) The globally unique name for the operating system included in this image. + - `resource_type` - (String) The resource type. + - `size` - (Integer) The size of this snapshot rounded up to the next gigabyte. + - `source_image` - (String) If present, the unique identifier for the image from which the data on this volume was most directly provisioned. + diff --git a/website/docs/d/is_volume.html.markdown b/website/docs/d/is_volume.html.markdown index b9803f4b33e..140d90c66ae 100644 --- a/website/docs/d/is_volume.html.markdown +++ b/website/docs/d/is_volume.html.markdown @@ -38,6 +38,7 @@ In addition to all argument reference list, you can access the following attribu - `iops` - (String) The bandwidth for the volume. - `profile` - (String) The profile to use for this volume. - `resource_group` - (String) The resource group ID for this volume. +- `source_snapshot` - ID of the snapshot, if volume was created from it. - `status` - (String) The status of the volume. Supported values are **available**, **failed**, **pending**, **unusable**, **pending_deletion**. - `status_reasons` - (List) Array of reasons for the current status. diff --git a/website/docs/r/is_instance.html.markdown b/website/docs/r/is_instance.html.markdown index 0ecae9c3101..e74116d3f0f 100644 --- a/website/docs/r/is_instance.html.markdown +++ b/website/docs/r/is_instance.html.markdown @@ -213,6 +213,31 @@ resource "ibm_is_instance" "testacc_instance2" { } } +// Example to provision instance from a snapshot, restoring boot volume from an existing snapshot + +resource "ibm_is_snapshot" "testacc_snapshot" { + name = "testsnapshot" + source_volume = ibm_is_instance.testacc_instance.volume_attachments[0].volume_id +} + +resource "ibm_is_instance" "testacc_instance_restore" { + name = "vsirestore" + profile = "cx2-2x4" + boot_volume { + name = "boot-restore" + snapshot = ibm_is_snapshot.testacc_snapshot.id + } + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "us-south-1" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } +} ``` @@ -227,19 +252,26 @@ The `ibm_is_instance` resource provides the following [[Timeouts](https://www.te ## Argument reference -Review the argument references that you can specify for your resource. +Review the argument references that you can specify for your resource. - `auto_delete_volume`- (Optional, Bool) If set to **true**, automatically deletes the volumes that are attached to an instance. **Note** Setting this argument can bring some inconsistency in the volume resource, as the volumes is destroyed along with instances. - `boot_volume` (Optional, List) A list of boot volumes for an instance. Nested scheme for `boot_volume`: - - `name` - (Optional, String) The name of the boot volume. - `encryption` - (Optional, String) The type of encryption to use for the boot volume. + - `name` - (Optional, String) The name of the boot volume. + - `snapshot` - (Optional, Forces new resource, String) The snapshot id of the volume to be used for creating boot volume attachment + **Note** + + - `snapshot` conflicts with `image` id and `instance_template` - `dedicated_host` - (Optional, Forces new resource, String) The placement restrictions to use the virtual server instance. Unique ID of the dedicated host where the instance id placed. - `dedicated_host_group` - (Optional, Forces new resource, String) The placement restrictions to use for the virtual server instance. Unique ID of the dedicated host group where the instance is placed. - `force_recovery_time` - (Optional, Integer) Define timeout (in minutes), to force the `is_instance` to recover from a perpetual "starting" state, during provisioning. And to force the is_instance to recover from a perpetual "stopping" state, during removal of user access. **Note** The force_recovery_time is used to retry multiple times until timeout. -- `image` - (Required, String) The ID of the virtual server image that you want to use. To list supported images, run `ibmcloud is images`. -- `keys` - (Required, List) A comma-separated list of SSH keys that you want to add to your instance. +- `image` - (Optional, String) The ID of the virtual server image that you want to use. To list supported images, run `ibmcloud is images`. + **Note** + + - `image` conflicts with `boot_volume.0.snapshot` +- `keys` - (Optional, List) A comma-separated list of SSH keys that you want to add to your instance. - `name` - (Optional, String) The instance name. - `network_interfaces` (Optional, Forces new resource, List) A list of more network interfaces that are set up for the instance. @@ -249,7 +281,7 @@ Review the argument references that you can specify for your resource. - `primary_ipv4_address` - (Optional, Forces new resource, String) The IPV4 address of the interface. - `subnet` - (Required, String) The ID of the subnet. - `security_groups`- (Optional, List of strings)A comma separated list of security groups to add to the primary network interface. -- `primary_network_interface` - (Required, List) A nested block describes the primary network interface of this instance. Only one primary network interface can be specified for an instance. +- `primary_network_interface` - (Optional, List) A nested block describes the primary network interface of this instance. Only one primary network interface can be specified for an instance. Nested scheme for `primary_network_interface`: - `allow_ip_spoofing`- (Optional, Bool) Indicates whether IP spoofing is allowed on the interface. If **false**, IP spoofing is prevented on the interface. If **true**, IP spoofing is allowed on the interface. @@ -258,13 +290,17 @@ Review the argument references that you can specify for your resource. - `primary_ipv4_address` - (Optional, Forces new resource, String) The IPV4 address of the interface. - `subnet` - (Required, String) The ID of the subnet. - `security_groups`-List of strings-Optional-A comma separated list of security groups to add to the primary network interface. -- `profile` - (Required, Forces new resource, String) The name of the profile that you want to use for your instance. To list supported profiles, run `ibmcloud is instance-profiles`. +- `profile` - (Optional, Forces new resource, String) The name of the profile that you want to use for your instance. To list supported profiles, run `ibmcloud is instance-profiles`. - `resource_group` - (Optional, Forces new resource, String) The ID of the resource group where you want to create the instance. +- `instance_template` - (Optional, String) ID of the source template. + **Note** + + - `instance_template` conflicts with `boot_volume.0.snapshot` - `tags` (Optional, Array of Strings) A list of tags that you want to add to your instance. Tags can help you find your instance more easily later. - `user_data` - (Optional, String) User data to transfer to the instance. - `volumes` (Optional, List) A comma separated list of volume IDs to attach to the instance. -- `vpc` - (Required, Forces new resource, String) The ID of the VPC where you want to create the instance. -- `zone` - (Required, Forces new resource, String) The name of the VPC zone where you want to create the instance. +- `vpc` - (Optional, Forces new resource, String) The ID of the VPC where you want to create the instance. +- `zone` - (Optional, Forces new resource, String) The name of the VPC zone where you want to create the instance. ## Attribute reference diff --git a/website/docs/r/is_instance_volume_attachment.html.markdown b/website/docs/r/is_instance_volume_attachment.html.markdown new file mode 100644 index 00000000000..95779d59be9 --- /dev/null +++ b/website/docs/r/is_instance_volume_attachment.html.markdown @@ -0,0 +1,162 @@ +--- + +subcategory: "VPC infrastructure" +layout: "ibm" +page_title: "IBM : instance_volume_attachment" +description: |- + Manages IBM Cloud infrastructure instance volume attachment. +--- + +# ibm_is_vpc_route +Create, update, or delete a Volume attachment on an instance. For more information, about VPC virtual server instances, see [Managing virtual server instances](https://cloud.ibm.com/docs/vpc?topic=vpc-managing-virtual-server-instances). + +## Example usage (using capacity) + +```terraform +resource "ibm_is_vpc" "testacc_vpc" { + name = "testvpc" +} + +resource "ibm_is_subnet" "testacc_subnet" { + name = "testsubnet" + vpc = ibm_is_vpc.testacc_vpc.id + zone = "us-south-2" + total_ipv4_address_count = 16 +} + +resource "ibm_is_ssh_key" "testacc_sshkey" { + name = "testssh" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR" +} + +resource "ibm_is_instance" "testacc_instance" { + name = "testvsi1" + image = "7eb4e35b-4257-56f8-d7da-326d85452591" + profile = "bc1-2x8" + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "us-south-2" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } +} + +resource "ibm_is_instance_volume_attachment" "testacc_att1" { + instance = ibm_is_instance.testacc_instance.id + + name = "test-vol-att-1" + profile = "general-purpose" + capacity = "20" + delete_volume_on_attachment_delete = true + delete_volume_on_instance_delete = true + volume_name = "testvol1" + + //User can configure timeouts + timeouts { + create = "15m" + update = "15m" + delete = "15m" + } +} + +``` +## Example usage (using existing volume) + +```terraform +resource "ibm_is_volume" "testacc_vol" { + name = "testvol2" + profile = "10iops-tier" + zone = "us-south-2" +} + +resource "ibm_is_instance_volume_attachment" "testacc_att2" { + instance = ibm_is_instance.testacc_instance.id + + name = "test-col-att-2" + volume = ibm_is_volume.testacc_vol.id + delete_volume_on_attachment_delete = false + delete_volume_on_instance_delete = false +} + +``` +## Example usage (restoring using snapshot) + +```terraform +resource "ibm_is_instance_volume_attachment" "testacc_att3" { + instance = ibm_is_instance.testacc_instance.id + + name = "test-col-att-3" + profile = "general-purpose" + snapshot = xxxx-xx-x-xxxxx + delete_volume_on_attachment_delete = true + delete_volume_on_instance_delete = true + volume_name = "testvol3" + + //User can configure timeouts + timeouts { + create = "15m" + update = "15m" + delete = "15m" + } +} + +``` + +## Timeouts + +The `ibm_is_instance_volume_attachment` resource provides the following [[Timeouts](https://www.terraform.io/docs/language/resources/syntax.html) configuration options: + + +- **create**: The creation of the instance volume attachment is considered failed when no response is received for 10 minutes. +- **update**: The update of the instance volume attachment or the attachment of a volume to an instance is considered failed when no response is received for 10 minutes. +- **delete**: The deletion of the instance volume attachment is considered failed when no response is received for 10 minutes. + + + +## Argument reference +Review the argument references that you can specify for your resource. + +- `capacity` - (Optional, Integer) The capacity of the volume in gigabytes. **NOTE** The specified minimum and maximum capacity values for creating or updating volumes may expand in the future. Accepted value is in [10-2000]. + - If this property is not provided or less than the minimum_capacity, minimum_capacity of the snapshot will be used as the capacity for the volume. + +- `delete_volume_on_attachment_delete` - (Optional, Bool) If set to true, when deleting the attachment, the volume will also be deleted +- `delete_volume_on_instance_delete` - (Optional, Bool) If set to true, when deleting the instance, the volume will also be deleted +- `encryption_key` - (Optional, String) The CRN of the Key Protect Root Key or Hyper Protect Crypto Service Root Key for this resource. If this property is not provided but the image is encrypted, the image's encryption_key will be used. Otherwise, the encryption type for the volume will be `provider_managed`. +- `instance` - (Required, String) The id of the instance. +- `iops` - (Optional, Integer) The bandwidth for the new volume +- `name` - (Required, String) The name of the volume attachment. +- `profile` - (Optional, String) The globally unique name for this volume profile +- `snapshot` - (Optional, String) The unique identifier for this snapshot from which to clone the new volume. + + **NOTE** + - one of `capacity` or `snapshot` must be present for volume creation + - if `capacity` is not present or less than `minimum_capacity` of the snapshot, `minimum_cpacity` is taken as the volume capacity. +- `volume` - (Optional, String) The unique identifier for the existing volume +- `volume_name` - (Optional, String) he unique user-defined name for this new volume. + +## Attribute reference +In addition to all argument reference list, you can access the following attribute references after your data source is created. + +- `device`- (String) A unique identifier for the device which is exposed to the instance operating system. +- `href` - (String) The URL for this volume attachment. +- `id` - (String) The ID of the instance volume attachment. The ID is composed of `/`. +- `status` - (String) The status of this volume attachment [ attached, attaching, deleting, detaching ]. +- `type` - (String) The type of volume attachment [ boot, data ]. +- `volume_attachment_id` - (String) The unique identifier for this volume attachment. +- `volume_crn` - (String) The CRN for this volume. +- `volume_deleted` - (String) If present, this property indicates the referenced resource has been deleted and provides some supplementary information. +- `volume_href` - (String) The URL for this volume. + + +## Import +The `ibm_is_instance_volume_attachment` resource can be imported by using the instance id and volume attachment id. + +**Syntax** + +``` +$ terraform import ibm_is_instance_volume_attachment.example / +``` diff --git a/website/docs/r/is_snapshot.html.markdown b/website/docs/r/is_snapshot.html.markdown new file mode 100644 index 00000000000..a54f4a4bdcb --- /dev/null +++ b/website/docs/r/is_snapshot.html.markdown @@ -0,0 +1,92 @@ +--- +subcategory: "VPC infrastructure" +layout: "ibm" +page_title: "IBM : snapshot" +description: |- + Manages IBM snapshot. +--- + +# ibm\_is_snapshot + +Create, update, or delete a snapshot. For more information, about subnet, see [creating snapshots](https://cloud.ibm.com/docs/vpc?topic=vpc-snapshots-vpc-create). + + +## Example Usage + +```terraform +resource "ibm_is_vpc" "testacc_vpc" { + name = "testvpc" +} + +resource "ibm_is_subnet" "testacc_subnet" { + name = "testsubnet" + vpc = ibm_is_vpc.testacc_vpc.id + zone = "us-south-2" + total_ipv4_address_count = 16 +} + +resource "ibm_is_ssh_key" "testacc_sshkey" { + name = "testssh" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCKVmnMOlHKcZK8tpt3MP1lqOLAcqcJzhsvJcjscgVERRN7/9484SOBJ3HSKxxNG5JN8owAjy5f9yYwcUg+JaUVuytn5Pv3aeYROHGGg+5G346xaq3DAwX6Y5ykr2fvjObgncQBnuU5KHWCECO/4h8uWuwh/kfniXPVjFToc+gnkqA+3RKpAecZhFXwfalQ9mMuYGFxn+fwn8cYEApsJbsEmb0iJwPiZ5hjFC8wREuiTlhPHDgkBLOiycd20op2nXzDbHfCHInquEe/gYxEitALONxm0swBOwJZwlTDOB7C6y2dzlrtxr1L59m7pCkWI4EtTRLvleehBoj3u7jB4usR" +} + +resource "ibm_is_instance" "testacc_instance" { + name = "testvsi" + image = "xxxxx-xxxxx-xxxxx-xxxxxx" + profile = "bx2-2x8" + primary_network_interface { + subnet = ibm_is_subnet.testacc_subnet.id + } + vpc = ibm_is_vpc.testacc_vpc.id + zone = "us-south-2" + keys = [ibm_is_ssh_key.testacc_sshkey.id] + network_interfaces { + subnet = ibm_is_subnet.testacc_subnet.id + name = "eth1" + } +} +resource "ibm_is_snapshot" "testacc_snapshot" { + name = "testsnapshot" + source_volume = ibm_is_instance.testacc_instance.volume_attachments[0].volume_id +} + +``` + + +## Argument reference +Review the argument references that you can specify for your resource. + +- `name` - (Optional, String) The name of the snapshot. +- `resource_group` - (Optional, Forces new resource, String) The resource group ID where the snapshot is to be created +- `source_volume` - (Required, Forces new resource, String) The unique identifier for the volume for which snapshot is to be created. + +## Attribute reference +In addition to all argument reference list, you can access the following attribute reference after your resource is created. + +- `bootable` - (Bool) Indicates if a boot volume attachment can be created with a volume created from this snapshot. +- `crn` - (String) The CRN for this snapshot. +- `encryption` - (String) The type of encryption used on the source volume(One of [ **provider_managed**, **user_managed** ]). +- `href` - (String) The URL for this snapshot. +- `id` - (String) The unique identifier for this snapshot. +- `lifecycle_state` - (String) The lifecycle state of this snapshot. Supported values are **deleted**, **deleting**, **failed**, **pending**, **stable**, **updating**, **waiting**, **suspended**. +- `minimum_capacity` - (Integer) The minimum capacity of a volume created from this snapshot. When a snapshot is created, this will be set to the capacity of the source_volume. +- `operating_system` - (String) The globally unique name for the operating system included in this image. +- `resource_type` - (String) The resource type. +- `size` - (Integer) The size of this snapshot rounded up to the next gigabyte. +- `source_image` - (String) If present, the unique identifier for the image from which the data on this volume was most directly provisioned. + +## Import + +The `ibm_is_snapshot` can be imported using ID. + +**Syntax** + +``` +$ terraform import ibm_is_snapshot.example +``` + +**Example** + +``` +$ terraform import ibm_is_snapshot.example d7bec597-4726-451f-8a63-e62e6f19c32c +``` diff --git a/website/docs/r/is_volume.html.markdown b/website/docs/r/is_volume.html.markdown index 891ff86e6e7..cbec2b408f5 100644 --- a/website/docs/r/is_volume.html.markdown +++ b/website/docs/r/is_volume.html.markdown @@ -46,6 +46,7 @@ The `ibm_is_volume` resource provides the following [Timeouts](https://www.terra Review the argument references that you can specify for your resource. - `capacity` - (Optional, Forces new resource, Integer) (The capacity of the volume in gigabytes. This defaults to `100`. +- `delete_all_snapshots` - (Optional, Bool) Deletes all snapshots created from this volume. - `encryption_key` - (Optional, Forces new resource, String) The key to use for encrypting this volume. - `iops` - (Optional, Forces new resource, Integer) The total input/ output operations per second (IOPS) for your storage. This value is required for `custom` storage profiles only. - `name` - (Required, String) The user-defined name for this volume.No. @@ -59,7 +60,8 @@ Review the argument references that you can specify for your resource. In addition to all argument reference list, you can access the following attribute reference after your resource is created. - `id` - (String) The unique identifier of the volume. -- `status` - (String) The status of volume. Supported values are **available**, **failed**, **pending**, **unusable**, or `pending_deletion`. +- `source_snapshot` - ID of the snapshot, if volume was created from it. +- `status` - (String) The status of volume. Supported values are **available**, **failed**, **pending**, **unusable**, or **pending_deletion**. - `status_reasons` - (List) Array of reasons for the current status. Nested scheme for `status_reasons`: