cloudapi: Share an ec2 snapshot/ami with an account
This commit is contained in:
parent
6388aaff4c
commit
22c9f6af61
10 changed files with 118 additions and 28 deletions
|
|
@ -16,6 +16,7 @@ func main() {
|
||||||
var keyName string
|
var keyName string
|
||||||
var filename string
|
var filename string
|
||||||
var imageName string
|
var imageName string
|
||||||
|
var shareWith string
|
||||||
flag.StringVar(&accessKeyID, "access-key-id", "", "access key ID")
|
flag.StringVar(&accessKeyID, "access-key-id", "", "access key ID")
|
||||||
flag.StringVar(&secretAccessKey, "secret-access-key", "", "secret access key")
|
flag.StringVar(&secretAccessKey, "secret-access-key", "", "secret access key")
|
||||||
flag.StringVar(®ion, "region", "", "target region")
|
flag.StringVar(®ion, "region", "", "target region")
|
||||||
|
|
@ -23,6 +24,7 @@ func main() {
|
||||||
flag.StringVar(&keyName, "key", "", "target S3 key name")
|
flag.StringVar(&keyName, "key", "", "target S3 key name")
|
||||||
flag.StringVar(&filename, "image", "", "image file to upload")
|
flag.StringVar(&filename, "image", "", "image file to upload")
|
||||||
flag.StringVar(&imageName, "name", "", "AMI name")
|
flag.StringVar(&imageName, "name", "", "AMI name")
|
||||||
|
flag.StringVar(&shareWith, "account-id", "", "account id to share image with")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
a, err := awsupload.New(region, accessKeyID, secretAccessKey)
|
a, err := awsupload.New(region, accessKeyID, secretAccessKey)
|
||||||
|
|
@ -39,7 +41,11 @@ func main() {
|
||||||
|
|
||||||
fmt.Printf("file uploaded to %s\n", aws.StringValue(&uploadOutput.Location))
|
fmt.Printf("file uploaded to %s\n", aws.StringValue(&uploadOutput.Location))
|
||||||
|
|
||||||
ami, err := a.Register(imageName, bucketName, keyName)
|
var share []string
|
||||||
|
if shareWith != "" {
|
||||||
|
share = append(share, shareWith)
|
||||||
|
}
|
||||||
|
ami, err := a.Register(imageName, bucketName, keyName, share)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println(err.Error())
|
println(err.Error())
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: communicate back the AMI */
|
/* TODO: communicate back the AMI */
|
||||||
_, err = a.Register(t.ImageName, options.Bucket, key)
|
_, err = a.Register(t.ImageName, options.Bucket, key, options.ShareWithAccounts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r = append(r, err)
|
r = append(r, err)
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ func UploadImageToAWS(c *awsCredentials, imagePath string, imageName string) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot upload the image: %#v", err)
|
return fmt.Errorf("cannot upload the image: %#v", err)
|
||||||
}
|
}
|
||||||
_, err = uploader.Register(imageName, c.Bucket, imageName)
|
_, err = uploader.Register(imageName, c.Bucket, imageName, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot register the image: %#v", err)
|
return fmt.Errorf("cannot register the image: %#v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,10 @@ type AWSUploadRequestOptions struct {
|
||||||
|
|
||||||
// AWSUploadRequestOptionsEc2 defines model for AWSUploadRequestOptionsEc2.
|
// AWSUploadRequestOptionsEc2 defines model for AWSUploadRequestOptionsEc2.
|
||||||
type AWSUploadRequestOptionsEc2 struct {
|
type AWSUploadRequestOptionsEc2 struct {
|
||||||
AccessKeyId string `json:"access_key_id"`
|
AccessKeyId string `json:"access_key_id"`
|
||||||
SecretAccessKey string `json:"secret_access_key"`
|
SecretAccessKey string `json:"secret_access_key"`
|
||||||
SnapshotName *string `json:"snapshot_name,omitempty"`
|
ShareWithAccounts *[]string `json:"share_with_accounts,omitempty"`
|
||||||
|
SnapshotName *string `json:"snapshot_name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AWSUploadRequestOptionsS3 defines model for AWSUploadRequestOptionsS3.
|
// AWSUploadRequestOptionsS3 defines model for AWSUploadRequestOptionsS3.
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,11 @@ components:
|
||||||
snapshot_name:
|
snapshot_name:
|
||||||
type: string
|
type: string
|
||||||
example: 'my-snapshot'
|
example: 'my-snapshot'
|
||||||
|
share_with_accounts:
|
||||||
|
type: array
|
||||||
|
example: ['123456789012']
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
Customizations:
|
Customizations:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
||||||
|
|
@ -167,14 +167,19 @@ func (server *Server) Compose(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var share []string
|
||||||
|
if awsUploadOptions.Ec2.ShareWithAccounts != nil {
|
||||||
|
share = *awsUploadOptions.Ec2.ShareWithAccounts
|
||||||
|
}
|
||||||
key := fmt.Sprintf("composer-api-%s", uuid.New().String())
|
key := fmt.Sprintf("composer-api-%s", uuid.New().String())
|
||||||
t := target.NewAWSTarget(&target.AWSTargetOptions{
|
t := target.NewAWSTarget(&target.AWSTargetOptions{
|
||||||
Filename: imageType.Filename(),
|
Filename: imageType.Filename(),
|
||||||
Region: awsUploadOptions.Region,
|
Region: awsUploadOptions.Region,
|
||||||
AccessKeyID: awsUploadOptions.S3.AccessKeyId,
|
AccessKeyID: awsUploadOptions.S3.AccessKeyId,
|
||||||
SecretAccessKey: awsUploadOptions.S3.SecretAccessKey,
|
SecretAccessKey: awsUploadOptions.S3.SecretAccessKey,
|
||||||
Bucket: awsUploadOptions.S3.Bucket,
|
Bucket: awsUploadOptions.S3.Bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
|
ShareWithAccounts: share,
|
||||||
})
|
})
|
||||||
if awsUploadOptions.Ec2.SnapshotName != nil {
|
if awsUploadOptions.Ec2.SnapshotName != nil {
|
||||||
t.ImageName = *awsUploadOptions.Ec2.SnapshotName
|
t.ImageName = *awsUploadOptions.Ec2.SnapshotName
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package target
|
package target
|
||||||
|
|
||||||
type AWSTargetOptions struct {
|
type AWSTargetOptions struct {
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
AccessKeyID string `json:"accessKeyID"`
|
AccessKeyID string `json:"accessKeyID"`
|
||||||
SecretAccessKey string `json:"secretAccessKey"`
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
Bucket string `json:"bucket"`
|
Bucket string `json:"bucket"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
|
ShareWithAccounts []string `json:"shareWithAccounts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (AWSTargetOptions) isTargetOptions() {}
|
func (AWSTargetOptions) isTargetOptions() {}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import (
|
||||||
|
|
||||||
type AWS struct {
|
type AWS struct {
|
||||||
uploader *s3manager.Uploader
|
uploader *s3manager.Uploader
|
||||||
importer *ec2.EC2
|
ec2 *ec2.EC2
|
||||||
s3 *s3.S3
|
s3 *s3.S3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ func New(region, accessKeyID, accessKey string) (*AWS, error) {
|
||||||
|
|
||||||
return &AWS{
|
return &AWS{
|
||||||
uploader: s3manager.NewUploader(sess),
|
uploader: s3manager.NewUploader(sess),
|
||||||
importer: ec2.New(sess),
|
ec2: ec2.New(sess),
|
||||||
s3: s3.New(sess),
|
s3: s3.New(sess),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -117,10 +117,10 @@ func WaitUntilImportSnapshotTaskCompletedWithContext(c *ec2.EC2, ctx aws.Context
|
||||||
// Register is a function that imports a snapshot, waits for the snapshot to
|
// Register is a function that imports a snapshot, waits for the snapshot to
|
||||||
// fully import, tags the snapshot, cleans up the image in S3, and registers
|
// fully import, tags the snapshot, cleans up the image in S3, and registers
|
||||||
// an AMI in AWS.
|
// an AMI in AWS.
|
||||||
func (a *AWS) Register(name, bucket, key string) (*string, error) {
|
func (a *AWS) Register(name, bucket, key string, shareWith []string) (*string, error) {
|
||||||
log.Printf("[AWS] 📥 Importing snapshot from image: %s/%s", bucket, key)
|
log.Printf("[AWS] 📥 Importing snapshot from image: %s/%s", bucket, key)
|
||||||
snapshotDescription := fmt.Sprintf("Image Builder AWS Import of %s", name)
|
snapshotDescription := fmt.Sprintf("Image Builder AWS Import of %s", name)
|
||||||
importTaskOutput, err := a.importer.ImportSnapshot(
|
importTaskOutput, err := a.ec2.ImportSnapshot(
|
||||||
&ec2.ImportSnapshotInput{
|
&ec2.ImportSnapshotInput{
|
||||||
Description: aws.String(snapshotDescription),
|
Description: aws.String(snapshotDescription),
|
||||||
DiskContainer: &ec2.SnapshotDiskContainer{
|
DiskContainer: &ec2.SnapshotDiskContainer{
|
||||||
|
|
@ -132,12 +132,13 @@ func (a *AWS) Register(name, bucket, key string) (*string, error) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("[AWS] error importing snapshot: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[AWS] 🚚 Waiting for snapshot to finish importing: %s", *importTaskOutput.ImportTaskId)
|
log.Printf("[AWS] 🚚 Waiting for snapshot to finish importing: %s", *importTaskOutput.ImportTaskId)
|
||||||
err = WaitUntilImportSnapshotTaskCompleted(
|
err = WaitUntilImportSnapshotTaskCompleted(
|
||||||
a.importer,
|
a.ec2,
|
||||||
&ec2.DescribeImportSnapshotTasksInput{
|
&ec2.DescribeImportSnapshotTasksInput{
|
||||||
ImportTaskIds: []*string{
|
ImportTaskIds: []*string{
|
||||||
importTaskOutput.ImportTaskId,
|
importTaskOutput.ImportTaskId,
|
||||||
|
|
@ -158,7 +159,7 @@ func (a *AWS) Register(name, bucket, key string) (*string, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
importOutput, err := a.importer.DescribeImportSnapshotTasks(
|
importOutput, err := a.ec2.DescribeImportSnapshotTasks(
|
||||||
&ec2.DescribeImportSnapshotTasksInput{
|
&ec2.DescribeImportSnapshotTasksInput{
|
||||||
ImportTaskIds: []*string{
|
ImportTaskIds: []*string{
|
||||||
importTaskOutput.ImportTaskId,
|
importTaskOutput.ImportTaskId,
|
||||||
|
|
@ -171,8 +172,28 @@ func (a *AWS) Register(name, bucket, key string) (*string, error) {
|
||||||
|
|
||||||
snapshotID := importOutput.ImportSnapshotTasks[0].SnapshotTaskDetail.SnapshotId
|
snapshotID := importOutput.ImportSnapshotTasks[0].SnapshotTaskDetail.SnapshotId
|
||||||
|
|
||||||
|
if len(shareWith) > 0 {
|
||||||
|
log.Printf("[AWS] 🎥 Sharing ec2 snapshot")
|
||||||
|
var userIds []*string
|
||||||
|
for _, v := range shareWith {
|
||||||
|
userIds = append(userIds, &v)
|
||||||
|
}
|
||||||
|
_, err := a.ec2.ModifySnapshotAttribute(
|
||||||
|
&ec2.ModifySnapshotAttributeInput{
|
||||||
|
Attribute: aws.String("createVolumePermission"),
|
||||||
|
OperationType: aws.String("add"),
|
||||||
|
SnapshotId: snapshotID,
|
||||||
|
UserIds: userIds,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Println("[AWS] 📨 Shared ec2 snapshot")
|
||||||
|
}
|
||||||
|
|
||||||
// Tag the snapshot with the image name.
|
// Tag the snapshot with the image name.
|
||||||
req, _ := a.importer.CreateTagsRequest(
|
req, _ := a.ec2.CreateTagsRequest(
|
||||||
&ec2.CreateTagsInput{
|
&ec2.CreateTagsInput{
|
||||||
Resources: []*string{snapshotID},
|
Resources: []*string{snapshotID},
|
||||||
Tags: []*ec2.Tag{
|
Tags: []*ec2.Tag{
|
||||||
|
|
@ -189,7 +210,7 @@ func (a *AWS) Register(name, bucket, key string) (*string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[AWS] 📋 Registering AMI from imported snapshot: %s", *snapshotID)
|
log.Printf("[AWS] 📋 Registering AMI from imported snapshot: %s", *snapshotID)
|
||||||
registerOutput, err := a.importer.RegisterImage(
|
registerOutput, err := a.ec2.RegisterImage(
|
||||||
&ec2.RegisterImageInput{
|
&ec2.RegisterImageInput{
|
||||||
Architecture: aws.String("x86_64"),
|
Architecture: aws.String("x86_64"),
|
||||||
VirtualizationType: aws.String("hvm"),
|
VirtualizationType: aws.String("hvm"),
|
||||||
|
|
@ -211,5 +232,28 @@ func (a *AWS) Register(name, bucket, key string) (*string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[AWS] 🎉 AMI registered: %s", *registerOutput.ImageId)
|
log.Printf("[AWS] 🎉 AMI registered: %s", *registerOutput.ImageId)
|
||||||
|
|
||||||
|
if len(shareWith) > 0 {
|
||||||
|
log.Println("[AWS] 💿 Sharing ec2 AMI")
|
||||||
|
var launchPerms []*ec2.LaunchPermission
|
||||||
|
for _, id := range shareWith {
|
||||||
|
launchPerms = append(launchPerms, &ec2.LaunchPermission{
|
||||||
|
UserId: &id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_, err := a.ec2.ModifyImageAttribute(
|
||||||
|
&ec2.ModifyImageAttributeInput{
|
||||||
|
ImageId: registerOutput.ImageId,
|
||||||
|
LaunchPermission: &ec2.LaunchPermissionModifications{
|
||||||
|
Add: launchPerms,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Println("[AWS] 💿 Shared AMI")
|
||||||
|
}
|
||||||
|
|
||||||
return registerOutput.ImageId, nil
|
return registerOutput.ImageId, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
schutzbot/Jenkinsfile
vendored
3
schutzbot/Jenkinsfile
vendored
|
|
@ -143,6 +143,7 @@ pipeline {
|
||||||
TEST_TYPE = "integration"
|
TEST_TYPE = "integration"
|
||||||
AWS_CREDS = credentials('aws-credentials-osbuildci')
|
AWS_CREDS = credentials('aws-credentials-osbuildci')
|
||||||
AWS_IMAGE_TEST_CREDS = credentials('aws-credentials-osbuild-image-test')
|
AWS_IMAGE_TEST_CREDS = credentials('aws-credentials-osbuild-image-test')
|
||||||
|
AWS_API_TEST_SHARE_ACCOUNT = credentials('aws-credentials-share-account')
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
run_tests('integration', 'bios')
|
run_tests('integration', 'bios')
|
||||||
|
|
@ -213,6 +214,7 @@ pipeline {
|
||||||
TEST_TYPE = "integration"
|
TEST_TYPE = "integration"
|
||||||
AWS_CREDS = credentials('aws-credentials-osbuildci')
|
AWS_CREDS = credentials('aws-credentials-osbuildci')
|
||||||
AWS_IMAGE_TEST_CREDS = credentials('aws-credentials-osbuild-image-test')
|
AWS_IMAGE_TEST_CREDS = credentials('aws-credentials-osbuild-image-test')
|
||||||
|
AWS_API_TEST_SHARE_ACCOUNT = credentials('aws-credentials-share-account')
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
run_tests('integration', 'bios')
|
run_tests('integration', 'bios')
|
||||||
|
|
@ -281,6 +283,7 @@ pipeline {
|
||||||
AWS_CREDS = credentials('aws-credentials-osbuildci')
|
AWS_CREDS = credentials('aws-credentials-osbuildci')
|
||||||
AWS_IMAGE_TEST_CREDS = credentials('aws-credentials-osbuild-image-test')
|
AWS_IMAGE_TEST_CREDS = credentials('aws-credentials-osbuild-image-test')
|
||||||
RHN_REGISTRATION_SCRIPT = credentials('rhn-register-script-production')
|
RHN_REGISTRATION_SCRIPT = credentials('rhn-register-script-production')
|
||||||
|
AWS_API_TEST_SHARE_ACCOUNT = credentials('aws-credentials-share-account')
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
run_tests('integration', 'bios')
|
run_tests('integration', 'bios')
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ set -euxo pipefail
|
||||||
# it needs variables is set to access AWS.
|
# it needs variables is set to access AWS.
|
||||||
#
|
#
|
||||||
|
|
||||||
printenv AWS_REGION AWS_BUCKET AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY > /dev/null
|
printenv AWS_REGION AWS_BUCKET AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_API_TEST_SHARE_ACCOUNT > /dev/null
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -103,7 +103,8 @@ cat > "$REQUEST_FILE" << EOF
|
||||||
"ec2": {
|
"ec2": {
|
||||||
"access_key_id": "${AWS_ACCESS_KEY_ID}",
|
"access_key_id": "${AWS_ACCESS_KEY_ID}",
|
||||||
"secret_access_key": "${AWS_SECRET_ACCESS_KEY}",
|
"secret_access_key": "${AWS_SECRET_ACCESS_KEY}",
|
||||||
"snapshot_name": "${SNAPSHOT_NAME}"
|
"snapshot_name": "${SNAPSHOT_NAME}",
|
||||||
|
"share_with_accounts": ["${AWS_API_TEST_SHARE_ACCOUNT}"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -166,6 +167,30 @@ $AWS_CMD ec2 describe-images \
|
||||||
|
|
||||||
AMI_IMAGE_ID=$(jq -r '.Images[].ImageId' "$WORKDIR/ami.json")
|
AMI_IMAGE_ID=$(jq -r '.Images[].ImageId' "$WORKDIR/ami.json")
|
||||||
SNAPSHOT_ID=$(jq -r '.Images[].BlockDeviceMappings[].Ebs.SnapshotId' "$WORKDIR/ami.json")
|
SNAPSHOT_ID=$(jq -r '.Images[].BlockDeviceMappings[].Ebs.SnapshotId' "$WORKDIR/ami.json")
|
||||||
|
SHARE_OK=1
|
||||||
|
|
||||||
|
# Verify that the ec2 snapshot was shared
|
||||||
|
$AWS_CMD ec2 describe-snapshot-attribute --snapshot-id "$SNAPSHOT_ID" --attribute createVolumePermission > "$WORKDIR/snapshot-attributes.json"
|
||||||
|
|
||||||
|
SHARED_ID=$(jq -r '.CreateVolumePermissions[0].UserId' "$WORKDIR/snapshot-attributes.json")
|
||||||
|
if [ "$AWS_API_TEST_SHARE_ACCOUNT" != "$SHARED_ID" ]; then
|
||||||
|
SHARE_OK=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify that the ec2 ami was shared
|
||||||
|
$AWS_CMD ec2 describe-image-attribute --image-id "$AMI_IMAGE_ID" --attribute launchPermission > "$WORKDIR/ami-attributes.json"
|
||||||
|
|
||||||
|
SHARED_ID=$(jq -r '.LaunchPermissions[0].UserId' "$WORKDIR/ami-attributes.json")
|
||||||
|
if [ "$AWS_API_TEST_SHARE_ACCOUNT" != "$SHARED_ID" ]; then
|
||||||
|
SHARE_OK=0
|
||||||
|
fi
|
||||||
|
|
||||||
$AWS_CMD ec2 deregister-image --image-id "$AMI_IMAGE_ID"
|
$AWS_CMD ec2 deregister-image --image-id "$AMI_IMAGE_ID"
|
||||||
$AWS_CMD ec2 delete-snapshot --snapshot-id "$SNAPSHOT_ID"
|
$AWS_CMD ec2 delete-snapshot --snapshot-id "$SNAPSHOT_ID"
|
||||||
|
|
||||||
|
if [ "$SHARE_OK" != 1 ]; then
|
||||||
|
echo "EC2 snapshot wasn't shared with the AWS_API_TEST_SHARE_ACCOUNT. 😢"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue