diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index cfe8033e0..edb2a744f 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -121,6 +121,7 @@ func Provider() terraform.ResourceProvider { "aws_ami": resourceAwsAmi(), "aws_ami_copy": resourceAwsAmiCopy(), "aws_ami_from_instance": resourceAwsAmiFromInstance(), + "aws_ami_launch_permission": resourceAwsAmiLaunchPermission(), "aws_api_gateway_account": resourceAwsApiGatewayAccount(), "aws_api_gateway_api_key": resourceAwsApiGatewayApiKey(), "aws_api_gateway_authorizer": resourceAwsApiGatewayAuthorizer(), diff --git a/builtin/providers/aws/resource_aws_ami_launch_permission.go b/builtin/providers/aws/resource_aws_ami_launch_permission.go new file mode 100644 index 000000000..d1c738d39 --- /dev/null +++ b/builtin/providers/aws/resource_aws_ami_launch_permission.go @@ -0,0 +1,104 @@ +package aws + +import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsAmiLaunchPermission() *schema.Resource { + return &schema.Resource{ + Exists: resourceAwsAmiLaunchPermissionExists, + Create: resourceAwsAmiLaunchPermissionCreate, + Read: resourceAwsAmiLaunchPermissionRead, + Delete: resourceAwsAmiLaunchPermissionDelete, + + Schema: map[string]*schema.Schema{ + "image_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "account_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsAmiLaunchPermissionExists(d *schema.ResourceData, meta interface{}) (bool, error) { + conn := meta.(*AWSClient).ec2conn + + image_id := d.Get("image_id").(string) + account_id := d.Get("account_id").(string) + return hasLaunchPermission(conn, image_id, account_id) +} + +func resourceAwsAmiLaunchPermissionCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + image_id := d.Get("image_id").(string) + account_id := d.Get("account_id").(string) + + _, err := conn.ModifyImageAttribute(&ec2.ModifyImageAttributeInput{ + ImageId: aws.String(image_id), + Attribute: aws.String("launchPermission"), + LaunchPermission: &ec2.LaunchPermissionModifications{ + Add: []*ec2.LaunchPermission{ + &ec2.LaunchPermission{UserId: aws.String(account_id)}, + }, + }, + }) + if err != nil { + return fmt.Errorf("error creating ami launch permission: %s", err) + } + + d.SetId(fmt.Sprintf("%s-%s", image_id, account_id)) + return nil +} + +func resourceAwsAmiLaunchPermissionRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceAwsAmiLaunchPermissionDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + image_id := d.Get("image_id").(string) + account_id := d.Get("account_id").(string) + + _, err := conn.ModifyImageAttribute(&ec2.ModifyImageAttributeInput{ + ImageId: aws.String(image_id), + Attribute: aws.String("launchPermission"), + LaunchPermission: &ec2.LaunchPermissionModifications{ + Remove: []*ec2.LaunchPermission{ + &ec2.LaunchPermission{UserId: aws.String(account_id)}, + }, + }, + }) + if err != nil { + return fmt.Errorf("error removing ami launch permission: %s", err) + } + + return nil +} + +func hasLaunchPermission(conn *ec2.EC2, image_id string, account_id string) (bool, error) { + attrs, err := conn.DescribeImageAttribute(&ec2.DescribeImageAttributeInput{ + ImageId: aws.String(image_id), + Attribute: aws.String("launchPermission"), + }) + if err != nil { + return false, err + } + + for _, lp := range attrs.LaunchPermissions { + if *lp.UserId == account_id { + return true, nil + } + } + return false, nil +} diff --git a/builtin/providers/aws/resource_aws_ami_launch_permission_test.go b/builtin/providers/aws/resource_aws_ami_launch_permission_test.go new file mode 100644 index 000000000..0affa9161 --- /dev/null +++ b/builtin/providers/aws/resource_aws_ami_launch_permission_test.go @@ -0,0 +1,105 @@ +package aws + +import ( + "fmt" + r "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "os" + "testing" +) + +func TestAccAWSAMILaunchPermission_Basic(t *testing.T) { + image_id := "" + account_id := os.Getenv("AWS_ACCOUNT_ID") + + r.Test(t, r.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + if os.Getenv("AWS_ACCOUNT_ID") == "" { + t.Fatal("AWS_ACCOUNT_ID must be set") + } + }, + Providers: testAccProviders, + Steps: []r.TestStep{ + // Scaffold everything + r.TestStep{ + Config: testAccAWSAMILaunchPermissionConfig(account_id, true), + Check: r.ComposeTestCheckFunc( + testCheckResourceGetAttr("aws_ami_copy.test", "id", &image_id), + testAccAWSAMILaunchPermissionExists(account_id, &image_id), + ), + }, + // Drop just launch permission to test destruction + r.TestStep{ + Config: testAccAWSAMILaunchPermissionConfig(account_id, false), + Check: r.ComposeTestCheckFunc( + testAccAWSAMILaunchPermissionDestroyed(account_id, &image_id), + ), + }, + }, + }) +} + +func testCheckResourceGetAttr(name, key string, value *string) r.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) + } + + *value = is.Attributes[key] + return nil + } +} + +func testAccAWSAMILaunchPermissionExists(account_id string, image_id *string) r.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + if has, err := hasLaunchPermission(conn, *image_id, account_id); err != nil { + return err + } else if !has { + return fmt.Errorf("launch permission does not exist for '%s' on '%s'", account_id, *image_id) + } + return nil + } +} + +func testAccAWSAMILaunchPermissionDestroyed(account_id string, image_id *string) r.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + if has, err := hasLaunchPermission(conn, *image_id, account_id); err != nil { + return err + } else if has { + return fmt.Errorf("launch permission still exists for '%s' on '%s'", account_id, *image_id) + } + return nil + } +} + +func testAccAWSAMILaunchPermissionConfig(account_id string, includeLaunchPermission bool) string { + base := ` +resource "aws_ami_copy" "test" { + name = "launch-permission-test" + description = "Launch Permission Test Copy" + source_ami_id = "ami-7172b611" + source_ami_region = "us-west-2" +} +` + + if !includeLaunchPermission { + return base + } + + return base + fmt.Sprintf(` +resource "aws_ami_launch_permission" "self-test" { + image_id = "${aws_ami_copy.test.id}" + account_id = "%s" +} +`, account_id) +} diff --git a/website/source/docs/providers/aws/r/ami.html.markdown b/website/source/docs/providers/aws/r/ami.html.markdown index 25ac04db6..1a579407e 100644 --- a/website/source/docs/providers/aws/r/ami.html.markdown +++ b/website/source/docs/providers/aws/r/ami.html.markdown @@ -14,6 +14,9 @@ The AMI resource allows the creation and management of a completely-custom If you just want to duplicate an existing AMI, possibly copying it to another region, it's better to use `aws_ami_copy` instead. +If you just want to share an existing AMI with another AWS account, +it's better to use `aws_ami_launch_permission` instead. + ## Example Usage ``` diff --git a/website/source/docs/providers/aws/r/ami_launch_permission.html.markdown b/website/source/docs/providers/aws/r/ami_launch_permission.html.markdown new file mode 100644 index 000000000..7be0ffd59 --- /dev/null +++ b/website/source/docs/providers/aws/r/ami_launch_permission.html.markdown @@ -0,0 +1,33 @@ +--- +layout: "aws" +page_title: "AWS: aws_ami_launch_permission" +sidebar_current: "docs-aws-resource-ami-launch-permission" +description: |- + Adds launch permission to Amazon Machine Image (AMI). +--- + +# aws\_ami\_launch\_permission + +Adds launch permission to Amazon Machine Image (AMI) from another AWS account. + +## Example Usage + +``` +resource "aws_ami_launch_permission" "example" { + image_id = "ami-12345678" + account_id = "123456789012" +} +``` + +## Argument Reference + +The following arguments are supported: + + * `image_id` - (required) A region-unique name for the AMI. + * `account_id` - (required) An AWS Account ID to add launch permissions. + +## Attributes Reference + +The following attributes are exported: + + * `id` - A combination of "`image_id`-`account_id`".