diff --git a/.changelog/32606.txt b/.changelog/32606.txt new file mode 100644 index 00000000000..1f159bfa674 --- /dev/null +++ b/.changelog/32606.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_lightsail_key_pair: Add `tags` attribute +``` \ No newline at end of file diff --git a/internal/generate/tags/templates/v2/service_tags_slice_body.tmpl b/internal/generate/tags/templates/v2/service_tags_slice_body.tmpl index 4812d87bbd4..c5aa8ebd415 100644 --- a/internal/generate/tags/templates/v2/service_tags_slice_body.tmpl +++ b/internal/generate/tags/templates/v2/service_tags_slice_body.tmpl @@ -254,7 +254,7 @@ func {{ .SetTagsOutFunc }}(ctx context.Context, tags []awstypes.{{ .TagType }}) {{- if ne .CreateTagsFunc "" }} // {{ .CreateTagsFunc }} creates {{ .ServicePackage }} service tags for new resources. -func {{ .CreateTagsFunc }}(ctx context.Context, conn {{ .ClientType }}, identifier{{ if .TagResTypeElem }}, resourceType{{ end }} string, tags []*{{ .TagPackage }}.{{ .TagType }}) error { +func {{ .CreateTagsFunc }}(ctx context.Context, conn {{ .ClientType }}, identifier{{ if .TagResTypeElem }}, resourceType{{ end }} string, tags []awstypes.{{ .TagType }}) error { if len(tags) == 0 { return nil } diff --git a/internal/service/lightsail/generate.go b/internal/service/lightsail/generate.go index d1aad92a595..71da4d3c82a 100644 --- a/internal/service/lightsail/generate.go +++ b/internal/service/lightsail/generate.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ListTagsInIDElem=ResourceName -ServiceTagsSlice -TagInIDElem=ResourceName -UpdateTags +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ListTagsInIDElem=ResourceName -ServiceTagsSlice -TagInIDElem=ResourceName -UpdateTags -CreateTags //go:generate go run ../../generate/servicepackage/main.go // ONLY generate directives and package declaration! Do not add anything else to this file. diff --git a/internal/service/lightsail/key_pair.go b/internal/service/lightsail/key_pair.go index eaa19b265fa..bc20efabffb 100644 --- a/internal/service/lightsail/key_pair.go +++ b/internal/service/lightsail/key_pair.go @@ -18,18 +18,23 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/vault/helper/pgpkeys" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" ) const ( ResKeyPair = "KeyPair" ) -// @SDKResource("aws_lightsail_key_pair") +// @SDKResource("aws_lightsail_key_pair", name=KeyPair) +// @Tags(identifierAttribute="id") func ResourceKeyPair() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceKeyPairCreate, ReadWithoutTimeout: resourceKeyPairRead, + UpdateWithoutTimeout: resourceKeyPairUpdate, DeleteWithoutTimeout: resourceKeyPairDelete, Schema: map[string]*schema.Schema{ @@ -85,7 +90,10 @@ func ResourceKeyPair() *schema.Resource { Type: schema.TypeString, Computed: true, }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), }, + CustomizeDiff: verify.SetTagsDiff, } } @@ -112,6 +120,7 @@ func resourceKeyPairCreate(ctx context.Context, d *schema.ResourceData, meta int // creating new key resp, err := conn.CreateKeyPair(ctx, &lightsail.CreateKeyPairInput{ KeyPairName: aws.String(kName), + Tags: getTagsIn(ctx), }) if err != nil { return sdkdiag.AppendErrorf(diags, "creating Lightsail Key Pair (%s): %s", kName, err) @@ -160,6 +169,10 @@ func resourceKeyPairCreate(ctx context.Context, d *schema.ResourceData, meta int d.SetId(kName) op = resp.Operation + + if err := createTags(ctx, conn, kName, getTagsIn(ctx)); err != nil { + return sdkdiag.AppendErrorf(diags, "creating Lightsail Key Pair (%s): %s", kName, err) + } } diag := expandOperations(ctx, conn, []types.Operation{*op}, "CreateKeyPair", ResKeyPair, kName) @@ -192,9 +205,16 @@ func resourceKeyPairRead(ctx context.Context, d *schema.ResourceData, meta inter d.Set("name", resp.KeyPair.Name) d.Set("fingerprint", resp.KeyPair.Fingerprint) + setTagsOut(ctx, resp.KeyPair.Tags) + return diags } +func resourceKeyPairUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // Tags only. + return resourceKeyPairRead(ctx, d, meta) +} + func resourceKeyPairDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).LightsailClient(ctx) diff --git a/internal/service/lightsail/key_pair_test.go b/internal/service/lightsail/key_pair_test.go index 6ac6708f7bd..268bcd66022 100644 --- a/internal/service/lightsail/key_pair_test.go +++ b/internal/service/lightsail/key_pair_test.go @@ -27,7 +27,11 @@ func TestAccLightsailKeyPair_basic(t *testing.T) { resourceName := "aws_lightsail_key_pair.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, strings.ToLower(lightsail.ServiceID)) + testAccPreCheck(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, strings.ToLower(lightsail.ServiceID)), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckKeyPairDestroy(ctx), @@ -58,7 +62,11 @@ func TestAccLightsailKeyPair_publicKey(t *testing.T) { } resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, strings.ToLower(lightsail.ServiceID)) + testAccPreCheck(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, strings.ToLower(lightsail.ServiceID)), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckKeyPairDestroy(ctx), @@ -86,7 +94,11 @@ func TestAccLightsailKeyPair_encrypted(t *testing.T) { resourceName := "aws_lightsail_key_pair.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, strings.ToLower(lightsail.ServiceID)) + testAccPreCheck(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, strings.ToLower(lightsail.ServiceID)), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckKeyPairDestroy(ctx), @@ -110,7 +122,11 @@ func TestAccLightsailKeyPair_encrypted(t *testing.T) { func TestAccLightsailKeyPair_namePrefix(t *testing.T) { ctx := acctest.Context(t) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, strings.ToLower(lightsail.ServiceID)) + testAccPreCheck(ctx, t) + }, ErrorCheck: acctest.ErrorCheck(t, strings.ToLower(lightsail.ServiceID)), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckKeyPairDestroy(ctx), @@ -128,6 +144,79 @@ func TestAccLightsailKeyPair_namePrefix(t *testing.T) { }) } +func TestAccLightsailKeyPair_tags(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resourceName := "aws_lightsail_key_pair.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, strings.ToLower(lightsail.ServiceID)) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, strings.ToLower(lightsail.ServiceID)), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckKeyPairDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccKeyPairConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKeyPairExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + Config: testAccKeyPairConfig_tags2(rName, "key1", "value1", "key2", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKeyPairExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccKeyPairConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKeyPairExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccLightsailKeyPair_disappears(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resourceName := "aws_lightsail_key_pair.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, strings.ToLower(lightsail.ServiceID)) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, strings.ToLower(lightsail.ServiceID)), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckKeyPairDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccKeyPairConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKeyPairExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tflightsail.ResourceKeyPair(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccCheckKeyPairExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -189,7 +278,7 @@ func testAccCheckKeyPairDestroy(ctx context.Context) resource.TestCheckFunc { func testAccKeyPairConfig_basic(lightsailName string) string { return fmt.Sprintf(` resource "aws_lightsail_key_pair" "test" { - name = "%s" + name = %[1]q } `, lightsailName) } @@ -226,6 +315,31 @@ resource "aws_lightsail_key_pair" "lightsail_key_pair_test_prefixed" { ` } +func testAccKeyPairConfig_tags1(lightsailName, key1, value1 string) string { + return fmt.Sprintf(` +resource "aws_lightsail_key_pair" "test" { + name = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, lightsailName, key1, value1) +} + +func testAccKeyPairConfig_tags2(lightsailName, key1, value1, key2, value2 string) string { + return fmt.Sprintf(` +resource "aws_lightsail_key_pair" "test" { + name = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, lightsailName, key1, value1, key2, value2) +} + const testKeyPairPubKey1 = `mQENBFXbjPUBCADjNjCUQwfxKL+RR2GA6pv/1K+zJZ8UWIF9S0lk7cVIEfJiprzzwiMwBS5cD0da rGin1FHvIWOZxujA7oW0O2TUuatqI3aAYDTfRYurh6iKLC+VS+F7H+/mhfFvKmgr0Y5kDCF1j0T/ 063QZ84IRGucR/X43IY7kAtmxGXH0dYOCzOe5UBX1fTn3mXGe2ImCDWBH7gOViynXmb6XNvXkP0f diff --git a/internal/service/lightsail/service_package_gen.go b/internal/service/lightsail/service_package_gen.go index a0cdc9da6ec..3b51c47220c 100644 --- a/internal/service/lightsail/service_package_gen.go +++ b/internal/service/lightsail/service_package_gen.go @@ -113,6 +113,10 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka { Factory: ResourceKeyPair, TypeName: "aws_lightsail_key_pair", + Name: "KeyPair", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "id", + }, }, { Factory: ResourceLoadBalancer, diff --git a/internal/service/lightsail/tags_gen.go b/internal/service/lightsail/tags_gen.go index 4b685fff261..765a4600e17 100644 --- a/internal/service/lightsail/tags_gen.go +++ b/internal/service/lightsail/tags_gen.go @@ -62,6 +62,15 @@ func setTagsOut(ctx context.Context, tags []awstypes.Tag) { } } +// createTags creates lightsail service tags for new resources. +func createTags(ctx context.Context, conn *lightsail.Client, identifier string, tags []awstypes.Tag) error { + if len(tags) == 0 { + return nil + } + + return updateTags(ctx, conn, identifier, nil, KeyValueTags(ctx, tags)) +} + // updateTags updates lightsail service tags. // The identifier is typically the Amazon Resource Name (ARN), although // it may also be a different identifier depending on the service. diff --git a/website/docs/r/lightsail_key_pair.html.markdown b/website/docs/r/lightsail_key_pair.html.markdown index 5d68f134a34..5c68edb3cb5 100644 --- a/website/docs/r/lightsail_key_pair.html.markdown +++ b/website/docs/r/lightsail_key_pair.html.markdown @@ -47,32 +47,24 @@ resource "aws_lightsail_key_pair" "lg_key_pair" { This resource supports the following arguments: -* `name` - (Optional) The name of the Lightsail Key Pair. If omitted, a unique -name will be generated by Terraform -* `pgp_key` – (Optional) An optional PGP key to encrypt the resulting private -key material. Only used when creating a new key pair -* `public_key` - (Required) The public key material. This public key will be -imported into Lightsail +* `name` - (Optional) The name of the Lightsail Key Pair. If omitted, a unique name will be generated by Terraform +* `pgp_key` – (Optional) An optional PGP key to encrypt the resulting private key material. Only used when creating a new key pair +* `public_key` - (Required) The public key material. This public key will be imported into Lightsail +* `tags` - (Optional) A map of tags to assign to the collection. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. -~> **NOTE:** a PGP key is not required, however it is strongly encouraged. -Without a PGP key, the private key material will be stored in state unencrypted. -`pgp_key` is ignored if `public_key` is supplied. +~> **NOTE:** a PGP key is not required, however it is strongly encouraged. Without a PGP key, the private key material will be stored in state unencrypted.`pgp_key` is ignored if `public_key` is supplied. ## Attribute Reference This resource exports the following attributes in addition to the arguments above: -* `id` - The name used for this key pair -* `arn` - The ARN of the Lightsail key pair +* `id` - The name used for this key pair. +* `arn` - The ARN of the Lightsail key pair. +* `encrypted_fingerprint` - The MD5 public key fingerprint for the encrypted private key. +* `encrypted_private_key` – the private key material, base 64 encoded and encrypted with the given `pgp_key`. This is only populated when creating a new key and `pgp_key` is supplied. * `fingerprint` - The MD5 public key fingerprint as specified in section 4 of RFC 4716. -* `public_key` - the public key, base64 encoded -* `private_key` - the private key, base64 encoded. This is only populated -when creating a new key, and when no `pgp_key` is provided -* `encrypted_private_key` – the private key material, base 64 encoded and -encrypted with the given `pgp_key`. This is only populated when creating a new -key and `pgp_key` is supplied -* `encrypted_fingerprint` - The MD5 public key fingerprint for the encrypted -private key +* `public_key` - the public key, base64 encoded. +* `private_key` - the private key, base64 encoded. This is only populated when creating a new key, and when no `pgp_key` is provided. ## Import