Skip to content

Commit

Permalink
Adds support for AMI sharing to Orgs and OUs
Browse files Browse the repository at this point in the history
  • Loading branch information
joraff committed Nov 9, 2021
1 parent e4288f2 commit 0b275c1
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 22 deletions.
115 changes: 95 additions & 20 deletions internal/service/ec2/ami_launch_permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"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"
)

Expand All @@ -21,13 +22,19 @@ func ResourceAMILaunchPermission() *schema.Resource {
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
idParts := strings.Split(d.Id(), "/")
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
return nil, fmt.Errorf("Unexpected format of ID (%q), expected ACCOUNT-ID/IMAGE-ID", d.Id())
return nil, fmt.Errorf("Unexpected format of ID (%q), expected ACCOUNT-ID/IMAGE-ID, or 'ARN'/IMAGE-ID", d.Id())
}
accountId := idParts[0]
imageId := idParts[1]
d.Set("account_id", accountId)
d.Set("image_id", imageId)
d.SetId(fmt.Sprintf("%s-%s", imageId, accountId))
if strings.HasPrefix(idParts[0], "arn") {
arn := idParts[0]
d.Set("arn", arn)
d.SetId(fmt.Sprintf("%s-%s", imageId, arn))
} else {
accountId := idParts[0]
d.Set("account_id", accountId)
d.SetId(fmt.Sprintf("%s-%s", imageId, accountId))
}
return []*schema.ResourceData{d}, nil
},
},
Expand All @@ -39,9 +46,26 @@ func ResourceAMILaunchPermission() *schema.Resource {
ForceNew: true,
},
"account_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ExactlyOneOf: []string{"account_id", "arn"},
},
"arn": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ExactlyOneOf: []string{"account_id", "arn"},
},
"arn_type": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
RequiredWith: []string{"arn"},
ValidateFunc: validation.StringInSlice([]string{
"OrganizationArn",
"OrganizationalUnitArn",
}, false),
},
},
}
Expand All @@ -51,29 +75,43 @@ func resourceAMILaunchPermissionCreate(d *schema.ResourceData, meta interface{})
conn := meta.(*conns.AWSClient).EC2Conn

image_id := d.Get("image_id").(string)
account_id := d.Get("account_id").(string)

launch_permission := BuildLaunchPermission(d)

_, err := conn.ModifyImageAttribute(&ec2.ModifyImageAttributeInput{
ImageId: aws.String(image_id),
Attribute: aws.String(ec2.ImageAttributeNameLaunchPermission),
LaunchPermission: &ec2.LaunchPermissionModifications{
Add: []*ec2.LaunchPermission{
{UserId: aws.String(account_id)},
},
Add: launch_permission,
},
})
if err != nil {
return fmt.Errorf("error creating AMI launch permission: %w", err)
}

account_id := d.Get("account_id").(string)
d.SetId(fmt.Sprintf("%s-%s", image_id, account_id))
return nil
}

func resourceAMILaunchPermissionRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).EC2Conn

exists, err := HasLaunchPermission(conn, d.Get("image_id").(string), d.Get("account_id").(string))
account_id := d.Get("account_id").(string)
arn := d.Get("arn").(string)
arn_type := d.Get("arn_type").(string)

var read_value, read_type string

if len(account_id) > 0 {
read_value = account_id
read_type = "UserId"
} else {
read_value = arn
read_type = arn_type
}

exists, err := HasLaunchPermission(conn, d.Get("image_id").(string), read_value, read_type)
if err != nil {
return fmt.Errorf("error reading AMI launch permission (%s): %w", d.Id(), err)
}
Expand All @@ -94,15 +132,14 @@ func resourceAMILaunchPermissionDelete(d *schema.ResourceData, meta interface{})
conn := meta.(*conns.AWSClient).EC2Conn

image_id := d.Get("image_id").(string)
account_id := d.Get("account_id").(string)

launch_permission := BuildLaunchPermission(d)

