From aaece37ec9aaeba2242bb342908f1a6aa60019bc Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Tue, 1 Nov 2016 14:15:31 +0000 Subject: [PATCH] provider/aws: Adding a datasource for aws_ebs_volume (#9753) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will allows us to filter a specific ebs_volume for attachment to an aws_instance ``` make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSEbsVolumeDataSource_'✹ ==> Checking that code complies with gofmt requirements... go generate $(go list ./... | grep -v /terraform/vendor/) 2016/11/01 12:39:19 Generated command/internal_plugin_list.go TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSEbsVolumeDataSource_ -timeout 120m === RUN TestAccAWSEbsVolumeDataSource_basic --- PASS: TestAccAWSEbsVolumeDataSource_basic (28.74s) === RUN TestAccAWSEbsVolumeDataSource_multipleFilters --- PASS: TestAccAWSEbsVolumeDataSource_multipleFilters (28.37s) PASS ok github.com/hashicorp/terraform/builtin/providers/aws57.145s ``` --- .../aws/data_source_aws_ebs_volume.go | 211 ++++++++++++++++++ .../aws/data_source_aws_ebs_volume_test.go | 106 +++++++++ builtin/providers/aws/provider.go | 1 + .../providers/aws/d/ebs_volume.html.markdown | 56 +++++ website/source/layouts/aws.erb | 3 + 5 files changed, 377 insertions(+) create mode 100644 builtin/providers/aws/data_source_aws_ebs_volume.go create mode 100644 builtin/providers/aws/data_source_aws_ebs_volume_test.go create mode 100644 website/source/docs/providers/aws/d/ebs_volume.html.markdown diff --git a/builtin/providers/aws/data_source_aws_ebs_volume.go b/builtin/providers/aws/data_source_aws_ebs_volume.go new file mode 100644 index 000000000..96cda87c6 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ebs_volume.go @@ -0,0 +1,211 @@ +package aws + +import ( + "bytes" + "fmt" + "log" + "sort" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/davecgh/go-spew/spew" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsEbsVolume() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsEbsVolumeRead, + + Schema: map[string]*schema.Schema{ + "filter": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "values": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "most_recent": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + "availability_zone": { + Type: schema.TypeString, + Computed: true, + }, + "encrypted": { + Type: schema.TypeBool, + Computed: true, + }, + "iops": { + Type: schema.TypeInt, + Computed: true, + }, + "volume_type": { + Type: schema.TypeString, + Computed: true, + }, + "size": { + Type: schema.TypeInt, + Computed: true, + }, + "snapshot_id": { + Type: schema.TypeString, + Computed: true, + }, + "kms_key_id": { + Type: schema.TypeString, + Computed: true, + }, + "volume_id": { + Type: schema.TypeString, + Computed: true, + }, + "tags": { + Type: schema.TypeSet, + Computed: true, + Set: volumeTagsHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceAwsEbsVolumeRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + filters, filtersOk := d.GetOk("filter") + + params := &ec2.DescribeVolumesInput{} + if filtersOk { + params.Filters = buildVolumeFilters(filters.(*schema.Set)) + } + + resp, err := conn.DescribeVolumes(params) + if err != nil { + return err + } + + log.Printf("Found These Volumes %s", spew.Sdump(resp.Volumes)) + + filteredVolumes := resp.Volumes[:] + + var volume *ec2.Volume + if len(filteredVolumes) < 1 { + return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") + } + + if len(filteredVolumes) > 1 { + recent := d.Get("most_recent").(bool) + log.Printf("[DEBUG] aws_ebs_volume - multiple results found and `most_recent` is set to: %t", recent) + if recent { + volume = mostRecentVolume(filteredVolumes) + } else { + return fmt.Errorf("Your query returned more than one result. Please try a more " + + "specific search criteria, or set `most_recent` attribute to true.") + } + } else { + // Query returned single result. + volume = filteredVolumes[0] + } + + log.Printf("[DEBUG] aws_ebs_volume - Single Volume found: %s", *volume.VolumeId) + return volumeDescriptionAttributes(d, volume) +} + +func buildVolumeFilters(set *schema.Set) []*ec2.Filter { + var filters []*ec2.Filter + for _, v := range set.List() { + m := v.(map[string]interface{}) + var filterValues []*string + for _, e := range m["values"].([]interface{}) { + filterValues = append(filterValues, aws.String(e.(string))) + } + filters = append(filters, &ec2.Filter{ + Name: aws.String(m["name"].(string)), + Values: filterValues, + }) + } + return filters +} + +type volumeSort []*ec2.Volume + +func (a volumeSort) Len() int { return len(a) } +func (a volumeSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a volumeSort) Less(i, j int) bool { + itime := *a[i].CreateTime + jtime := *a[j].CreateTime + return itime.Unix() < jtime.Unix() +} + +func mostRecentVolume(volumes []*ec2.Volume) *ec2.Volume { + sortedVolumes := volumes + sort.Sort(volumeSort(sortedVolumes)) + return sortedVolumes[len(sortedVolumes)-1] +} + +func volumeDescriptionAttributes(d *schema.ResourceData, volume *ec2.Volume) error { + d.SetId(*volume.VolumeId) + d.Set("volume_id", volume.VolumeId) + d.Set("availability_zone", volume.AvailabilityZone) + d.Set("encrypted", volume.Encrypted) + d.Set("iops", volume.Iops) + d.Set("kms_key_id", volume.KmsKeyId) + d.Set("size", volume.Size) + d.Set("snapshot_id", volume.SnapshotId) + d.Set("volume_type", volume.VolumeType) + + if err := d.Set("tags", volumeTags(volume.Tags)); err != nil { + return err + } + + return nil +} + +func volumeTags(m []*ec2.Tag) *schema.Set { + s := &schema.Set{ + F: volumeTagsHash, + } + for _, v := range m { + tag := map[string]interface{}{ + "key": *v.Key, + "value": *v.Value, + } + s.Add(tag) + } + return s +} + +func volumeTagsHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["key"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["value"].(string))) + return hashcode.String(buf.String()) +} diff --git a/builtin/providers/aws/data_source_aws_ebs_volume_test.go b/builtin/providers/aws/data_source_aws_ebs_volume_test.go new file mode 100644 index 000000000..8be4455fe --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ebs_volume_test.go @@ -0,0 +1,106 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSEbsVolumeDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckAwsEbsVolumeDataSourceConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEbsVolumeDataSourceID("data.aws_ebs_volume.ebs_volume"), + resource.TestCheckResourceAttr("data.aws_ebs_volume.ebs_volume", "size", "40"), + ), + }, + }, + }) +} + +func TestAccAWSEbsVolumeDataSource_multipleFilters(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckAwsEbsVolumeDataSourceConfigWithMultipleFilters, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsEbsVolumeDataSourceID("data.aws_ebs_volume.ebs_volume"), + resource.TestCheckResourceAttr("data.aws_ebs_volume.ebs_volume", "size", "10"), + resource.TestCheckResourceAttr("data.aws_ebs_volume.ebs_volume", "volume_type", "gp2"), + ), + }, + }, + }) +} + +func testAccCheckAwsEbsVolumeDataSourceID(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Can't find Volume data source: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Volume data source ID not set") + } + return nil + } +} + +const testAccCheckAwsEbsVolumeDataSourceConfig = ` +resource "aws_ebs_volume" "example" { + availability_zone = "us-west-2a" + type = "gp2" + size = 40 + tags { + Name = "External Volume" + } +} + +data "aws_ebs_volume" "ebs_volume" { + most_recent = true + filter { + name = "tag:Name" + values = ["External Volume"] + } + filter { + name = "volume-type" + values = ["${aws_ebs_volume.example.type}"] + } +} +` + +const testAccCheckAwsEbsVolumeDataSourceConfigWithMultipleFilters = ` +resource "aws_ebs_volume" "external1" { + availability_zone = "us-west-2a" + type = "gp2" + size = 10 + tags { + Name = "External Volume 1" + } +} + +data "aws_ebs_volume" "ebs_volume" { + most_recent = true + filter { + name = "tag:Name" + values = ["External Volume 1"] + } + filter { + name = "size" + values = ["${aws_ebs_volume.external1.size}"] + } + filter { + name = "volume-type" + values = ["${aws_ebs_volume.external1.type}"] + } +} +` diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index e3051b732..a6bbce76d 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -149,6 +149,7 @@ func Provider() terraform.ResourceProvider { "aws_billing_service_account": dataSourceAwsBillingServiceAccount(), "aws_caller_identity": dataSourceAwsCallerIdentity(), "aws_cloudformation_stack": dataSourceAwsCloudFormationStack(), + "aws_ebs_volume": dataSourceAwsEbsVolume(), "aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(), "aws_elb_service_account": dataSourceAwsElbServiceAccount(), "aws_iam_policy_document": dataSourceAwsIamPolicyDocument(), diff --git a/website/source/docs/providers/aws/d/ebs_volume.html.markdown b/website/source/docs/providers/aws/d/ebs_volume.html.markdown new file mode 100644 index 000000000..1238f4f80 --- /dev/null +++ b/website/source/docs/providers/aws/d/ebs_volume.html.markdown @@ -0,0 +1,56 @@ +--- +layout: "aws" +page_title: "AWS: aws_ebs_volume" +sidebar_current: "docs-aws-datasource-ebs-volume" +description: |- + Get information on an EBS volume. +--- + +# aws\_ebs\_volume + +Use this data source to get information about an EBS volume for use in other +resources. + +## Example Usage + +``` +data "aws_ebs_volume" "ebs_volume" { + most_recent = true + filter { + name = "volume-type" + values = ["gp2"] + } + filter { + name = "tag:Name" + values = ["Example"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `most_recent` - (Optional) If more than one result is returned, use the most +recent Volume. +* `filter` - (Optional) One or more name/value pairs to filter off of. There are +several valid keys, for a full reference, check out +[describe-volumes in the AWS CLI reference][1]. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The volume ID (e.g. vol-59fcb34e). +* `volume_id` - The volume ID (e.g. vol-59fcb34e). +* `availability_zone` - The AZ where the EBS volume exists. +* `encrypted` - Whether the disk is encrypted. +* `iops` - The amount of IOPS for the disk. +* `size` - The size of the drive in GiBs. +* `snapshot_id` - The snapshot_id the EBS volume is based off. +* `volume_type` - The type of EBS volume. +* `kms_key_id` - The ARN for the KMS encryption key. +* `tags` - A mapping of tags for the resource. + +[1]: http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-volumes.html diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index d3f57e09f..c917feedf 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -29,6 +29,9 @@ > aws_cloudformation_stack + > + aws_ebs_volume + > aws_ecs_container_definition