diff --git a/builtin/providers/aws/data_source_aws_instance.go b/builtin/providers/aws/data_source_aws_instance.go new file mode 100644 index 000000000..09d74d060 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_instance.go @@ -0,0 +1,349 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsInstance() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsInstanceRead, + + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + "tags": dataSourceTagsSchema(), + "instance_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "ami": { + Type: schema.TypeString, + Computed: true, + }, + "instance_type": { + Type: schema.TypeString, + Computed: true, + }, + "instance_state": { + Type: schema.TypeString, + Computed: true, + }, + "availability_zone": { + Type: schema.TypeString, + Computed: true, + }, + "tenancy": { + Type: schema.TypeString, + Computed: true, + }, + "key_name": { + Type: schema.TypeString, + Computed: true, + }, + "public_dns": { + Type: schema.TypeString, + Computed: true, + }, + "public_ip": { + Type: schema.TypeString, + Computed: true, + }, + "private_dns": { + Type: schema.TypeString, + Computed: true, + }, + "private_ip": { + Type: schema.TypeString, + Computed: true, + }, + "iam_instance_profile": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_id": { + Type: schema.TypeString, + Computed: true, + }, + "network_interface_id": { + Type: schema.TypeString, + Computed: true, + }, + "associate_public_ip_address": { + Type: schema.TypeBool, + Computed: true, + }, + "ebs_optimized": { + Type: schema.TypeBool, + Computed: true, + }, + "source_dest_check": { + Type: schema.TypeBool, + Computed: true, + }, + "monitoring": { + Type: schema.TypeBool, + Computed: true, + }, + "user_data": { + Type: schema.TypeString, + Computed: true, + }, + "security_groups": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "vpc_security_group_ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "ephemeral_block_device": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "device_name": { + Type: schema.TypeString, + Required: true, + }, + + "virtual_name": { + Type: schema.TypeString, + Optional: true, + }, + + "no_device": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + "ebs_block_device": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "delete_on_termination": { + Type: schema.TypeBool, + Computed: true, + }, + + "device_name": { + Type: schema.TypeString, + Computed: true, + }, + + "encrypted": { + Type: schema.TypeBool, + Computed: true, + }, + + "iops": { + Type: schema.TypeInt, + Computed: true, + }, + + "snapshot_id": { + Type: schema.TypeString, + Computed: true, + }, + + "volume_size": { + Type: schema.TypeInt, + Computed: true, + }, + + "volume_type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "root_block_device": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "delete_on_termination": { + Type: schema.TypeBool, + Computed: true, + }, + + "iops": { + Type: schema.TypeInt, + Computed: true, + }, + + "volume_size": { + Type: schema.TypeInt, + Computed: true, + }, + + "volume_type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +// dataSourceAwsInstanceRead performs the instanceID lookup +func dataSourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + filters, filtersOk := d.GetOk("filter") + instanceID, instanceIDOk := d.GetOk("instance_id") + + if filtersOk == false && instanceIDOk == false { + return fmt.Errorf("One of filters, or instance_id must be assigned") + } + + // Build up search parameters + params := &ec2.DescribeInstancesInput{} + if filtersOk { + params.Filters = buildAwsDataSourceFilters(filters.(*schema.Set)) + } + if instanceIDOk { + params.InstanceIds = []*string{aws.String(instanceID.(string))} + } + + // Perform the lookup + resp, err := conn.DescribeInstances(params) + if err != nil { + return err + } + + // If no instances were returned, return + if len(resp.Reservations) == 0 { + return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") + } + + var filteredInstances []*ec2.Instance + + // loop through reservations, and remove terminated instances, populate instance slice + for _, res := range resp.Reservations { + for _, instance := range res.Instances { + if instance.State != nil && *instance.State.Name != "terminated" { + filteredInstances = append(filteredInstances, instance) + } + } + } + + var instance *ec2.Instance + if len(filteredInstances) < 1 { + return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") + } + + // (TODO: Support a list of instances to be returned) + // Possibly with a different data source that returns a list of individual instance data sources + if len(filteredInstances) > 1 { + return fmt.Errorf("Your query returned more than one result. Please try a more " + + "specific search criteria.") + } else { + instance = filteredInstances[0] + } + + log.Printf("[DEBUG] aws_instance - Single Instance ID found: %s", *instance.InstanceId) + return instanceDescriptionAttributes(d, instance, conn) +} + +// Populate instance attribute fields with the returned instance +func instanceDescriptionAttributes(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error { + d.SetId(*instance.InstanceId) + // Set the easy attributes + d.Set("instance_state", instance.State.Name) + if instance.Placement != nil { + d.Set("availability_zone", instance.Placement.AvailabilityZone) + } + if instance.Placement.Tenancy != nil { + d.Set("tenancy", instance.Placement.Tenancy) + } + d.Set("ami", instance.ImageId) + d.Set("instance_type", instance.InstanceType) + d.Set("key_name", instance.KeyName) + d.Set("public_dns", instance.PublicDnsName) + d.Set("public_ip", instance.PublicIpAddress) + d.Set("private_dns", instance.PrivateDnsName) + d.Set("private_ip", instance.PrivateIpAddress) + d.Set("iam_instance_profile", iamInstanceProfileArnToName(instance.IamInstanceProfile)) + + // iterate through network interfaces, and set subnet, network_interface, public_addr + if len(instance.NetworkInterfaces) > 0 { + for _, ni := range instance.NetworkInterfaces { + if *ni.Attachment.DeviceIndex == 0 { + d.Set("subnet_id", ni.SubnetId) + d.Set("network_interface_id", ni.NetworkInterfaceId) + d.Set("associate_public_ip_address", ni.Association != nil) + } + } + } else { + d.Set("subnet_id", instance.SubnetId) + d.Set("network_interface_id", "") + } + + d.Set("ebs_optimized", instance.EbsOptimized) + if instance.SubnetId != nil && *instance.SubnetId != "" { + d.Set("source_dest_check", instance.SourceDestCheck) + } + + if instance.Monitoring != nil && instance.Monitoring.State != nil { + monitoringState := *instance.Monitoring.State + d.Set("monitoring", monitoringState == "enabled" || monitoringState == "pending") + } + + d.Set("tags", dataSourceTags(instance.Tags)) + + // Security Groups + if err := readSecurityGroups(d, instance); err != nil { + return err + } + + // Block devices + if err := readBlockDevices(d, instance, conn); err != nil { + return err + } + if _, ok := d.GetOk("ephemeral_block_device"); !ok { + d.Set("ephemeral_block_device", []interface{}{}) + } + + // Lookup and Set Instance Attributes + { + attr, err := conn.DescribeInstanceAttribute(&ec2.DescribeInstanceAttributeInput{ + Attribute: aws.String("disableApiTermination"), + InstanceId: aws.String(d.Id()), + }) + if err != nil { + return err + } + d.Set("disable_api_termination", attr.DisableApiTermination.Value) + } + { + attr, err := conn.DescribeInstanceAttribute(&ec2.DescribeInstanceAttributeInput{ + Attribute: aws.String(ec2.InstanceAttributeNameUserData), + InstanceId: aws.String(d.Id()), + }) + if err != nil { + return err + } + if attr.UserData.Value != nil { + d.Set("user_data", userDataHashSum(*attr.UserData.Value)) + } + } + + return nil +} diff --git a/builtin/providers/aws/data_source_aws_instance_test.go b/builtin/providers/aws/data_source_aws_instance_test.go new file mode 100644 index 000000000..0d6d2cfd0 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_instance_test.go @@ -0,0 +1,498 @@ +package aws + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAWSInstanceDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.aws_instance.web-instance", "ami", "ami-4fccb37f"), + resource.TestCheckResourceAttr( + "data.aws_instance.web-instance", "tags.#", "1"), + resource.TestCheckResourceAttr( + "data.aws_instance.web-instance", "instance_type", "m1.small"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_AzUserData(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_AzUserData, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "ami", "ami-4fccb37f"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "tags.#", "1"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "instance_type", "m1.small"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "availability_zone", "us-west-2a"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "user_data", "3dc39dda39be1205215e776bad998da361a5955d"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_gp2IopsDevice(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_gp2IopsDevice, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "ami", "ami-55a7ea65"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "instance_type", "m3.medium"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.#", "1"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.0.volume_size", "11"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.0.volume_type", "gp2"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.0.iops", "100"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_blockDevices(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_blockDevices, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "ami", "ami-55a7ea65"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "instance_type", "m3.medium"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.#", "1"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.0.volume_size", "11"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.0.volume_type", "gp2"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ebs_block_device.#", "3"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ephemeral_block_device.#", "1"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_rootInstanceStore(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_rootInstanceStore, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "ami", "ami-44c36524"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "instance_type", "m3.medium"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ebs_block_device.#", "0"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ebs_optimized", "false"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "root_block_device.#", "0"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_privateIP(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_privateIP, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "ami", "ami-c5eabbf5"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "instance_type", "t2.micro"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "private_ip", "10.1.1.42"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_keyPair(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_keyPair, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "ami", "ami-408c7f28"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "instance_type", "t1.micro"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "tags.#", "1"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "key_name", "tmp-key"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_VPC(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_VPC, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "ami", "ami-4fccb37f"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "instance_type", "m1.small"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "user_data", "562a3e32810edf6ff09994f050f12e799452379d"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "associate_public_ip_address", "true"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "tenancy", "dedicated"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_SecurityGroups(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_SecurityGroups, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "ami", "ami-408c7f28"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "instance_type", "m1.small"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "vpc_security_group_ids.#", "0"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "security_groups.#", "1"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "user_data", "3dc39dda39be1205215e776bad998da361a5955d"), + ), + }, + }, + }) +} + +func TestAccAWSInstanceDataSource_VPCSecurityGroups(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_VPCSecurityGroups, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "ami", "ami-21f78e11"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "instance_type", "t1.micro"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "security_groups.#", "0"), + resource.TestCheckResourceAttr( + "data.aws_instance.foo", "vpc_security_group_ids.#", "1"), + ), + }, + }, + }) +} + +// Lookup based on InstanceID +const testAccInstanceDataSourceConfig = ` +resource "aws_instance" "web" { + # us-west-2 + ami = "ami-4fccb37f" + instance_type = "m1.small" + tags { + Name = "HelloWorld" + } +} + +data "aws_instance" "web-instance" { + filter { + name = "instance-id" + values = ["${aws_instance.web.id}"] + } +} +` + +// filter on tag, populate more attributes +const testAccInstanceDataSourceConfig_AzUserData = ` +resource "aws_instance" "foo" { + # us-west-2 + ami = "ami-4fccb37f" + availability_zone = "us-west-2a" + + instance_type = "m1.small" + user_data = "foo:-with-character's" + tags { + TFAccTest = "YesThisIsATest" + } +} + +data "aws_instance" "foo" { + instance_id = "${aws_instance.foo.id}" +} +` + +// GP2IopsDevice +const testAccInstanceDataSourceConfig_gp2IopsDevice = ` +resource "aws_instance" "foo" { + # us-west-2 + ami = "ami-55a7ea65" + instance_type = "m3.medium" + root_block_device { + volume_type = "gp2" + volume_size = 11 + iops = 330 + } +} + +data "aws_instance" "foo" { + instance_id = "${aws_instance.foo.id}" +} +` + +// Block Device +const testAccInstanceDataSourceConfig_blockDevices = ` +resource "aws_instance" "foo" { + # us-west-2 + ami = "ami-55a7ea65" + instance_type = "m3.medium" + + root_block_device { + volume_type = "gp2" + volume_size = 11 + } + ebs_block_device { + device_name = "/dev/sdb" + volume_size = 9 + } + ebs_block_device { + device_name = "/dev/sdc" + volume_size = 10 + volume_type = "io1" + iops = 100 + } + + # Encrypted ebs block device + ebs_block_device { + device_name = "/dev/sdd" + volume_size = 12 + encrypted = true + } + + ephemeral_block_device { + device_name = "/dev/sde" + virtual_name = "ephemeral0" + } +} + +data "aws_instance" "foo" { + instance_id = "${aws_instance.foo.id}" +} +` + +const testAccInstanceDataSourceConfig_rootInstanceStore = ` +resource "aws_instance" "foo" { + ami = "ami-44c36524" + instance_type = "m3.medium" +} +data "aws_instance" "foo" { + instance_id = "${aws_instance.foo.id}" +} +` + +const testAccInstanceDataSourceConfig_privateIP = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_subnet" "foo" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_instance" "foo" { + ami = "ami-c5eabbf5" + instance_type = "t2.micro" + subnet_id = "${aws_subnet.foo.id}" + private_ip = "10.1.1.42" +} + +data "aws_instance" "foo" { + instance_id = "${aws_instance.foo.id}" +} +` + +const testAccInstanceDataSourceConfig_keyPair = ` +provider "aws" { + region = "us-east-1" +} + +resource "aws_key_pair" "debugging" { + key_name = "tmp-key" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 phodgson@thoughtworks.com" +} + +resource "aws_instance" "foo" { + ami = "ami-408c7f28" + instance_type = "t1.micro" + key_name = "${aws_key_pair.debugging.key_name}" + tags { + Name = "testAccInstanceDataSourceConfigKeyPair_TestAMI" + } +} + +data "aws_instance" "foo" { + filter { + name = "tag:Name" + values = ["testAccInstanceDataSourceConfigKeyPair_TestAMI"] + } + filter { + name = "key-name" + values = ["${aws_instance.foo.key_name}"] + } +} +` + +const testAccInstanceDataSourceConfig_VPC = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_subnet" "foo" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_instance" "foo" { + # us-west-2 + ami = "ami-4fccb37f" + instance_type = "m1.small" + subnet_id = "${aws_subnet.foo.id}" + associate_public_ip_address = true + tenancy = "dedicated" + # pre-encoded base64 data + user_data = "3dc39dda39be1205215e776bad998da361a5955d" +} + +data "aws_instance" "foo" { + instance_id = "${aws_instance.foo.id}" +} +` + +const testAccInstanceDataSourceConfig_SecurityGroups = ` +provider "aws" { + region = "us-east-1" +} + +resource "aws_security_group" "tf_test_foo" { + name = "tf_test_foo" + description = "foo" + + ingress { + protocol = "icmp" + from_port = -1 + to_port = -1 + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_instance" "foo" { + ami = "ami-408c7f28" + instance_type = "m1.small" + security_groups = ["${aws_security_group.tf_test_foo.name}"] + user_data = "foo:-with-character's" +} + +data "aws_instance" "foo" { + instance_id = "${aws_instance.foo.id}" +} +` + +const testAccInstanceDataSourceConfig_VPCSecurityGroups = ` +resource "aws_internet_gateway" "gw" { + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + tags { + Name = "tf-network-test" + } +} + +resource "aws_security_group" "tf_test_foo" { + name = "tf_test_foo" + description = "foo" + vpc_id="${aws_vpc.foo.id}" + + ingress { + protocol = "icmp" + from_port = -1 + to_port = -1 + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_subnet" "foo" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_instance" "foo_instance" { + ami = "ami-21f78e11" + instance_type = "t1.micro" + vpc_security_group_ids = ["${aws_security_group.tf_test_foo.id}"] + subnet_id = "${aws_subnet.foo.id}" + depends_on = ["aws_internet_gateway.gw"] +} + +data "aws_instance" "foo" { + instance_id = "${aws_instance.foo_instance.id}" +} +` diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 08a85d3fd..cbae16bb4 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -161,6 +161,7 @@ func Provider() terraform.ResourceProvider { "aws_iam_account_alias": dataSourceAwsIamAccountAlias(), "aws_iam_policy_document": dataSourceAwsIamPolicyDocument(), "aws_iam_server_certificate": dataSourceAwsIAMServerCertificate(), + "aws_instance": dataSourceAwsInstance(), "aws_ip_ranges": dataSourceAwsIPRanges(), "aws_prefix_list": dataSourceAwsPrefixList(), "aws_redshift_service_account": dataSourceAwsRedshiftServiceAccount(), diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index b99cb0c0b..7f59b7531 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -526,52 +526,8 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { d.Set("tags", tagsToMap(instance.Tags)) - // Determine whether we're referring to security groups with - // IDs or names. We use a heuristic to figure this out. By default, - // we use IDs if we're in a VPC. However, if we previously had an - // all-name list of security groups, we use names. Or, if we had any - // IDs, we use IDs. - useID := instance.SubnetId != nil && *instance.SubnetId != "" - if v := d.Get("security_groups"); v != nil { - match := useID - sgs := v.(*schema.Set).List() - if len(sgs) > 0 { - match = false - for _, v := range v.(*schema.Set).List() { - if strings.HasPrefix(v.(string), "sg-") { - match = true - break - } - } - } - - useID = match - } - - // Build up the security groups - sgs := make([]string, 0, len(instance.SecurityGroups)) - if useID { - for _, sg := range instance.SecurityGroups { - sgs = append(sgs, *sg.GroupId) - } - log.Printf("[DEBUG] Setting Security Group IDs: %#v", sgs) - if err := d.Set("vpc_security_group_ids", sgs); err != nil { - return err - } - if err := d.Set("security_groups", []string{}); err != nil { - return err - } - } else { - for _, sg := range instance.SecurityGroups { - sgs = append(sgs, *sg.GroupName) - } - log.Printf("[DEBUG] Setting Security Group Names: %#v", sgs) - if err := d.Set("security_groups", sgs); err != nil { - return err - } - if err := d.Set("vpc_security_group_ids", []string{}); err != nil { - return err - } + if err := readSecurityGroups(d, instance); err != nil { + return err } if err := readBlockDevices(d, instance, conn); err != nil { @@ -1013,6 +969,57 @@ func readBlockDeviceMappingsFromConfig( return blockDevices, nil } +// Determine whether we're referring to security groups with +// IDs or names. We use a heuristic to figure this out. By default, +// we use IDs if we're in a VPC. However, if we previously had an +// all-name list of security groups, we use names. Or, if we had any +// IDs, we use IDs. +func readSecurityGroups(d *schema.ResourceData, instance *ec2.Instance) error { + useID := instance.SubnetId != nil && *instance.SubnetId != "" + if v := d.Get("security_groups"); v != nil { + match := useID + sgs := v.(*schema.Set).List() + if len(sgs) > 0 { + match = false + for _, v := range v.(*schema.Set).List() { + if strings.HasPrefix(v.(string), "sg-") { + match = true + break + } + } + } + + useID = match + } + + // Build up the security groups + sgs := make([]string, 0, len(instance.SecurityGroups)) + if useID { + for _, sg := range instance.SecurityGroups { + sgs = append(sgs, *sg.GroupId) + } + log.Printf("[DEBUG] Setting Security Group IDs: %#v", sgs) + if err := d.Set("vpc_security_group_ids", sgs); err != nil { + return err + } + if err := d.Set("security_groups", []string{}); err != nil { + return err + } + } else { + for _, sg := range instance.SecurityGroups { + sgs = append(sgs, *sg.GroupName) + } + log.Printf("[DEBUG] Setting Security Group Names: %#v", sgs) + if err := d.Set("security_groups", sgs); err != nil { + return err + } + if err := d.Set("vpc_security_group_ids", []string{}); err != nil { + return err + } + } + return nil +} + type awsInstanceOpts struct { BlockDeviceMappings []*ec2.BlockDeviceMapping DisableAPITermination *bool diff --git a/website/source/docs/providers/aws/d/instance.html.markdown b/website/source/docs/providers/aws/d/instance.html.markdown new file mode 100644 index 000000000..f459fe877 --- /dev/null +++ b/website/source/docs/providers/aws/d/instance.html.markdown @@ -0,0 +1,93 @@ +--- +layout: "aws" +page_title: "AWS: aws_instance" +sidebar_current: "docs-aws-datasource-instance" +description: |- + Get information on a Amazon EC2 Instance. +--- + +# aws\_instance + +Use this data source to get the ID of an EC2 Instance for use in other +resources. + +## Example Usage + +``` +data "aws_instance" "foo" { + instance_id = "i-instanceid" + filter { + name = "image-id" + values = ["ami-xxxxxxxx"] + } + filter { + name = "tag:Name" + values = ["instance-name-tag"] + } +} +``` + +## Argument Reference + +* `instance_id` - (Optional) Specify the exact Instance ID to populate the data source with. + +* `filter` - (Optional) One or more name/value pairs to filter off of. There are +several valid keys, for a full reference, check out +[describe-instances in the AWS CLI reference][1]. + +~> **NOTE:** At least one of `filter` or `instance_id` must be specified. + +~> **NOTE:** If more or less than a single match is returned by the search, +Terraform will fail. Ensure that your search is specific enough to return +a single Instance ID only. + +## Attributes Reference + +`id` is set to the ID of the found Instance. In addition, the following attributes +are exported: + +~> **NOTE:** Some values are not always set and may not be available for +interpolation. + +* `associate_public_ip_address` - Whether or not the instance is associated with a public ip address or not (Boolean). +* `availability_zone` - The availability zone of the instance. +* `ebs_block_device` - The EBS block device mappings of the instance. + * `delete_on_termination` - If the EBS volume will be deleted on termination. + * `device_name` - The physical name of the device. + * `encrypted` - If the EBS volume is encrypted. + * `iops` - `0` If the EBS volume is not a provisioned IOPS image, otherwise the supported IOPS count. + * `snapshot_id` - The ID of the snapshot. + * `volume_size` - The size of the volume, in GiB. + * `volume_type` - The volume type. +* `ebs_optimized` - Whether the instance is ebs optimized or not (Boolean). +* `ephemeral_block_device` - The ephemeral block device mappings of the instance. + * `device_name` - The physical name of the device. + * `no_device` - Whether the specified device included in the device mapping was suppressed or not (Boolean). + * `virtual_name` - The virtual device name +* `iam_instance_profile` - The instance profile associated with the instance. Specified as an ARN. +* `instance_type` - The type of the instance. +* `key_name` - The key name of the instance. +* `monitoring` - Whether detailed monitoring is enabled or disabled for the instance (Boolean). +* `network_interface_id` - The ID of the network interface that was created with the instance. +* `placement_group` - The placement group of the instance. +* `private_dns` - The private DNS name assigned to the instance. Can only be + used inside the Amazon EC2, and only available if you've enabled DNS hostnames + for your VPC. +* `private_ip` - The private IP address assigned to the instance. +* `public_dns` - The public DNS name assigned to the instance. For EC2-VPC, this + is only available if you've enabled DNS hostnames for your VPC. +* `public_ip` - The public IP address assigned to the instance, if applicable. **NOTE**: If you are using an [`aws_eip`](/docs/providers/aws/r/eip.html) with your instance, you should refer to the EIP's address directly and not use `public_ip`, as this field will change after the EIP is attached. +* `root_block_device` - The root block device mappings of the instance + * `delete_on_termination` - If the root block device will be deleted on termination. + * `iops` - `0` If the volume is not a provisioned IOPS image, otherwise the supported IOPS count. + * `volume_size` - The size of the volume, in GiB. + * `volume_type` - The type of the volume. +* `security_groups` - The associated security groups. +* `source_dest_check` - Whether the network interface performs source/destination checking (Boolean). +* `subnet_id` - The VPC subnet ID. +* `user_data` - The User Data supplied to the instance. +* `tags` - A mapping of tags assigned to the instance. +* `tenancy` - The tenancy of the instance (dedicated | default | host ). +* `vpc_security_group_ids` - The associated security groups in non-default VPC. + +[1]: http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instances.html