_, err := conn.ModifyImageAttribute(&ec2.ModifyImageAttributeInput{
ImageId: aws.String(image_id),
Attribute: aws.String(ec2.ImageAttributeNameLaunchPermission),
LaunchPermission: &ec2.LaunchPermissionModifications{
Remove: []*ec2.LaunchPermission{
{UserId: aws.String(account_id)},
},
Remove: launch_permission,
},
})
if err != nil {
Expand All @@ -112,7 +149,7 @@ func resourceAMILaunchPermissionDelete(d *schema.ResourceData, meta interface{})
return nil
}

func HasLaunchPermission(conn *ec2.EC2, image_id string, account_id string) (bool, error) {
func HasLaunchPermission(conn *ec2.EC2, image_id string, read_value string, read_type string) (bool, error) {
attrs, err := conn.DescribeImageAttribute(&ec2.DescribeImageAttributeInput{
ImageId: aws.String(image_id),
Attribute: aws.String(ec2.ImageAttributeNameLaunchPermission),
Expand All @@ -121,16 +158,54 @@ func HasLaunchPermission(conn *ec2.EC2, image_id string, account_id string) (boo
// When an AMI disappears out from under a launch permission resource, we will
// see either InvalidAMIID.NotFound or InvalidAMIID.Unavailable.
if ec2err, ok := err.(awserr.Error); ok && strings.HasPrefix(ec2err.Code(), "InvalidAMIID") {
log.Printf("[DEBUG] %s no longer exists, so we'll drop launch permission for %s from the state", image_id, account_id)
log.Printf("[DEBUG] %s no longer exists, so we'll drop launch permission for %s from the state", image_id, read_value)
return false, nil
}
return false, err
}

for _, lp := range attrs.LaunchPermissions {
if aws.StringValue(lp.UserId) == account_id {
return true, nil
switch read_type {
case "UserId":
if aws.StringValue(lp.UserId) == read_value {
return true, nil
}
case "OrganizationArn":
if aws.StringValue(lp.OrganizationArn) == read_value {
return true, nil
}
case "OrganizationalUnitArn":
if aws.StringValue(lp.OrganizationalUnitArn) == read_value {
return true, nil
}
}
}
return false, nil
}

func BuildLaunchPermission(d *schema.ResourceData) []*ec2.LaunchPermission {
account_id := d.Get("account_id").(string)
arn := d.Get("arn").(string)
arn_type := d.Get("arn_type").(string)

var launch_permission []*ec2.LaunchPermission

if len(account_id) > 0 {
log.Printf("[DEBUG] Building LaunchPermission of type UserId: %s", account_id)
launch_permission = []*ec2.LaunchPermission{
{UserId: aws.String(account_id)},
}
} else if arn_type == "OrganizationArn" {
log.Printf("[DEBUG] Building LaunchPermission of type OrganizationArn: %s", arn)
launch_permission = []*ec2.LaunchPermission{
{OrganizationArn: aws.String(arn)},
}
} else if arn_type == "OrganizationalUnitArn" {
log.Printf("[DEBUG] Building LaunchPermission of type OrganizationalUnitArn: %s", arn)
launch_permission = []*ec2.LaunchPermission{
{OrganizationalUnitArn: aws.String(arn)},
}
}

return launch_permission
}
4 changes: 2 additions & 2 deletions internal/service/ec2/ami_launch_permission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func testAccCheckAMILaunchPermissionExists(resourceName string) resource.TestChe
accountID := rs.Primary.Attributes["account_id"]
imageID := rs.Primary.Attributes["image_id"]

if has, err := tfec2.HasLaunchPermission(conn, imageID, accountID); err != nil {
if has, err := tfec2.HasLaunchPermission(conn, imageID, accountID, "UserId"); err != nil {
return err
} else if !has {
return fmt.Errorf("launch permission does not exist for '%s' on '%s'", accountID, imageID)
Expand All @@ -170,7 +170,7 @@ func testAccCheckAMILaunchPermissionDestroy(s *terraform.State) error {
accountID := rs.Primary.Attributes["account_id"]
imageID := rs.Primary.Attributes["image_id"]

if has, err := tfec2.HasLaunchPermission(conn, imageID, accountID); err != nil {
if has, err := tfec2.HasLaunchPermission(conn, imageID, accountID, "UserId"); err != nil {
return err
} else if has {
return fmt.Errorf("launch permission still exists for '%s' on '%s'", accountID, imageID)
Expand Down

0 comments on commit 0b275c1

Please sign in to comment.