From 52027820c2977500c43a49474644f3c4743e96c7 Mon Sep 17 00:00:00 2001 From: Piotr Kruk Date: Mon, 19 Oct 2020 08:55:09 +0200 Subject: [PATCH 1/8] init skeleton for ds aws_key_pair --- aws/data_source_aws_key_pair.go | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 aws/data_source_aws_key_pair.go diff --git a/aws/data_source_aws_key_pair.go b/aws/data_source_aws_key_pair.go new file mode 100644 index 00000000000..4f3b192cde6 --- /dev/null +++ b/aws/data_source_aws_key_pair.go @@ -0,0 +1,41 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceAwsKeyPair() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsKeyPair, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "fingerprint": { + Type: schema.TypeString, + Required: false, + } + }, + } +} + +func dataSourceAwsKeyPair(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + params := &ec2.DescribeKeyPairs{} + + keyName := d.Get("name").(string) + params.KeyName = []*string(aws.String(keyname)) + log.Printf("[DEBUG] Reading key name: %s", keyName) + resp, err := conn.DescribeKeyPairs(params) + if err != nil { + return err + } + log.Printf("Resp: %s", resp) + v := KeyPairInfo[0] + d.set("fingerprint", v.keyFingerprint) + + return nil +} From 04ad368b20b493685c2db08d2de2f9ccf57721b8 Mon Sep 17 00:00:00 2001 From: Piotr Kruk Date: Sun, 25 Oct 2020 00:11:16 +0200 Subject: [PATCH 2/8] Add aws_key_pair data source --- aws/data_source_aws_key_pair.go | 56 ++++++++++++++++++------ aws/data_source_aws_key_pair_test.go | 47 ++++++++++++++++++++ website/docs/d/key_pair.html.markdown | 62 +++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 aws/data_source_aws_key_pair_test.go create mode 100644 website/docs/d/key_pair.html.markdown diff --git a/aws/data_source_aws_key_pair.go b/aws/data_source_aws_key_pair.go index 4f3b192cde6..c817fcaa137 100644 --- a/aws/data_source_aws_key_pair.go +++ b/aws/data_source_aws_key_pair.go @@ -1,41 +1,69 @@ package aws import ( + "errors" + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func dataSourceAwsKeyPair() *schema.Resource { return &schema.Resource{ - Read: dataSourceAwsKeyPair, + Read: dataSourceAwsKeyPairRead, Schema: map[string]*schema.Schema{ - "name": { + "key_name": { Type: schema.TypeString, Required: true, }, "fingerprint": { - Type: schema.TypeString, - Required: false, - } + Type: schema.TypeString, + Computed: true, + }, + "filter": dataSourceFiltersSchema(), }, } } -func dataSourceAwsKeyPair(d *schema.ResourceData, meta interface{}) error { +func dataSourceAwsKeyPairRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - params := &ec2.DescribeKeyPairs{} + params := &ec2.DescribeKeyPairsInput{} + filters, filtersOk := d.GetOk("filter") + if filtersOk { + params.Filters = buildAwsDataSourceFilters(filters.(*schema.Set)) + } + + keyName := d.Get("key_name").(string) + + params.KeyNames = []*string{ + aws.String(keyName), + } - keyName := d.Get("name").(string) - params.KeyName = []*string(aws.String(keyname)) - log.Printf("[DEBUG] Reading key name: %s", keyName) + log.Printf("[DEBUG] Reading key pair: %s", keyName) resp, err := conn.DescribeKeyPairs(params) if err != nil { - return err + return fmt.Errorf("error describing EC2 Key Pairs: %w", err) } - log.Printf("Resp: %s", resp) - v := KeyPairInfo[0] - d.set("fingerprint", v.keyFingerprint) + + if resp == nil || len(resp.KeyPairs) == 0 { + return errors.New("no matching Key Pair found") + } + + filteredKeyPair := resp.KeyPairs + + if len(filteredKeyPair) > 1 { + return fmt.Errorf("Your query returned more than one result. Please try a more " + + "specific search criteria") + } + + keyPair := filteredKeyPair[0] + log.Printf("[DEBUG] aws_key_pair - Single key pair found: %s", *keyPair.KeyName) + + d.Set("fingerprint", keyPair.KeyFingerprint) + d.SetId(aws.StringValue(keyPair.KeyPairId)) return nil } diff --git a/aws/data_source_aws_key_pair_test.go b/aws/data_source_aws_key_pair_test.go new file mode 100644 index 00000000000..a450ffa4615 --- /dev/null +++ b/aws/data_source_aws_key_pair_test.go @@ -0,0 +1,47 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceAwsKeyPair_basic(t *testing.T) { + keyName, publicKey := "testKey", "ssh-rsa testKey" + resourceName := "data.aws_key_pair.default" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVolumeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsKeyPairConfig(keyName, publicKey), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "key_name", keyName), + ), + }, + }, + }) +} + +func testAccDataSourceAwsKeyPairConfig(keyName, publicKey string) string { + return fmt.Sprintf(` +data "aws_region" "current" {} + +resource "aws_key_pair" "default" { + + key_name = "%s" + public_key = "%s" + + tags = { + TestIdentifierSet = "testAccDataSourceAwsKeyPair" + } +} + +data "aws_key_pair" "default" { + key_name = aws_key_pair.default.key_name +} + +`, keyName, publicKey) +} diff --git a/website/docs/d/key_pair.html.markdown b/website/docs/d/key_pair.html.markdown new file mode 100644 index 00000000000..7d21a1e1b16 --- /dev/null +++ b/website/docs/d/key_pair.html.markdown @@ -0,0 +1,62 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_key_pair" +description: |- + Provides details about a specific EC2 Key Pair +--- + +# Data Source: aws_key_pair + +`aws_key_pair` provides details about a specific EC2 Key Pair. + +This data source allows to find a EC2 Key Pair given name and certain search criteria. + +## Example Usage + +The following example shows how to get a EC2 Key Pair from its name. + + +```hcl +data "aws_key_pair" "example" { + key_name = "test" + filter { + name = "tag:Component" + values = ["web"] + } +} + +output "fingerprint" { + value = data.aws_key_pair.example.fingerprint +} + +output "name" { + value = data.aws_key_pair.example.key_name +} + +output "id" { + value = data.aws_key_pair.example.id +} +``` + +## Argument Reference + +The arguments of this data source act as filters for querying the available +Key Pairs. The given filter must match exactly one Key Pairs. + +* `key_name` - (Required) The Key Pair name. + +* `filter` - (Optional) Custom filter block as described below. + +## Attributes Reference + +All of the argument attributes except `filter` blocks are also exported as +result attributes. This data source will complete the data by populating +any fields that are not included in the configuration with the data for +the selected Key Pair. + +The following attributes are additionally exported: + +* `id` - Amazon Resource Name (ARN) of Key Pair +* `fingerprint` - The SHA-1 digest of the DER encoded private key.. + From 74ea51f4f179fd5f6925fa6162b3277b9719bed4 Mon Sep 17 00:00:00 2001 From: Piotr Kruk Date: Sun, 25 Oct 2020 00:23:41 +0200 Subject: [PATCH 3/8] fix test --- aws/data_source_aws_key_pair_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/data_source_aws_key_pair_test.go b/aws/data_source_aws_key_pair_test.go index a450ffa4615..f1e7f40b04b 100644 --- a/aws/data_source_aws_key_pair_test.go +++ b/aws/data_source_aws_key_pair_test.go @@ -8,7 +8,7 @@ import ( ) func TestAccDataSourceAwsKeyPair_basic(t *testing.T) { - keyName, publicKey := "testKey", "ssh-rsa testKey" + keyName, publicKey := "testKey", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 email@example.com" resourceName := "data.aws_key_pair.default" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, From f5c5bc326f813c3c1edeecaad56d49f42e35dfca Mon Sep 17 00:00:00 2001 From: Piotr Kruk Date: Sun, 25 Oct 2020 00:38:24 +0200 Subject: [PATCH 4/8] linting test --- aws/data_source_aws_key_pair_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/data_source_aws_key_pair_test.go b/aws/data_source_aws_key_pair_test.go index f1e7f40b04b..714a10462b4 100644 --- a/aws/data_source_aws_key_pair_test.go +++ b/aws/data_source_aws_key_pair_test.go @@ -40,7 +40,7 @@ resource "aws_key_pair" "default" { } data "aws_key_pair" "default" { - key_name = aws_key_pair.default.key_name + key_name = aws_key_pair.default.key_name } `, keyName, publicKey) From ea9ffc74c64ccb5628f3f9ebbd9c4300b985c387 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 10 Nov 2021 13:12:17 -0500 Subject: [PATCH 5/8] Move new files. --- .../service/ec2/key_pair_data_source.go | 0 .../service/ec2/key_pair_data_source_test.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename aws/data_source_aws_key_pair.go => internal/service/ec2/key_pair_data_source.go (100%) rename aws/data_source_aws_key_pair_test.go => internal/service/ec2/key_pair_data_source_test.go (100%) diff --git a/aws/data_source_aws_key_pair.go b/internal/service/ec2/key_pair_data_source.go similarity index 100% rename from aws/data_source_aws_key_pair.go rename to internal/service/ec2/key_pair_data_source.go diff --git a/aws/data_source_aws_key_pair_test.go b/internal/service/ec2/key_pair_data_source_test.go similarity index 100% rename from aws/data_source_aws_key_pair_test.go rename to internal/service/ec2/key_pair_data_source_test.go From 7f099e425ea7640ee782a3c819e24f314efaa68b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 10 Nov 2021 14:25:36 -0500 Subject: [PATCH 6/8] d/aws_key_pair: New code structure. Acceptance test output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2KeyPairDataSource_basic' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2KeyPairDataSource_basic -timeout 180m === RUN TestAccEC2KeyPairDataSource_basic === PAUSE TestAccEC2KeyPairDataSource_basic === CONT TestAccEC2KeyPairDataSource_basic --- PASS: TestAccEC2KeyPairDataSource_basic (14.10s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 17.678s --- internal/provider/provider.go | 1 + internal/service/ec2/errors.go | 1 + internal/service/ec2/find.go | 56 +++++++++++++ internal/service/ec2/key_pair_data_source.go | 81 +++++++++++-------- .../service/ec2/key_pair_data_source_test.go | 77 +++++++++++++----- website/docs/d/key_pair.html.markdown | 35 ++++---- 6 files changed, 181 insertions(+), 70 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 545d95836dd..bacf80d6a78 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -451,6 +451,7 @@ func Provider() *schema.Provider { "aws_instance": ec2.DataSourceInstance(), "aws_instances": ec2.DataSourceInstances(), "aws_internet_gateway": ec2.DataSourceInternetGateway(), + "aws_key_pair": ec2.DataSourceKeyPair(), "aws_launch_template": ec2.DataSourceLaunchTemplate(), "aws_nat_gateway": ec2.DataSourceNatGateway(), "aws_network_acls": ec2.DataSourceNetworkACLs(), diff --git a/internal/service/ec2/errors.go b/internal/service/ec2/errors.go index 0928c4546b5..2912988972a 100644 --- a/internal/service/ec2/errors.go +++ b/internal/service/ec2/errors.go @@ -14,6 +14,7 @@ const ( ErrCodeGatewayNotAttached = "Gateway.NotAttached" ErrCodeInvalidAssociationIDNotFound = "InvalidAssociationID.NotFound" ErrCodeInvalidAttachmentIDNotFound = "InvalidAttachmentID.NotFound" + ErrCodeInvalidKeyPairNotFound = "InvalidKeyPair.NotFound" ErrCodeInvalidParameter = "InvalidParameter" ErrCodeInvalidParameterException = "InvalidParameterException" ErrCodeInvalidParameterValue = "InvalidParameterValue" diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index e12330d2c76..e2800ee55bd 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -1062,6 +1062,62 @@ func FindInternetGatewayAttachment(conn *ec2.EC2, internetGatewayID, vpcID strin return attachment, nil } +func FindKeyPair(conn *ec2.EC2, input *ec2.DescribeKeyPairsInput) (*ec2.KeyPairInfo, error) { + output, err := FindKeyPairs(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindKeyPairs(conn *ec2.EC2, input *ec2.DescribeKeyPairsInput) ([]*ec2.KeyPairInfo, error) { + output, err := conn.DescribeKeyPairs(input) + + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidKeyPairNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output.KeyPairs, nil +} + +func FindKeyPairByName(conn *ec2.EC2, name string) (*ec2.KeyPairInfo, error) { + input := &ec2.DescribeKeyPairsInput{ + KeyNames: aws.StringSlice([]string{name}), + } + + output, err := FindKeyPair(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.KeyName) != name { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + func FindManagedPrefixListByID(conn *ec2.EC2, id string) (*ec2.ManagedPrefixList, error) { input := &ec2.DescribeManagedPrefixListsInput{ PrefixListIds: aws.StringSlice([]string{id}), diff --git a/internal/service/ec2/key_pair_data_source.go b/internal/service/ec2/key_pair_data_source.go index c817fcaa137..20e15aeae97 100644 --- a/internal/service/ec2/key_pair_data_source.go +++ b/internal/service/ec2/key_pair_data_source.go @@ -1,69 +1,86 @@ -package aws +package ec2 import ( - "errors" "fmt" - "log" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -func dataSourceAwsKeyPair() *schema.Resource { +func DataSourceKeyPair() *schema.Resource { return &schema.Resource{ - Read: dataSourceAwsKeyPairRead, + Read: dataSourceKeyPairRead, + Schema: map[string]*schema.Schema{ - "key_name": { + "arn": { Type: schema.TypeString, - Required: true, + Computed: true, }, + "filter": DataSourceFiltersSchema(), "fingerprint": { Type: schema.TypeString, Computed: true, }, - "filter": dataSourceFiltersSchema(), + "key_name": { + Type: schema.TypeString, + Optional: true, + }, + "key_pair_id": { + Type: schema.TypeString, + Optional: true, + }, + "tags": tftags.TagsSchemaComputed(), }, } } -func dataSourceAwsKeyPairRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ec2conn +func dataSourceKeyPairRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).EC2Conn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - params := &ec2.DescribeKeyPairsInput{} - filters, filtersOk := d.GetOk("filter") - if filtersOk { - params.Filters = buildAwsDataSourceFilters(filters.(*schema.Set)) - } - - keyName := d.Get("key_name").(string) + input := &ec2.DescribeKeyPairsInput{} - params.KeyNames = []*string{ - aws.String(keyName), + if v, ok := d.GetOk("filter"); ok { + input.Filters = BuildFiltersDataSource(v.(*schema.Set)) } - log.Printf("[DEBUG] Reading key pair: %s", keyName) - resp, err := conn.DescribeKeyPairs(params) - if err != nil { - return fmt.Errorf("error describing EC2 Key Pairs: %w", err) + if v, ok := d.GetOk("key_name"); ok { + input.KeyNames = aws.StringSlice([]string{v.(string)}) } - if resp == nil || len(resp.KeyPairs) == 0 { - return errors.New("no matching Key Pair found") + if v, ok := d.GetOk("key_pair_id"); ok { + input.KeyPairIds = aws.StringSlice([]string{v.(string)}) } - filteredKeyPair := resp.KeyPairs + keyPair, err := FindKeyPair(conn, input) - if len(filteredKeyPair) > 1 { - return fmt.Errorf("Your query returned more than one result. Please try a more " + - "specific search criteria") + if err != nil { + return tfresource.SingularDataSourceFindError("EC2 Key Pair", err) } - keyPair := filteredKeyPair[0] - log.Printf("[DEBUG] aws_key_pair - Single key pair found: %s", *keyPair.KeyName) + d.SetId(aws.StringValue(keyPair.KeyPairId)) + keyName := aws.StringValue(keyPair.KeyName) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: ec2.ServiceName, + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("key-pair/%s", keyName), + }.String() + d.Set("arn", arn) d.Set("fingerprint", keyPair.KeyFingerprint) - d.SetId(aws.StringValue(keyPair.KeyPairId)) + d.Set("key_name", keyName) + d.Set("key_pair_id", keyPair.KeyPairId) + + if err := d.Set("tags", KeyValueTags(keyPair.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } return nil } diff --git a/internal/service/ec2/key_pair_data_source_test.go b/internal/service/ec2/key_pair_data_source_test.go index 714a10462b4..fa1b1740db5 100644 --- a/internal/service/ec2/key_pair_data_source_test.go +++ b/internal/service/ec2/key_pair_data_source_test.go @@ -1,47 +1,82 @@ -package aws +package ec2_test import ( "fmt" "testing" + "github.com/aws/aws-sdk-go/service/ec2" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" ) -func TestAccDataSourceAwsKeyPair_basic(t *testing.T) { - keyName, publicKey := "testKey", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 email@example.com" - resourceName := "data.aws_key_pair.default" +func TestAccEC2KeyPairDataSource_basic(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSource1Name := "data.aws_key_pair.by_id" + dataSource2Name := "data.aws_key_pair.by_name" + dataSource3Name := "data.aws_key_pair.by_filter" + resourceName := "aws_key_pair.test" + + publicKey, _, err := sdkacctest.RandSSHKeyPair(acctest.DefaultEmailAddress) + if err != nil { + t.Fatalf("error generating random SSH key: %s", err) + } + resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckVolumeDestroy, + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, Steps: []resource.TestStep{ { - Config: testAccDataSourceAwsKeyPairConfig(keyName, publicKey), + Config: testAccKeyPairDataSourceConfig(rName, publicKey), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "key_name", keyName), + resource.TestCheckResourceAttrPair(dataSource1Name, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSource1Name, "fingerprint", resourceName, "fingerprint"), + resource.TestCheckResourceAttrPair(dataSource1Name, "key_name", resourceName, "key_name"), + resource.TestCheckResourceAttrPair(dataSource1Name, "key_pair_id", resourceName, "key_pair_id"), + resource.TestCheckResourceAttrPair(dataSource1Name, "tags.%", resourceName, "tags.%"), + + resource.TestCheckResourceAttrPair(dataSource2Name, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSource2Name, "fingerprint", resourceName, "fingerprint"), + resource.TestCheckResourceAttrPair(dataSource2Name, "key_name", resourceName, "key_name"), + resource.TestCheckResourceAttrPair(dataSource2Name, "key_pair_id", resourceName, "key_pair_id"), + resource.TestCheckResourceAttrPair(dataSource2Name, "tags.%", resourceName, "tags.%"), + + resource.TestCheckResourceAttrPair(dataSource3Name, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSource3Name, "fingerprint", resourceName, "fingerprint"), + resource.TestCheckResourceAttrPair(dataSource3Name, "key_name", resourceName, "key_name"), + resource.TestCheckResourceAttrPair(dataSource3Name, "key_pair_id", resourceName, "key_pair_id"), + resource.TestCheckResourceAttrPair(dataSource3Name, "tags.%", resourceName, "tags.%"), ), }, }, }) } -func testAccDataSourceAwsKeyPairConfig(keyName, publicKey string) string { +func testAccKeyPairDataSourceConfig(rName, publicKey string) string { return fmt.Sprintf(` -data "aws_region" "current" {} - -resource "aws_key_pair" "default" { +data "aws_key_pair" "by_name" { + key_name = aws_key_pair.test.key_name +} - key_name = "%s" - public_key = "%s" +data "aws_key_pair" "by_id" { + key_pair_id = aws_key_pair.test.key_pair_id +} - tags = { - TestIdentifierSet = "testAccDataSourceAwsKeyPair" +data "aws_key_pair" "by_filter" { + filter { + name = "tag:Name" + values = [aws_key_pair.test.tags["Name"]] } } -data "aws_key_pair" "default" { - key_name = aws_key_pair.default.key_name -} +resource "aws_key_pair" "test" { + key_name = %[1]q + public_key = %[2]q -`, keyName, publicKey) + tags = { + Name = %[1]q + } +} +`, rName, publicKey) } diff --git a/website/docs/d/key_pair.html.markdown b/website/docs/d/key_pair.html.markdown index 7d21a1e1b16..577a47f0b82 100644 --- a/website/docs/d/key_pair.html.markdown +++ b/website/docs/d/key_pair.html.markdown @@ -3,21 +3,18 @@ subcategory: "EC2" layout: "aws" page_title: "AWS: aws_key_pair" description: |- - Provides details about a specific EC2 Key Pair + Provides details about a specific EC2 Key Pair. --- # Data Source: aws_key_pair -`aws_key_pair` provides details about a specific EC2 Key Pair. - -This data source allows to find a EC2 Key Pair given name and certain search criteria. +Use this data source to get information about a specific EC2 Key Pair. ## Example Usage The following example shows how to get a EC2 Key Pair from its name. - -```hcl +```terraform data "aws_key_pair" "example" { key_name = "test" filter { @@ -42,21 +39,25 @@ output "id" { ## Argument Reference The arguments of this data source act as filters for querying the available -Key Pairs. The given filter must match exactly one Key Pairs. - -* `key_name` - (Required) The Key Pair name. +Key Pairs. The given filters must match exactly one Key Pair +whose data will be exported as attributes. +* `key_id` - (Optional) The Key Pair ID. +* `key_name` - (Optional) The Key Pair name. * `filter` - (Optional) Custom filter block as described below. -## Attributes Reference +### filter Configuration Block + +The following arguments are supported by the `filter` configuration block: -All of the argument attributes except `filter` blocks are also exported as -result attributes. This data source will complete the data by populating -any fields that are not included in the configuration with the data for -the selected Key Pair. +* `name` - (Required) The name of the filter field. Valid values can be found in the [EC2 DescribeKeyPairs API Reference](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeKeyPairs.html). +* `values` - (Required) Set of values that are accepted for the given filter field. Results will be selected if any given value matches. -The following attributes are additionally exported: +## Attributes Reference -* `id` - Amazon Resource Name (ARN) of Key Pair -* `fingerprint` - The SHA-1 digest of the DER encoded private key.. +In addition to all arguments above, the following attributes are exported: +* `id` - ID of the Key Pair. +* `arn` - The ARN of the Key Pair. +* `fingerprint` - The SHA-1 digest of the DER encoded private key. +* `tags` - Any tags assigned to the Key Pair. \ No newline at end of file From e17687cf56f3ccc2277b893b17fa41902caa4c25 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 10 Nov 2021 14:25:50 -0500 Subject: [PATCH 7/8] Add CHANGELOG entry. --- .changelog/15829.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/15829.txt diff --git a/.changelog/15829.txt b/.changelog/15829.txt new file mode 100644 index 00000000000..4137078dc92 --- /dev/null +++ b/.changelog/15829.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +aws_key_pair +``` \ No newline at end of file From ca339df7d32095c045dfa15b841b9c4ec85d5a72 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 10 Nov 2021 14:58:00 -0500 Subject: [PATCH 8/8] r/aws_key_pair: Support importing 'key_name_prefix' (#9574). Acceptance test output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2KeyPair_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2KeyPair_ -timeout 180m === RUN TestAccEC2KeyPair_basic === PAUSE TestAccEC2KeyPair_basic === RUN TestAccEC2KeyPair_tags === PAUSE TestAccEC2KeyPair_tags === RUN TestAccEC2KeyPair_nameGenerated === PAUSE TestAccEC2KeyPair_nameGenerated === RUN TestAccEC2KeyPair_namePrefix === PAUSE TestAccEC2KeyPair_namePrefix === RUN TestAccEC2KeyPair_disappears === PAUSE TestAccEC2KeyPair_disappears === CONT TestAccEC2KeyPair_basic === CONT TestAccEC2KeyPair_namePrefix === CONT TestAccEC2KeyPair_nameGenerated === CONT TestAccEC2KeyPair_tags === CONT TestAccEC2KeyPair_disappears --- PASS: TestAccEC2KeyPair_disappears (12.31s) --- PASS: TestAccEC2KeyPair_namePrefix (15.84s) --- PASS: TestAccEC2KeyPair_basic (16.10s) --- PASS: TestAccEC2KeyPair_nameGenerated (16.95s) --- PASS: TestAccEC2KeyPair_tags (35.31s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 41.854s --- internal/service/ec2/key_pair.go | 117 ++++++++++++-------------- internal/service/ec2/key_pair_test.go | 73 ++++++---------- 2 files changed, 80 insertions(+), 110 deletions(-) diff --git a/internal/service/ec2/key_pair.go b/internal/service/ec2/key_pair.go index 6cb05fcbe59..4df8b061c62 100644 --- a/internal/service/ec2/key_pair.go +++ b/internal/service/ec2/key_pair.go @@ -8,12 +8,13 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) @@ -34,20 +35,33 @@ func ResourceKeyPair() *schema.Resource { MigrateState: KeyPairMigrateState, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "fingerprint": { + Type: schema.TypeString, + Computed: true, + }, "key_name": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, - ConflictsWith: []string{"key_name_prefix"}, ValidateFunc: validation.StringLenBetween(0, 255), + ConflictsWith: []string{"key_name_prefix"}, }, "key_name_prefix": { Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, - ConflictsWith: []string{"key_name"}, ValidateFunc: validation.StringLenBetween(0, 255-resource.UniqueIDSuffixLength), + ConflictsWith: []string{"key_name"}, + }, + "key_pair_id": { + Type: schema.TypeString, + Computed: true, }, "public_key": { Type: schema.TypeString, @@ -62,20 +76,8 @@ func ResourceKeyPair() *schema.Resource { } }, }, - "fingerprint": { - Type: schema.TypeString, - Computed: true, - }, - "key_pair_id": { - Type: schema.TypeString, - Computed: true, - }, "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), - "arn": { - Type: schema.TypeString, - Computed: true, - }, }, } } @@ -85,29 +87,21 @@ func resourceKeyPairCreate(d *schema.ResourceData, meta interface{}) error { defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - var keyName string - if v, ok := d.GetOk("key_name"); ok { - keyName = v.(string) - } else if v, ok := d.GetOk("key_name_prefix"); ok { - keyName = resource.PrefixedUniqueId(v.(string)) - d.Set("key_name", keyName) - } else { - keyName = resource.UniqueId() - d.Set("key_name", keyName) - } + keyName := create.Name(d.Get("key_name").(string), d.Get("key_name_prefix").(string)) - publicKey := d.Get("public_key").(string) - req := &ec2.ImportKeyPairInput{ + input := &ec2.ImportKeyPairInput{ KeyName: aws.String(keyName), - PublicKeyMaterial: []byte(publicKey), + PublicKeyMaterial: []byte(d.Get("public_key").(string)), TagSpecifications: ec2TagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeKeyPair), } - resp, err := conn.ImportKeyPair(req) + + output, err := conn.ImportKeyPair(input) + if err != nil { - return fmt.Errorf("Error import KeyPair: %s", err) + return fmt.Errorf("error importing EC2 Key Pair (%s): %w", keyName, err) } - d.SetId(aws.StringValue(resp.KeyName)) + d.SetId(aws.StringValue(output.KeyName)) return resourceKeyPairRead(d, meta) } @@ -117,35 +111,32 @@ func resourceKeyPairRead(d *schema.ResourceData, meta interface{}) error { defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - req := &ec2.DescribeKeyPairsInput{ - KeyNames: []*string{aws.String(d.Id())}, - } - resp, err := conn.DescribeKeyPairs(req) - if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidKeyPair.NotFound", "") { - log.Printf("[WARN] Key Pair (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - return fmt.Errorf("Error retrieving KeyPair: %s", err) - } + keyPair, err := FindKeyPairByName(conn, d.Id()) - if len(resp.KeyPairs) == 0 { - log.Printf("[WARN] Key Pair (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 Key Pair (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - kp := resp.KeyPairs[0] - - if aws.StringValue(kp.KeyName) != d.Id() { - return fmt.Errorf("Unable to find key pair within: %#v", resp.KeyPairs) + if err != nil { + return fmt.Errorf("error reading EC2 Key Pair (%s): %w", d.Id(), err) } - d.Set("key_name", kp.KeyName) - d.Set("fingerprint", kp.KeyFingerprint) - d.Set("key_pair_id", kp.KeyPairId) - tags := KeyValueTags(kp.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: ec2.ServiceName, + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("key-pair/%s", d.Id()), + }.String() + d.Set("arn", arn) + d.Set("fingerprint", keyPair.KeyFingerprint) + d.Set("key_name", keyPair.KeyName) + d.Set("key_name_prefix", create.NamePrefixFromName(aws.StringValue(keyPair.KeyName))) + d.Set("key_pair_id", keyPair.KeyPairId) + + tags := KeyValueTags(keyPair.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { @@ -156,16 +147,6 @@ func resourceKeyPairRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting tags_all: %w", err) } - arn := arn.ARN{ - Partition: meta.(*conns.AWSClient).Partition, - Service: ec2.ServiceName, - Region: meta.(*conns.AWSClient).Region, - AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("key-pair/%s", d.Id()), - }.String() - - d.Set("arn", arn) - return nil } @@ -175,7 +156,7 @@ func resourceKeyPairUpdate(d *schema.ResourceData, meta interface{}) error { if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") if err := UpdateTags(conn, d.Get("key_pair_id").(string), o, n); err != nil { - return fmt.Errorf("error adding tags: %s", err) + return fmt.Errorf("error updating tags: %w", err) } } @@ -185,8 +166,14 @@ func resourceKeyPairUpdate(d *schema.ResourceData, meta interface{}) error { func resourceKeyPairDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn + log.Printf("[DEBUG] Deleting EC2 Key Pair: %s", d.Id()) _, err := conn.DeleteKeyPair(&ec2.DeleteKeyPairInput{ KeyName: aws.String(d.Id()), }) - return err + + if err != nil { + return fmt.Errorf("error deleting EC2 Key Pair (%s): %w", d.Id(), err) + } + + return nil } diff --git a/internal/service/ec2/key_pair_test.go b/internal/service/ec2/key_pair_test.go index 7baf595cdbf..5a3ff38f8c5 100644 --- a/internal/service/ec2/key_pair_test.go +++ b/internal/service/ec2/key_pair_test.go @@ -3,18 +3,17 @@ package ec2_test import ( "fmt" "regexp" - "strings" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" sdkacctest "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" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccEC2KeyPair_basic(t *testing.T) { @@ -40,6 +39,7 @@ func TestAccEC2KeyPair_basic(t *testing.T) { acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "ec2", fmt.Sprintf("key-pair/%s", rName)), resource.TestMatchResourceAttr(resourceName, "fingerprint", regexp.MustCompile(`[a-f0-9]{2}(:[a-f0-9]{2}){15}`)), resource.TestCheckResourceAttr(resourceName, "key_name", rName), + resource.TestCheckResourceAttr(resourceName, "key_name_prefix", ""), resource.TestCheckResourceAttr(resourceName, "public_key", publicKey), ), }, @@ -104,7 +104,7 @@ func TestAccEC2KeyPair_tags(t *testing.T) { }) } -func TestAccEC2KeyPair_generatedName(t *testing.T) { +func TestAccEC2KeyPair_nameGenerated(t *testing.T) { var keyPair ec2.KeyPairInfo resourceName := "aws_key_pair.test" @@ -120,11 +120,11 @@ func TestAccEC2KeyPair_generatedName(t *testing.T) { CheckDestroy: testAccCheckKeyPairDestroy, Steps: []resource.TestStep{ { - Config: testAccKeyPairConfig_generatedName(publicKey), + Config: testAccKeyPairNameGeneratedConfig(publicKey), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKeyPairExists(resourceName, &keyPair), - testAccCheckKeyPairKeyNamePrefix(&keyPair, "terraform-"), - resource.TestMatchResourceAttr(resourceName, "key_name", regexp.MustCompile(`^terraform-`)), + create.TestCheckResourceAttrNameGenerated(resourceName, "key_name"), + resource.TestCheckResourceAttr(resourceName, "key_name_prefix", "terraform-"), ), }, { @@ -153,18 +153,18 @@ func TestAccEC2KeyPair_namePrefix(t *testing.T) { CheckDestroy: testAccCheckKeyPairDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckKeyPairPrefixNameConfig(publicKey), + Config: testAccCheckKeyPairNamePrefixConfig("tf-acc-test-prefix-", publicKey), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckKeyPairExists(resourceName, &keyPair), - testAccCheckKeyPairKeyNamePrefix(&keyPair, "baz-"), - resource.TestMatchResourceAttr(resourceName, "key_name", regexp.MustCompile(`^baz-`)), + create.TestCheckResourceAttrNameFromPrefix(resourceName, "key_name", "tf-acc-test-prefix-"), + resource.TestCheckResourceAttr(resourceName, "key_name_prefix", "tf-acc-test-prefix-"), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"key_name_prefix", "public_key"}, + ImportStateVerifyIgnore: []string{"public_key"}, }, }, }) @@ -206,35 +206,23 @@ func testAccCheckKeyPairDestroy(s *terraform.State) error { continue } - // Try to find key pair - resp, err := conn.DescribeKeyPairs(&ec2.DescribeKeyPairsInput{ - KeyNames: []*string{aws.String(rs.Primary.ID)}, - }) - if err == nil { - if len(resp.KeyPairs) > 0 { - return fmt.Errorf("still exist.") - } - return nil + _, err := tfec2.FindKeyPairByName(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue } - if !tfawserr.ErrMessageContains(err, "InvalidKeyPair.NotFound", "") { + if err != nil { return err } + + return fmt.Errorf("EC2 Key Pair %s still exists", rs.Primary.ID) } return nil } -func testAccCheckKeyPairKeyNamePrefix(conf *ec2.KeyPairInfo, namePrefix string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if !strings.HasPrefix(aws.StringValue(conf.KeyName), namePrefix) { - return fmt.Errorf("incorrect key name. expected %s prefix, got %s", namePrefix, aws.StringValue(conf.KeyName)) - } - return nil - } -} - -func testAccCheckKeyPairExists(n string, res *ec2.KeyPairInfo) resource.TestCheckFunc { +func testAccCheckKeyPairExists(n string, v *ec2.KeyPairInfo) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -242,23 +230,18 @@ func testAccCheckKeyPairExists(n string, res *ec2.KeyPairInfo) resource.TestChec } if rs.Primary.ID == "" { - return fmt.Errorf("No KeyPair name is set") + return fmt.Errorf("No EC2 Key Pair ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - resp, err := conn.DescribeKeyPairs(&ec2.DescribeKeyPairsInput{ - KeyNames: []*string{aws.String(rs.Primary.ID)}, - }) + output, err := tfec2.FindKeyPairByName(conn, rs.Primary.ID) + if err != nil { return err } - if len(resp.KeyPairs) != 1 || - aws.StringValue(resp.KeyPairs[0].KeyName) != rs.Primary.ID { - return fmt.Errorf("KeyPair not found") - } - *res = *resp.KeyPairs[0] + *v = *output return nil } @@ -300,7 +283,7 @@ resource "aws_key_pair" "test" { `, rName, publicKey, tagKey1, tagValue1, tagKey2, tagValue2) } -func testAccKeyPairConfig_generatedName(publicKey string) string { +func testAccKeyPairNameGeneratedConfig(publicKey string) string { return fmt.Sprintf(` resource "aws_key_pair" "test" { public_key = %[1]q @@ -308,11 +291,11 @@ resource "aws_key_pair" "test" { `, publicKey) } -func testAccCheckKeyPairPrefixNameConfig(publicKey string) string { +func testAccCheckKeyPairNamePrefixConfig(namePrefix, publicKey string) string { return fmt.Sprintf(` resource "aws_key_pair" "test" { - key_name_prefix = "baz-" - public_key = %[1]q + key_name_prefix = %[1]q + public_key = %[2]q } -`, publicKey) +`, namePrefix, publicKey) }