Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] aws_snapshot_create_volume_permission #9891

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ func Provider() terraform.ResourceProvider {
"aws_spot_fleet_request": resourceAwsSpotFleetRequest(),
"aws_sqs_queue": resourceAwsSqsQueue(),
"aws_sqs_queue_policy": resourceAwsSqsQueuePolicy(),
"aws_snapshot_create_volume_permission": resourceAwsSnapshotCreateVolumePermission(),
"aws_sns_topic": resourceAwsSnsTopic(),
"aws_sns_topic_policy": resourceAwsSnsTopicPolicy(),
"aws_sns_topic_subscription": resourceAwsSnsTopicSubscription(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package aws

import (
"fmt"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsSnapshotCreateVolumePermission() *schema.Resource {
return &schema.Resource{
Exists: resourceAwsSnapshotCreateVolumePermissionExists,
Create: resourceAwsSnapshotCreateVolumePermissionCreate,
Read: resourceAwsSnapshotCreateVolumePermissionRead,
Delete: resourceAwsSnapshotCreateVolumePermissionDelete,

Schema: map[string]*schema.Schema{
"snapshot_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"account_id": &schema.Schema{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should have this as account_ids and support a set of IDs, instead of a single 1:1 relationship?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about that a bit. I agree that it would be nice to supply multiple IDs at once, but I opted to keep this similar to the aws_ami_launch_permission resource. I don't mind changing it if you prefer.

Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceAwsSnapshotCreateVolumePermissionExists(d *schema.ResourceData, meta interface{}) (bool, error) {
conn := meta.(*AWSClient).ec2conn

snapshot_id := d.Get("snapshot_id").(string)
account_id := d.Get("account_id").(string)
return hasCreateVolumePermission(conn, snapshot_id, account_id)
}

func resourceAwsSnapshotCreateVolumePermissionCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*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"),
CreateVolumePermission: &ec2.CreateVolumePermissionModifications{
Add: []*ec2.CreateVolumePermission{
&ec2.CreateVolumePermission{UserId: aws.String(account_id)},
},
},
})
if err != nil {
return fmt.Errorf("Error adding snapshot createVolumePermission: %s", err)
}

d.SetId(fmt.Sprintf("%s-%s", snapshot_id, account_id))

// Wait for the account to appear in the permission list
stateConf := &resource.StateChangeConf{
Pending: []string{"denied"},
Target: []string{"granted"},
Refresh: resourceAwsSnapshotCreateVolumePermissionStateRefreshFunc(conn, snapshot_id, account_id),
Timeout: 5 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 10 * time.Second,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for snapshot createVolumePermission (%s) to be added: %s",
d.Id(), err)
}

return nil
}

func resourceAwsSnapshotCreateVolumePermissionRead(d *schema.ResourceData, meta interface{}) error {
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably call conn.DescribeSnapshotAttribute here and verify that the account ID we expect is still in the list of permissions. If not, we should remove this resource from state with d.SetId(""), that way if someone modifies the snapshot permissions outside of Terraform, we can detect that drift and offer to repair it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, just noticed you implemented the Exists method, ignore this comment!

}

func resourceAwsSnapshotCreateVolumePermissionDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*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"),
CreateVolumePermission: &ec2.CreateVolumePermissionModifications{
Remove: []*ec2.CreateVolumePermission{
&ec2.CreateVolumePermission{UserId: aws.String(account_id)},
},
},
})
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: resourceAwsSnapshotCreateVolumePermissionStateRefreshFunc(conn, snapshot_id, account_id),
Timeout: 5 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 10 * time.Second,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for snapshot createVolumePermission (%s) to be removed: %s",
d.Id(), err)
}

return nil
}

func hasCreateVolumePermission(conn *ec2.EC2, snapshot_id string, account_id string) (bool, error) {
_, state, err := resourceAwsSnapshotCreateVolumePermissionStateRefreshFunc(conn, snapshot_id, account_id)()
if err != nil {
return false, err
}
if state == "granted" {
return true, nil
} else {
return false, nil
}
}

func resourceAwsSnapshotCreateVolumePermissionStateRefreshFunc(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)
}

for _, vp := range attrs.CreateVolumePermissions {
if *vp.UserId == account_id {
return attrs, "granted", nil
}
}
return attrs, "denied", nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package aws

import (
"fmt"
"os"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSSnapshotCreateVolumePermission_Basic(t *testing.T) {
snapshot_id := ""
account_id := os.Getenv("AWS_ACCOUNT_ID")

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
if os.Getenv("AWS_ACCOUNT_ID") == "" {
t.Fatal("AWS_ACCOUNT_ID must be set")
}
},
Providers: testAccProviders,
Steps: []resource.TestStep{
// Scaffold everything
resource.TestStep{
Config: testAccAWSSnapshotCreateVolumePermissionConfig(account_id, true),
Check: resource.ComposeTestCheckFunc(
testCheckResourceGetAttr("aws_ami_copy.test", "block_device_mappings.0.ebs.snapshot_id", &snapshot_id),
testAccAWSSnapshotCreateVolumePermissionExists(account_id, &snapshot_id),
),
},
// Drop just create volume permission to test destruction
resource.TestStep{
Config: testAccAWSSnapshotCreateVolumePermissionConfig(account_id, false),
Check: resource.ComposeTestCheckFunc(
testAccAWSSnapshotCreateVolumePermissionDestroyed(account_id, &snapshot_id),
),
},
},
})
}

func testAccAWSSnapshotCreateVolumePermissionExists(account_id string, snapshot_id *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
if has, err := hasCreateVolumePermission(conn, *snapshot_id, account_id); err != nil {
return err
} else if !has {
return fmt.Errorf("create volume permission does not exist for '%s' on '%s'", account_id, *snapshot_id)
}
return nil
}
}

func testAccAWSSnapshotCreateVolumePermissionDestroyed(account_id string, snapshot_id *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
if has, err := hasCreateVolumePermission(conn, *snapshot_id, account_id); err != nil {
return err
} else if has {
return fmt.Errorf("create volume permission still exists for '%s' on '%s'", account_id, *snapshot_id)
}
return nil
}
}

func testAccAWSSnapshotCreateVolumePermissionConfig(account_id string, includeCreateVolumePermission bool) string {
base := `
resource "aws_ami_copy" "test" {
name = "create-volume-permission-test"
description = "Create Volume Permission Test Copy"
source_ami_id = "ami-7172b611"
source_ami_region = "us-west-2"
}
`

if !includeCreateVolumePermission {
return base
}

return base + fmt.Sprintf(`
resource "aws_snapshot_create_volume_permission" "self-test" {
snapshot_id = "${lookup(lookup(element(aws_ami_copy.test.block_device_mappings, 0), "ebs"), "snapshot_id")}"
account_id = "%s"
}
`, account_id)
}