diff --git a/.changelog/12103.txt b/.changelog/12103.txt new file mode 100644 index 000000000000..765b74e5a152 --- /dev/null +++ b/.changelog/12103.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_snapshot_create_volume_permission: Error if `account_id` is the snapshot's owner +``` \ No newline at end of file diff --git a/internal/service/ec2/ebs_snapshot_create_volume_permission.go b/internal/service/ec2/ebs_snapshot_create_volume_permission.go index 5cfe5b8ef89c..f5e205f7b7a2 100644 --- a/internal/service/ec2/ebs_snapshot_create_volume_permission.go +++ b/internal/service/ec2/ebs_snapshot_create_volume_permission.go @@ -1,6 +1,7 @@ package ec2 import ( + "context" "fmt" "log" "strings" @@ -8,9 +9,10 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceSnapshotCreateVolumePermission() *schema.Resource { @@ -19,13 +21,20 @@ func ResourceSnapshotCreateVolumePermission() *schema.Resource { Read: resourceSnapshotCreateVolumePermissionRead, Delete: resourceSnapshotCreateVolumePermissionDelete, + CustomizeDiff: resourceSnapshotCreateVolumePermissionCustomizeDiff, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(5 * time.Minute), + }, + Schema: map[string]*schema.Schema{ - "snapshot_id": { + "account_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "account_id": { + "snapshot_id": { Type: schema.TypeString, Required: true, ForceNew: true, @@ -37,37 +46,34 @@ func ResourceSnapshotCreateVolumePermission() *schema.Resource { func resourceSnapshotCreateVolumePermissionCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - snapshot_id := d.Get("snapshot_id").(string) - account_id := d.Get("account_id").(string) - - _, err := conn.ModifySnapshotAttribute(&ec2.ModifySnapshotAttributeInput{ - SnapshotId: aws.String(snapshot_id), - Attribute: aws.String("createVolumePermission"), + snapshotID := d.Get("snapshot_id").(string) + accountID := d.Get("account_id").(string) + id := EBSSnapshotCreateVolumePermissionCreateResourceID(snapshotID, accountID) + input := &ec2.ModifySnapshotAttributeInput{ + Attribute: aws.String(ec2.SnapshotAttributeNameCreateVolumePermission), CreateVolumePermission: &ec2.CreateVolumePermissionModifications{ Add: []*ec2.CreateVolumePermission{ - {UserId: aws.String(account_id)}, + {UserId: aws.String(accountID)}, }, }, - }) - if err != nil { - return fmt.Errorf("Error adding snapshot createVolumePermission: %s", err) + SnapshotId: aws.String(snapshotID), } - d.SetId(fmt.Sprintf("%s-%s", snapshot_id, account_id)) + log.Printf("[DEBUG] Creating EBS Snapshot CreateVolumePermission: %s", input) + _, err := conn.ModifySnapshotAttribute(input) - // Wait for the account to appear in the permission list - stateConf := &resource.StateChangeConf{ - Pending: []string{"denied"}, - Target: []string{"granted"}, - Refresh: resourceSnapshotCreateVolumePermissionStateRefreshFunc(conn, snapshot_id, account_id), - Timeout: 20 * time.Minute, - Delay: 10 * time.Second, - MinTimeout: 10 * time.Second, + if err != nil { + return fmt.Errorf("creating EBS Snapshot CreateVolumePermission (%s): %w", id, err) } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for snapshot createVolumePermission (%s) to be added: %s", - d.Id(), err) + + d.SetId(id) + + _, err = tfresource.RetryWhenNotFound(d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { + return FindCreateSnapshotCreateVolumePermissionByTwoPartKey(conn, snapshotID, accountID) + }) + + if err != nil { + return fmt.Errorf("waiting for EBS Snapshot CreateVolumePermission create (%s): %w", d.Id(), err) } return nil @@ -76,98 +82,101 @@ func resourceSnapshotCreateVolumePermissionCreate(d *schema.ResourceData, meta i func resourceSnapshotCreateVolumePermissionRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - snapshotID, accountID, err := SnapshotCreateVolumePermissionParseID(d.Id()) - if err != nil { - return err - } + snapshotID, accountID, err := EBSSnapshotCreateVolumePermissionParseResourceID(d.Id()) - exists, err := HasCreateVolumePermission(conn, snapshotID, accountID) if err != nil { return err } - if !exists { - log.Printf("[WARN] snapshot createVolumePermission (%s) not found, removing from state", d.Id()) + + _, err = FindCreateSnapshotCreateVolumePermissionByTwoPartKey(conn, snapshotID, accountID) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EBS Snapshot CreateVolumePermission %s not found, removing from state", d.Id()) d.SetId("") return nil } + if err != nil { + return fmt.Errorf("reading EBS Snapshot CreateVolumePermission (%s): %w", d.Id(), err) + } + return nil } func resourceSnapshotCreateVolumePermissionDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - snapshotID, accountID, err := SnapshotCreateVolumePermissionParseID(d.Id()) + snapshotID, accountID, err := EBSSnapshotCreateVolumePermissionParseResourceID(d.Id()) + if err != nil { return err } + log.Printf("[DEBUG] Deleting EBS Snapshot CreateVolumePermission: %s", d.Id()) _, err = conn.ModifySnapshotAttribute(&ec2.ModifySnapshotAttributeInput{ - SnapshotId: aws.String(snapshotID), - Attribute: aws.String("createVolumePermission"), + Attribute: aws.String(ec2.SnapshotAttributeNameCreateVolumePermission), CreateVolumePermission: &ec2.CreateVolumePermissionModifications{ Remove: []*ec2.CreateVolumePermission{ {UserId: aws.String(accountID)}, }, }, + SnapshotId: aws.String(snapshotID), }) - if err != nil { - return fmt.Errorf("Error removing snapshot createVolumePermission: %s", err) - } - // Wait for the account to disappear from the permission list - stateConf := &resource.StateChangeConf{ - Pending: []string{"granted"}, - Target: []string{"denied"}, - Refresh: resourceSnapshotCreateVolumePermissionStateRefreshFunc(conn, snapshotID, accountID), - Timeout: 5 * time.Minute, - Delay: 10 * time.Second, - MinTimeout: 10 * time.Second, + if tfawserr.ErrCodeEquals(err, errCodeInvalidSnapshotNotFound) { + return nil } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for snapshot createVolumePermission (%s) to be removed: %s", - d.Id(), err) + + if err != nil { + return fmt.Errorf("deleting EBS Snapshot CreateVolumePermission (%s): %w", d.Id(), err) } - return nil -} + _, err = tfresource.RetryUntilNotFound(d.Timeout(schema.TimeoutDelete), func() (interface{}, error) { + return FindCreateSnapshotCreateVolumePermissionByTwoPartKey(conn, snapshotID, accountID) + }) -func HasCreateVolumePermission(conn *ec2.EC2, snapshot_id string, account_id string) (bool, error) { - _, state, err := resourceSnapshotCreateVolumePermissionStateRefreshFunc(conn, snapshot_id, account_id)() if err != nil { - return false, err - } - if state == "granted" { - return true, nil - } else { - return false, nil + return fmt.Errorf("waiting for EBS Snapshot CreateVolumePermission delete (%s): %w", d.Id(), err) } + + return nil } -func resourceSnapshotCreateVolumePermissionStateRefreshFunc(conn *ec2.EC2, snapshot_id string, account_id string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - attrs, err := conn.DescribeSnapshotAttribute(&ec2.DescribeSnapshotAttributeInput{ - SnapshotId: aws.String(snapshot_id), - Attribute: aws.String("createVolumePermission"), - }) - if err != nil { - return nil, "", fmt.Errorf("Error refreshing snapshot createVolumePermission state: %s", err) - } +func resourceSnapshotCreateVolumePermissionCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error { + if diff.Id() == "" { + if snapshotID := diff.Get("snapshot_id").(string); snapshotID != "" { + conn := meta.(*conns.AWSClient).EC2Conn - for _, vp := range attrs.CreateVolumePermissions { - if aws.StringValue(vp.UserId) == account_id { - return attrs, "granted", nil + snapshot, err := FindSnapshotByID(conn, snapshotID) + + if err != nil { + return fmt.Errorf("reading EBS Snapshot (%s): %w", snapshotID, err) + } + + if accountID := diff.Get("account_id").(string); aws.StringValue(snapshot.OwnerId) == accountID { + return fmt.Errorf("AWS Account (%s) owns EBS Snapshot (%s)", accountID, snapshotID) } } - return attrs, "denied", nil } + + return nil } -func SnapshotCreateVolumePermissionParseID(id string) (string, string, error) { - idParts := strings.SplitN(id, "-", 3) - if len(idParts) != 3 || idParts[0] != "snap" || idParts[1] == "" || idParts[2] == "" { - return "", "", fmt.Errorf("unexpected format of ID (%s), expected SNAPSHOT_ID-ACCOUNT_ID", id) +const ebsSnapshotCreateVolumePermissionIDSeparator = "-" + +func EBSSnapshotCreateVolumePermissionCreateResourceID(snapshotID, accountID string) string { + parts := []string{snapshotID, accountID} + id := strings.Join(parts, ebsSnapshotCreateVolumePermissionIDSeparator) + + return id +} + +func EBSSnapshotCreateVolumePermissionParseResourceID(id string) (string, string, error) { + parts := strings.SplitN(id, ebsSnapshotCreateVolumePermissionIDSeparator, 3) + + if len(parts) != 3 || parts[0] != "snap" || parts[1] == "" || parts[2] == "" { + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected SNAPSHOT_ID%[2]sACCOUNT_ID", id, ebsSnapshotCreateVolumePermissionIDSeparator) } - return fmt.Sprintf("%s-%s", idParts[0], idParts[1]), idParts[2], nil + + return strings.Join([]string{parts[0], parts[1]}, ebsSnapshotCreateVolumePermissionIDSeparator), parts[2], nil } diff --git a/internal/service/ec2/ebs_snapshot_create_volume_permission_test.go b/internal/service/ec2/ebs_snapshot_create_volume_permission_test.go index 77b4e73aac3d..7b18cf307b57 100644 --- a/internal/service/ec2/ebs_snapshot_create_volume_permission_test.go +++ b/internal/service/ec2/ebs_snapshot_create_volume_permission_test.go @@ -2,40 +2,40 @@ package ec2_test import ( "fmt" + "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" "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-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccEC2EBSSnapshotCreateVolumePermission_basic(t *testing.T) { - var snapshotId string - accountId := "111122223333" + var providers []*schema.Provider + resourceName := "aws_snapshot_create_volume_permission.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckAlternateAccount(t) + }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProviderFactories: acctest.ProviderFactories, + ProviderFactories: acctest.FactoriesAlternate(&providers), CheckDestroy: testAccSnapshotCreateVolumePermissionDestroy, Steps: []resource.TestStep{ - // Scaffold everything - { - Config: testAccEBSSnapshotCreateVolumePermissionConfig_basic(true, accountId), - Check: resource.ComposeTestCheckFunc( - testCheckResourceGetAttr("aws_ebs_snapshot.test", "id", &snapshotId), - testAccSnapshotCreateVolumePermissionExists(&accountId, &snapshotId), - ), - }, - // Drop just create volume permission to test destruction { - Config: testAccEBSSnapshotCreateVolumePermissionConfig_basic(false, accountId), + Config: testAccEBSSnapshotCreateVolumePermissionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccSnapshotCreateVolumePermissionDestroyed(&accountId, &snapshotId), + testAccSnapshotCreateVolumePermissionExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "account_id"), + resource.TestCheckResourceAttrSet(resourceName, "snapshot_id"), ), }, }, @@ -43,21 +43,24 @@ func TestAccEC2EBSSnapshotCreateVolumePermission_basic(t *testing.T) { } func TestAccEC2EBSSnapshotCreateVolumePermission_disappears(t *testing.T) { - var snapshotId string - accountId := "111122223333" + var providers []*schema.Provider + resourceName := "aws_snapshot_create_volume_permission.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckAlternateAccount(t) + }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - ProviderFactories: acctest.ProviderFactories, + ProviderFactories: acctest.FactoriesAlternate(&providers), CheckDestroy: testAccSnapshotCreateVolumePermissionDestroy, Steps: []resource.TestStep{ { - Config: testAccEBSSnapshotCreateVolumePermissionConfig_basic(true, accountId), + Config: testAccEBSSnapshotCreateVolumePermissionConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testCheckResourceGetAttr("aws_ebs_snapshot.test", "id", &snapshotId), - testAccSnapshotCreateVolumePermissionExists(&accountId, &snapshotId), - acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceSnapshotCreateVolumePermission(), "aws_snapshot_create_volume_permission.test"), + testAccSnapshotCreateVolumePermissionExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceSnapshotCreateVolumePermission(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -65,22 +68,21 @@ func TestAccEC2EBSSnapshotCreateVolumePermission_disappears(t *testing.T) { }) } -func testCheckResourceGetAttr(name, key string, value *string) resource.TestCheckFunc { - return func(s *terraform.State) error { - ms := s.RootModule() - rs, ok := ms.Resources[name] - if !ok { - return fmt.Errorf("Not found: %s", name) - } - - is := rs.Primary - if is == nil { - return fmt.Errorf("No primary instance: %s", name) - } +func TestAccEC2EBSSnapshotCreateVolumePermission_snapshotOwnerExpectError(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - *value = is.Attributes[key] - return nil - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccSnapshotCreateVolumePermissionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccEBSSnapshotCreateVolumePermissionConfig_snapshotOwner(rName), + ExpectError: regexp.MustCompile(`owns EBS Snapshot`), + }, + }, + }) } func testAccSnapshotCreateVolumePermissionDestroy(s *terraform.State) error { @@ -91,77 +93,115 @@ func testAccSnapshotCreateVolumePermissionDestroy(s *terraform.State) error { continue } - snapshotID, accountID, err := tfec2.SnapshotCreateVolumePermissionParseID(rs.Primary.ID) + snapshotID, accountID, err := tfec2.EBSSnapshotCreateVolumePermissionParseResourceID(rs.Primary.ID) + if err != nil { return err } - if has, err := tfec2.HasCreateVolumePermission(conn, snapshotID, accountID); err != nil { + + _, err = tfec2.FindCreateSnapshotCreateVolumePermissionByTwoPartKey(conn, snapshotID, accountID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { return err - } else if has { - return fmt.Errorf("create volume permission still exist for '%s' on '%s'", accountID, snapshotID) } + + return fmt.Errorf("EBS Snapshot CreateVolumePermission %s still exists", rs.Primary.ID) } return nil } -func testAccSnapshotCreateVolumePermissionExists(accountId, snapshotId *string) resource.TestCheckFunc { +func testAccSnapshotCreateVolumePermissionExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - if has, err := tfec2.HasCreateVolumePermission(conn, aws.StringValue(snapshotId), aws.StringValue(accountId)); err != nil { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No EBS Snapshot CreateVolumePermission ID is set") + } + + snapshotID, accountID, err := tfec2.EBSSnapshotCreateVolumePermissionParseResourceID(rs.Primary.ID) + + if err != nil { return err - } else if !has { - return fmt.Errorf("create volume permission does not exist for '%s' on '%s'", aws.StringValue(snapshotId), aws.StringValue(accountId)) } - return nil - } -} -func testAccSnapshotCreateVolumePermissionDestroyed(accountId, snapshotId *string) resource.TestCheckFunc { - return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - if has, err := tfec2.HasCreateVolumePermission(conn, aws.StringValue(snapshotId), aws.StringValue(accountId)); err != nil { + + _, err = tfec2.FindCreateSnapshotCreateVolumePermissionByTwoPartKey(conn, snapshotID, accountID) + + if err != nil { return err - } else if has { - return fmt.Errorf("create volume permission still exists for '%s' on '%s'", aws.StringValue(snapshotId), aws.StringValue(accountId)) } + return nil } } -func testAccEBSSnapshotCreateVolumePermissionConfig_basic(includeCreateVolumePermission bool, accountID string) string { - base := ` -data "aws_availability_zones" "available" { - state = "available" +func testAccEBSSnapshotCreateVolumePermissionConfig_basic(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + acctest.ConfigAvailableAZsNoOptIn(), + fmt.Sprintf(` +resource "aws_ebs_volume" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + size = 1 + + tags = { + Name = %[1]q + } +} + +resource "aws_ebs_snapshot" "test" { + volume_id = aws_ebs_volume.test.id - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] + tags = { + Name = %[1]q } } +data "aws_caller_identity" "test" { + provider = "awsalternate" +} + +resource "aws_snapshot_create_volume_permission" "test" { + snapshot_id = aws_ebs_snapshot.test.id + account_id = data.aws_caller_identity.test.account_id +} +`, rName)) +} + +func testAccEBSSnapshotCreateVolumePermissionConfig_snapshotOwner(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), + fmt.Sprintf(` resource "aws_ebs_volume" "test" { availability_zone = data.aws_availability_zones.available.names[0] size = 1 tags = { - Name = "ebs_snap_perm" + Name = %[1]q } } resource "aws_ebs_snapshot" "test" { volume_id = aws_ebs_volume.test.id + + tags = { + Name = %[1]q + } } -` - if !includeCreateVolumePermission { - return base - } +data "aws_caller_identity" "test" {} - return base + fmt.Sprintf(` resource "aws_snapshot_create_volume_permission" "test" { snapshot_id = aws_ebs_snapshot.test.id - account_id = %q + account_id = data.aws_caller_identity.test.account_id } -`, accountID) +`, rName)) } diff --git a/internal/service/ec2/ebs_snapshot_data_source.go b/internal/service/ec2/ebs_snapshot_data_source.go index 1cf61870cd9e..4db0bf4e1019 100644 --- a/internal/service/ec2/ebs_snapshot_data_source.go +++ b/internal/service/ec2/ebs_snapshot_data_source.go @@ -2,7 +2,6 @@ package ec2 import ( "fmt" - "log" "sort" "github.com/aws/aws-sdk-go/aws" @@ -99,83 +98,77 @@ func DataSourceEBSSnapshot() *schema.Resource { func dataSourceEBSSnapshotRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - restorableUsers, restorableUsersOk := d.GetOk("restorable_by_user_ids") - filters, filtersOk := d.GetOk("filter") - snapshotIds, snapshotIdsOk := d.GetOk("snapshot_ids") - owners, ownersOk := d.GetOk("owners") + input := &ec2.DescribeSnapshotsInput{} - if !restorableUsersOk && !filtersOk && !snapshotIdsOk && !ownersOk { - return fmt.Errorf("One of snapshot_ids, filters, restorable_by_user_ids, or owners must be assigned") + if v, ok := d.GetOk("owners"); ok && len(v.([]interface{})) > 0 { + input.OwnerIds = flex.ExpandStringList(v.([]interface{})) } - params := &ec2.DescribeSnapshotsInput{} - if restorableUsersOk { - params.RestorableByUserIds = flex.ExpandStringList(restorableUsers.([]interface{})) - } - if filtersOk { - params.Filters = BuildFiltersDataSource(filters.(*schema.Set)) + if v, ok := d.GetOk("restorable_by_user_ids"); ok && len(v.([]interface{})) > 0 { + input.RestorableByUserIds = flex.ExpandStringList(v.([]interface{})) } - if ownersOk { - params.OwnerIds = flex.ExpandStringList(owners.([]interface{})) + + if v, ok := d.GetOk("snapshot_ids"); ok && len(v.([]interface{})) > 0 { + input.SnapshotIds = flex.ExpandStringList(v.([]interface{})) } - if snapshotIdsOk { - params.SnapshotIds = flex.ExpandStringList(snapshotIds.([]interface{})) + + input.Filters = append(input.Filters, BuildFiltersDataSource( + d.Get("filter").(*schema.Set), + )...) + + if len(input.Filters) == 0 { + input.Filters = nil } - log.Printf("[DEBUG] Reading EBS Snapshot: %s", params) - resp, err := conn.DescribeSnapshots(params) + snapshots, err := FindSnapshots(conn, input) + if err != nil { - return err + return fmt.Errorf("reading EBS Snapshots: %w", err) } - if len(resp.Snapshots) < 1 { + if len(snapshots) < 1 { return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") } - if len(resp.Snapshots) > 1 { + if len(snapshots) > 1 { if !d.Get("most_recent").(bool) { return fmt.Errorf("Your query returned more than one result. Please try a more " + "specific search criteria, or set `most_recent` attribute to true.") } - sort.Slice(resp.Snapshots, func(i, j int) bool { - return aws.TimeValue(resp.Snapshots[i].StartTime).Unix() > aws.TimeValue(resp.Snapshots[j].StartTime).Unix() + + sort.Slice(snapshots, func(i, j int) bool { + return aws.TimeValue(snapshots[i].StartTime).Unix() > aws.TimeValue(snapshots[j].StartTime).Unix() }) } - //Single Snapshot found so set to state - return snapshotDescriptionAttributes(d, resp.Snapshots[0], meta) -} - -func snapshotDescriptionAttributes(d *schema.ResourceData, snapshot *ec2.Snapshot, meta interface{}) error { - ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + snapshot := snapshots[0] d.SetId(aws.StringValue(snapshot.SnapshotId)) - d.Set("snapshot_id", snapshot.SnapshotId) - d.Set("volume_id", snapshot.VolumeId) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: ec2.ServiceName, + Region: meta.(*conns.AWSClient).Region, + Resource: fmt.Sprintf("snapshot/%s", d.Id()), + }.String() + d.Set("arn", arn) d.Set("data_encryption_key_id", snapshot.DataEncryptionKeyId) d.Set("description", snapshot.Description) d.Set("encrypted", snapshot.Encrypted) d.Set("kms_key_id", snapshot.KmsKeyId) - d.Set("volume_size", snapshot.VolumeSize) - d.Set("state", snapshot.State) - d.Set("owner_id", snapshot.OwnerId) + d.Set("outpost_arn", snapshot.OutpostArn) d.Set("owner_alias", snapshot.OwnerAlias) + d.Set("owner_id", snapshot.OwnerId) + d.Set("snapshot_id", snapshot.SnapshotId) + d.Set("state", snapshot.State) d.Set("storage_tier", snapshot.StorageTier) - d.Set("outpost_arn", snapshot.OutpostArn) + d.Set("volume_id", snapshot.VolumeId) + d.Set("volume_size", snapshot.VolumeSize) if err := d.Set("tags", KeyValueTags(snapshot.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) + return fmt.Errorf("setting tags: %w", err) } - snapshotArn := arn.ARN{ - Partition: meta.(*conns.AWSClient).Partition, - Region: meta.(*conns.AWSClient).Region, - Resource: fmt.Sprintf("snapshot/%s", d.Id()), - Service: ec2.ServiceName, - }.String() - - d.Set("arn", snapshotArn) - return nil } diff --git a/internal/service/ec2/ebs_snapshot_data_source_test.go b/internal/service/ec2/ebs_snapshot_data_source_test.go index e0ed64c617b1..a0fa6fb98a8b 100644 --- a/internal/service/ec2/ebs_snapshot_data_source_test.go +++ b/internal/service/ec2/ebs_snapshot_data_source_test.go @@ -5,14 +5,15 @@ import ( "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-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" ) func TestAccEC2EBSSnapshotDataSource_basic(t *testing.T) { dataSourceName := "data.aws_ebs_snapshot.test" resourceName := "aws_ebs_snapshot.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -20,20 +21,19 @@ func TestAccEC2EBSSnapshotDataSource_basic(t *testing.T) { ProviderFactories: acctest.ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccEBSSnapshotDataSourceConfig_basic, + Config: testAccEBSSnapshotDataSourceConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckEBSSnapshotIDDataSource(dataSourceName), - resource.TestCheckResourceAttrPair(dataSourceName, "id", resourceName, "id"), resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), resource.TestCheckResourceAttrPair(dataSourceName, "encrypted", resourceName, "encrypted"), + resource.TestCheckResourceAttrPair(dataSourceName, "id", resourceName, "id"), resource.TestCheckResourceAttrPair(dataSourceName, "kms_key_id", resourceName, "kms_key_id"), resource.TestCheckResourceAttrPair(dataSourceName, "owner_alias", resourceName, "owner_alias"), resource.TestCheckResourceAttrPair(dataSourceName, "owner_id", resourceName, "owner_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "storage_tier", resourceName, "storage_tier"), resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), resource.TestCheckResourceAttrPair(dataSourceName, "volume_id", resourceName, "volume_id"), resource.TestCheckResourceAttrPair(dataSourceName, "volume_size", resourceName, "volume_size"), - resource.TestCheckResourceAttrPair(dataSourceName, "storage_tier", resourceName, "storage_tier"), ), }, }, @@ -43,6 +43,7 @@ func TestAccEC2EBSSnapshotDataSource_basic(t *testing.T) { func TestAccEC2EBSSnapshotDataSource_filter(t *testing.T) { dataSourceName := "data.aws_ebs_snapshot.test" resourceName := "aws_ebs_snapshot.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -50,9 +51,8 @@ func TestAccEC2EBSSnapshotDataSource_filter(t *testing.T) { ProviderFactories: acctest.ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccEBSSnapshotDataSourceConfig_filter, + Config: testAccEBSSnapshotDataSourceConfig_filter(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckEBSSnapshotIDDataSource(dataSourceName), resource.TestCheckResourceAttrPair(dataSourceName, "id", resourceName, "id"), ), }, @@ -62,7 +62,8 @@ func TestAccEC2EBSSnapshotDataSource_filter(t *testing.T) { func TestAccEC2EBSSnapshotDataSource_mostRecent(t *testing.T) { dataSourceName := "data.aws_ebs_snapshot.test" - resourceName := "aws_ebs_snapshot.test" + resourceName := "aws_ebs_snapshot.b" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -70,9 +71,8 @@ func TestAccEC2EBSSnapshotDataSource_mostRecent(t *testing.T) { ProviderFactories: acctest.ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccEBSSnapshotDataSourceConfig_mostRecent, + Config: testAccEBSSnapshotDataSourceConfig_mostRecent(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckEBSSnapshotIDDataSource(dataSourceName), resource.TestCheckResourceAttrPair(dataSourceName, "id", resourceName, "id"), ), }, @@ -80,45 +80,50 @@ func TestAccEC2EBSSnapshotDataSource_mostRecent(t *testing.T) { }) } -func testAccCheckEBSSnapshotIDDataSource(n string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Can't find snapshot data source: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("Snapshot data source ID not set") - } - return nil - } -} - -var testAccEBSSnapshotDataSourceConfig_basic = acctest.ConfigAvailableAZsNoOptIn() + ` +func testAccEBSSnapshotDataSourceConfig_basic(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_ebs_volume" "test" { availability_zone = data.aws_availability_zones.available.names[0] type = "gp2" size = 1 + + tags = { + Name = %[1]q + } } resource "aws_ebs_snapshot" "test" { volume_id = aws_ebs_volume.test.id + + tags = { + Name = %[1]q + } } data "aws_ebs_snapshot" "test" { snapshot_ids = [aws_ebs_snapshot.test.id] } -` +`, rName)) +} -var testAccEBSSnapshotDataSourceConfig_filter = acctest.ConfigAvailableAZsNoOptIn() + ` +func testAccEBSSnapshotDataSourceConfig_filter(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_ebs_volume" "test" { availability_zone = data.aws_availability_zones.available.names[0] type = "gp2" size = 1 + + tags = { + Name = %[1]q + } } resource "aws_ebs_snapshot" "test" { volume_id = aws_ebs_volume.test.id + + tags = { + Name = %[1]q + } } data "aws_ebs_snapshot" "test" { @@ -127,28 +132,35 @@ data "aws_ebs_snapshot" "test" { values = [aws_ebs_snapshot.test.id] } } -` +`, rName)) +} -var testAccEBSSnapshotDataSourceConfig_mostRecent = acctest.ConfigAvailableAZsNoOptIn() + ` +func testAccEBSSnapshotDataSourceConfig_mostRecent(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_ebs_volume" "test" { availability_zone = data.aws_availability_zones.available.names[0] type = "gp2" size = 1 + + tags = { + Name = %[1]q + } } -resource "aws_ebs_snapshot" "incorrect" { +resource "aws_ebs_snapshot" "a" { volume_id = aws_ebs_volume.test.id tags = { - Name = "tf-acc-test-ec2-ebs-snapshot-data-source-most-recent" + Name = %[1]q } } -resource "aws_ebs_snapshot" "test" { - volume_id = aws_ebs_snapshot.incorrect.volume_id +resource "aws_ebs_snapshot" "b" { + # Ensure that this snapshot is created after the other. + volume_id = aws_ebs_snapshot.a.volume_id tags = { - Name = "tf-acc-test-ec2-ebs-snapshot-data-source-most-recent" + Name = %[1]q } } @@ -157,7 +169,10 @@ data "aws_ebs_snapshot" "test" { filter { name = "tag:Name" - values = [aws_ebs_snapshot.test.tags.Name] + values = [%[1]q] } + + depends_on = [aws_ebs_snapshot.a, aws_ebs_snapshot.b] +} +`, rName)) } -` diff --git a/internal/service/ec2/ebs_snapshot_ids_data_source.go b/internal/service/ec2/ebs_snapshot_ids_data_source.go index 7545d164d167..4c2d3de28739 100644 --- a/internal/service/ec2/ebs_snapshot_ids_data_source.go +++ b/internal/service/ec2/ebs_snapshot_ids_data_source.go @@ -2,7 +2,6 @@ package ec2 import ( "fmt" - "log" "sort" "github.com/aws/aws-sdk-go/aws" @@ -18,19 +17,19 @@ func DataSourceEBSSnapshotIDs() *schema.Resource { Schema: map[string]*schema.Schema{ "filter": DataSourceFiltersSchema(), - "owners": { + "ids": { Type: schema.TypeList, - Optional: true, + Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "restorable_by_user_ids": { + "owners": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "ids": { + "restorable_by_user_ids": { Type: schema.TypeList, - Computed: true, + Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, }, @@ -40,44 +39,42 @@ func DataSourceEBSSnapshotIDs() *schema.Resource { func dataSourceEBSSnapshotIDsRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - restorableUsers, restorableUsersOk := d.GetOk("restorable_by_user_ids") - filters, filtersOk := d.GetOk("filter") - owners, ownersOk := d.GetOk("owners") + input := &ec2.DescribeSnapshotsInput{} - if restorableUsers == false && !filtersOk && !ownersOk { - return fmt.Errorf("One of filters, restorable_by_user_ids, or owners must be assigned") + if v, ok := d.GetOk("owners"); ok && len(v.([]interface{})) > 0 { + input.OwnerIds = flex.ExpandStringList(v.([]interface{})) } - params := &ec2.DescribeSnapshotsInput{} - - if restorableUsersOk { - params.RestorableByUserIds = flex.ExpandStringList(restorableUsers.([]interface{})) - } - if filtersOk { - params.Filters = BuildFiltersDataSource(filters.(*schema.Set)) + if v, ok := d.GetOk("restorable_by_user_ids"); ok && len(v.([]interface{})) > 0 { + input.RestorableByUserIds = flex.ExpandStringList(v.([]interface{})) } - if ownersOk { - params.OwnerIds = flex.ExpandStringList(owners.([]interface{})) + + input.Filters = append(input.Filters, BuildFiltersDataSource( + d.Get("filter").(*schema.Set), + )...) + + if len(input.Filters) == 0 { + input.Filters = nil } - log.Printf("[DEBUG] Reading EBS Snapshot IDs: %s", params) - resp, err := conn.DescribeSnapshots(params) + snapshots, err := FindSnapshots(conn, input) + if err != nil { - return err + return fmt.Errorf("reading EBS Snapshots: %w", err) } - snapshotIds := make([]string, 0) - - sort.Slice(resp.Snapshots, func(i, j int) bool { - return aws.TimeValue(resp.Snapshots[i].StartTime).Unix() > aws.TimeValue(resp.Snapshots[j].StartTime).Unix() + sort.Slice(snapshots, func(i, j int) bool { + return aws.TimeValue(snapshots[i].StartTime).Unix() > aws.TimeValue(snapshots[j].StartTime).Unix() }) - for _, snapshot := range resp.Snapshots { - snapshotIds = append(snapshotIds, *snapshot.SnapshotId) + + var snapshotIDs []string + + for _, v := range snapshots { + snapshotIDs = append(snapshotIDs, aws.StringValue(v.SnapshotId)) } d.SetId(meta.(*conns.AWSClient).Region) - - d.Set("ids", snapshotIds) + d.Set("ids", snapshotIDs) return nil } diff --git a/internal/service/ec2/ebs_snapshot_ids_data_source_test.go b/internal/service/ec2/ebs_snapshot_ids_data_source_test.go index 6e71447e8880..3bc4ac26149a 100644 --- a/internal/service/ec2/ebs_snapshot_ids_data_source_test.go +++ b/internal/service/ec2/ebs_snapshot_ids_data_source_test.go @@ -11,15 +11,19 @@ import ( ) func TestAccEC2EBSSnapshotIDsDataSource_basic(t *testing.T) { + dataSourceName := "data.aws_ebs_snapshot_ids.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), ProviderFactories: acctest.ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccEBSSnapshotIdsDataSourceConfig_basic(), + Config: testAccEBSSnapshotIdsDataSourceConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckEBSSnapshotIDDataSource("data.aws_ebs_snapshot_ids.test"), + acctest.CheckResourceAttrGreaterThanValue(dataSourceName, "ids.#", "0"), + resource.TestCheckTypeSetElemAttrPair(dataSourceName, "ids.*", "aws_ebs_snapshot.test", "id"), ), }, }, @@ -27,6 +31,9 @@ func TestAccEC2EBSSnapshotIDsDataSource_basic(t *testing.T) { } func TestAccEC2EBSSnapshotIDsDataSource_sorted(t *testing.T) { + dataSourceName := "data.aws_ebs_snapshot_ids.test" + resource1Name := "aws_ebs_snapshot.a" + resource2Name := "aws_ebs_snapshot.b" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ @@ -35,23 +42,11 @@ func TestAccEC2EBSSnapshotIDsDataSource_sorted(t *testing.T) { ProviderFactories: acctest.ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccEBSSnapshotIdsDataSourceConfig_sorted1(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("aws_ebs_snapshot.a", "id"), - resource.TestCheckResourceAttrSet("aws_ebs_snapshot.b", "id"), - ), - }, - { - Config: testAccEBSSnapshotIdsDataSourceConfig_sorted2(rName), + Config: testAccEBSSnapshotIdsDataSourceConfig_sorted(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckEBSSnapshotIDDataSource("data.aws_ebs_snapshot_ids.test"), - resource.TestCheckResourceAttr("data.aws_ebs_snapshot_ids.test", "ids.#", "2"), - resource.TestCheckResourceAttrPair( - "data.aws_ebs_snapshot_ids.test", "ids.0", - "aws_ebs_snapshot.b", "id"), - resource.TestCheckResourceAttrPair( - "data.aws_ebs_snapshot_ids.test", "ids.1", - "aws_ebs_snapshot.a", "id"), + resource.TestCheckResourceAttr(dataSourceName, "ids.#", "2"), + resource.TestCheckResourceAttrPair(dataSourceName, "ids.0", resource2Name, "id"), + resource.TestCheckResourceAttrPair(dataSourceName, "ids.1", resource1Name, "id"), ), }, }, @@ -67,7 +62,6 @@ func TestAccEC2EBSSnapshotIDsDataSource_empty(t *testing.T) { { Config: testAccEBSSnapshotIdsDataSourceConfig_empty, Check: resource.ComposeTestCheckFunc( - testAccCheckEBSSnapshotIDDataSource("data.aws_ebs_snapshot_ids.empty"), resource.TestCheckResourceAttr("data.aws_ebs_snapshot_ids.empty", "ids.#", "0"), ), }, @@ -75,35 +69,53 @@ func TestAccEC2EBSSnapshotIDsDataSource_empty(t *testing.T) { }) } -func testAccEBSSnapshotIdsDataSourceConfig_basic() string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), ` +func testAccEBSSnapshotIdsDataSourceConfig_basic(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_ebs_volume" "test" { availability_zone = data.aws_availability_zones.available.names[0] size = 1 + + tags = { + Name = %[1]q + } } resource "aws_ebs_snapshot" "test" { volume_id = aws_ebs_volume.test.id + + tags = { + Name = %[1]q + } } data "aws_ebs_snapshot_ids" "test" { owners = ["self"] + + depends_on = [aws_ebs_snapshot.test] } -`) +`, rName)) } -func testAccEBSSnapshotIdsDataSourceConfig_sorted1(rName string) string { +func testAccEBSSnapshotIdsDataSourceConfig_sorted(rName string) string { return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_ebs_volume" "test" { availability_zone = data.aws_availability_zones.available.names[0] size = 1 count = 2 + + tags = { + Name = %[1]q + } } resource "aws_ebs_snapshot" "a" { volume_id = aws_ebs_volume.test.*.id[0] description = %[1]q + + tags = { + Name = %[1]q + } } resource "aws_ebs_snapshot" "b" { @@ -114,19 +126,21 @@ resource "aws_ebs_snapshot" "b" { # 'aws_ebs_snapshot.b.creation_date'/ so that we can ensure that the # snapshots are being sorted correctly. depends_on = [aws_ebs_snapshot.a] -} -`, rName)) + + tags = { + Name = %[1]q + } } -func testAccEBSSnapshotIdsDataSourceConfig_sorted2(rName string) string { - return acctest.ConfigCompose(testAccEBSSnapshotIdsDataSourceConfig_sorted1(rName), fmt.Sprintf(` data "aws_ebs_snapshot_ids" "test" { owners = ["self"] filter { name = "description" - values = [%q] + values = [%[1]q] } + + depends_on = [aws_ebs_snapshot.a, aws_ebs_snapshot.b] } `, rName)) } diff --git a/internal/service/ec2/ec2_ami_ids_data_source_test.go b/internal/service/ec2/ec2_ami_ids_data_source_test.go index f7d1c387af5c..5d33d8827f25 100644 --- a/internal/service/ec2/ec2_ami_ids_data_source_test.go +++ b/internal/service/ec2/ec2_ami_ids_data_source_test.go @@ -34,7 +34,6 @@ func TestAccEC2AMIIDsDataSource_sorted(t *testing.T) { { Config: testAccAMIIdsDataSourceConfig_sorted(false), Check: resource.ComposeTestCheckFunc( - testAccCheckEBSSnapshotIDDataSource("data.aws_ami_ids.test"), resource.TestCheckResourceAttr("data.aws_ami_ids.test", "ids.#", "2"), resource.TestCheckResourceAttrPair( "data.aws_ami_ids.test", "ids.0", diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index f3e19d22ce36..c8b472e7ec0f 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -4640,6 +4640,48 @@ func FindSnapshotByID(conn *ec2.EC2, id string) (*ec2.Snapshot, error) { return output, nil } +func FindSnapshotAttribute(conn *ec2.EC2, input *ec2.DescribeSnapshotAttributeInput) (*ec2.DescribeSnapshotAttributeOutput, error) { + output, err := conn.DescribeSnapshotAttribute(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidSnapshotNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func FindCreateSnapshotCreateVolumePermissionByTwoPartKey(conn *ec2.EC2, snapshotID, accountID string) (*ec2.CreateVolumePermission, error) { + input := &ec2.DescribeSnapshotAttributeInput{ + Attribute: aws.String(ec2.SnapshotAttributeNameCreateVolumePermission), + SnapshotId: aws.String(snapshotID), + } + + output, err := FindSnapshotAttribute(conn, input) + + if err != nil { + return nil, err + } + + for _, v := range output.CreateVolumePermissions { + if aws.StringValue(v.UserId) == accountID { + return v, nil + } + } + + return nil, &resource.NotFoundError{LastRequest: input} +} + func FindFindSnapshotTierStatuses(conn *ec2.EC2, input *ec2.DescribeSnapshotTierStatusInput) ([]*ec2.SnapshotTierStatus, error) { var output []*ec2.SnapshotTierStatus diff --git a/website/docs/r/snapshot_create_volume_permission.html.markdown b/website/docs/r/snapshot_create_volume_permission.html.markdown index e3b27031e616..7e27d842084f 100644 --- a/website/docs/r/snapshot_create_volume_permission.html.markdown +++ b/website/docs/r/snapshot_create_volume_permission.html.markdown @@ -33,7 +33,7 @@ resource "aws_ebs_snapshot" "example_snapshot" { The following arguments are supported: * `snapshot_id` - (required) A snapshot ID -* `account_id` - (required) An AWS Account ID to add create volume permissions +* `account_id` - (required) An AWS Account ID to add create volume permissions. The AWS Account cannot be the snapshot's owner ## Attributes Reference