Blame internal/upload/awsupload/awsupload.go

Packit 63bb0d
package awsupload
Packit 63bb0d
Packit 63bb0d
import (
Packit Service 509fd4
	"fmt"
Packit 63bb0d
	"log"
Packit 63bb0d
	"os"
Packit 63bb0d
	"time"
Packit 63bb0d
Packit 63bb0d
	"github.com/aws/aws-sdk-go/aws"
Packit 63bb0d
	"github.com/aws/aws-sdk-go/aws/credentials"
Packit 63bb0d
	"github.com/aws/aws-sdk-go/aws/request"
Packit 63bb0d
	"github.com/aws/aws-sdk-go/aws/session"
Packit 63bb0d
	"github.com/aws/aws-sdk-go/service/ec2"
Packit 63bb0d
	"github.com/aws/aws-sdk-go/service/s3"
Packit 63bb0d
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
type AWS struct {
Packit 63bb0d
	uploader *s3manager.Uploader
Packit Service 509fd4
	ec2      *ec2.EC2
Packit 63bb0d
	s3       *s3.S3
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func New(region, accessKeyID, accessKey string) (*AWS, error) {
Packit 63bb0d
	// Session credentials
Packit 63bb0d
	creds := credentials.NewStaticCredentials(accessKeyID, accessKey, "")
Packit 63bb0d
Packit 63bb0d
	// Create a Session with a custom region
Packit 63bb0d
	sess, err := session.NewSession(&aws.Config{
Packit 63bb0d
		Credentials: creds,
Packit 63bb0d
		Region:      aws.String(region),
Packit 63bb0d
	})
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return nil, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	return &AWS{
Packit 63bb0d
		uploader: s3manager.NewUploader(sess),
Packit Service 509fd4
		ec2:      ec2.New(sess),
Packit 63bb0d
		s3:       s3.New(sess),
Packit 63bb0d
	}, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (a *AWS) Upload(filename, bucket, key string) (*s3manager.UploadOutput, error) {
Packit 63bb0d
	file, err := os.Open(filename)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return nil, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	log.Printf("[AWS] 🚀 Uploading image to S3: %s/%s", bucket, key)
Packit 63bb0d
	return a.uploader.Upload(
Packit 63bb0d
		&s3manager.UploadInput{
Packit 63bb0d
			Bucket: aws.String(bucket),
Packit 63bb0d
			Key:    aws.String(key),
Packit 63bb0d
			Body:   file,
Packit 63bb0d
		},
Packit 63bb0d
	)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// WaitUntilImportSnapshotCompleted uses the Amazon EC2 API operation
Packit 63bb0d
// DescribeImportSnapshots to wait for a condition to be met before returning.
Packit 63bb0d
// If the condition is not met within the max attempt window, an error will
Packit 63bb0d
// be returned.
Packit 63bb0d
func WaitUntilImportSnapshotTaskCompleted(c *ec2.EC2, input *ec2.DescribeImportSnapshotTasksInput) error {
Packit 63bb0d
	return WaitUntilImportSnapshotTaskCompletedWithContext(c, aws.BackgroundContext(), input)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// WaitUntilImportSnapshotCompletedWithContext is an extended version of
Packit 63bb0d
// WaitUntilImportSnapshotCompleted. With the support for passing in a
Packit 63bb0d
// context and options to configure the Waiter and the underlying request
Packit 63bb0d
// options.
Packit 63bb0d
//
Packit 63bb0d
// The context must be non-nil and will be used for request cancellation. If
Packit 63bb0d
// the context is nil a panic will occur. In the future the SDK may create
Packit 63bb0d
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
Packit 63bb0d
// for more information on using Contexts.
Packit 63bb0d
//
Packit 63bb0d
// NOTE(mhayden): The MaxAttempts is set to zero here so that we will keep
Packit 63bb0d
// checking the status of the image import until it succeeds or fails. This
Packit 63bb0d
// process can take anywhere from 5 to 60+ minutes depending on how quickly
Packit 63bb0d
// AWS can import the snapshot.
Packit 63bb0d
func WaitUntilImportSnapshotTaskCompletedWithContext(c *ec2.EC2, ctx aws.Context, input *ec2.DescribeImportSnapshotTasksInput, opts ...request.WaiterOption) error {
Packit 63bb0d
	w := request.Waiter{
Packit 63bb0d
		Name:        "WaitUntilImportSnapshotTaskCompleted",
Packit 63bb0d
		MaxAttempts: 0,
Packit 63bb0d
		Delay:       request.ConstantWaiterDelay(15 * time.Second),
Packit 63bb0d
		Acceptors: []request.WaiterAcceptor{
Packit 63bb0d
			{
Packit 63bb0d
				State:   request.SuccessWaiterState,
Packit 63bb0d
				Matcher: request.PathAllWaiterMatch, Argument: "ImportSnapshotTasks[].SnapshotTaskDetail.Status",
Packit 63bb0d
				Expected: "completed",
Packit 63bb0d
			},
Packit 63bb0d
			{
Packit 63bb0d
				State:   request.FailureWaiterState,
Packit 63bb0d
				Matcher: request.PathAllWaiterMatch, Argument: "ImportSnapshotTasks[].SnapshotTaskDetail.Status",
Packit 63bb0d
				Expected: "deleted",
Packit 63bb0d
			},
Packit 63bb0d
		},
Packit 63bb0d
		Logger: c.Config.Logger,
Packit 63bb0d
		NewRequest: func(opts []request.Option) (*request.Request, error) {
Packit 63bb0d
			var inCpy *ec2.DescribeImportSnapshotTasksInput
Packit 63bb0d
			if input != nil {
Packit 63bb0d
				tmp := *input
Packit 63bb0d
				inCpy = &tmp
Packit 63bb0d
			}
Packit 63bb0d
			req, _ := c.DescribeImportSnapshotTasksRequest(inCpy)
Packit 63bb0d
			req.SetContext(ctx)
Packit 63bb0d
			req.ApplyOptions(opts...)
Packit 63bb0d
			return req, nil
Packit 63bb0d
		},
Packit 63bb0d
	}
Packit 63bb0d
	w.ApplyOptions(opts...)
Packit 63bb0d
Packit 63bb0d
	return w.WaitWithContext(ctx)
Packit 63bb0d
}
Packit 63bb0d
Packit Service 509fd4
// Register is a function that imports a snapshot, waits for the snapshot to
Packit Service 509fd4
// fully import, tags the snapshot, cleans up the image in S3, and registers
Packit Service 509fd4
// an AMI in AWS.
Packit Service 509fd4
func (a *AWS) Register(name, bucket, key string, shareWith []string, rpmArch string) (*string, error) {
Packit Service 509fd4
	rpmArchToEC2Arch := map[string]string{
Packit Service 509fd4
		"x86_64":  "x86_64",
Packit Service 509fd4
		"aarch64": "arm64",
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	ec2Arch, validArch := rpmArchToEC2Arch[rpmArch]
Packit Service 509fd4
	if !validArch {
Packit Service 509fd4
		return nil, fmt.Errorf("ec2 doesn't support the following arch: %s", rpmArch)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit 63bb0d
	log.Printf("[AWS] 📥 Importing snapshot from image: %s/%s", bucket, key)
Packit Service 509fd4
	snapshotDescription := fmt.Sprintf("Image Builder AWS Import of %s", name)
Packit Service 509fd4
	importTaskOutput, err := a.ec2.ImportSnapshot(
Packit 63bb0d
		&ec2.ImportSnapshotInput{
Packit Service 509fd4
			Description: aws.String(snapshotDescription),
Packit 63bb0d
			DiskContainer: &ec2.SnapshotDiskContainer{
Packit 63bb0d
				UserBucket: &ec2.UserBucket{
Packit 63bb0d
					S3Bucket: aws.String(bucket),
Packit 63bb0d
					S3Key:    aws.String(key),
Packit 63bb0d
				},
Packit 63bb0d
			},
Packit 63bb0d
		},
Packit 63bb0d
	)
Packit 63bb0d
	if err != nil {
Packit Service 509fd4
		log.Printf("[AWS] error importing snapshot: %s", err)
Packit 63bb0d
		return nil, err
Packit 63bb0d
	}
Packit 63bb0d
Packit Service 509fd4
	log.Printf("[AWS] 🚚 Waiting for snapshot to finish importing: %s", *importTaskOutput.ImportTaskId)
Packit 63bb0d
	err = WaitUntilImportSnapshotTaskCompleted(
Packit Service 509fd4
		a.ec2,
Packit 63bb0d
		&ec2.DescribeImportSnapshotTasksInput{
Packit 63bb0d
			ImportTaskIds: []*string{
Packit 63bb0d
				importTaskOutput.ImportTaskId,
Packit 63bb0d
			},
Packit 63bb0d
		},
Packit 63bb0d
	)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return nil, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// we no longer need the object in s3, let's just delete it
Packit 63bb0d
	log.Printf("[AWS] 🧹 Deleting image from S3: %s/%s", bucket, key)
Packit 63bb0d
	_, err = a.s3.DeleteObject(&s3.DeleteObjectInput{
Packit 63bb0d
		Bucket: aws.String(bucket),
Packit 63bb0d
		Key:    aws.String(key),
Packit 63bb0d
	})
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return nil, err
Packit 63bb0d
	}
Packit 63bb0d
Packit Service 509fd4
	importOutput, err := a.ec2.DescribeImportSnapshotTasks(
Packit 63bb0d
		&ec2.DescribeImportSnapshotTasksInput{
Packit 63bb0d
			ImportTaskIds: []*string{
Packit 63bb0d
				importTaskOutput.ImportTaskId,
Packit 63bb0d
			},
Packit 63bb0d
		},
Packit 63bb0d
	)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return nil, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	snapshotID := importOutput.ImportSnapshotTasks[0].SnapshotTaskDetail.SnapshotId
Packit Service 509fd4
Packit Service 509fd4
	if len(shareWith) > 0 {
Packit Service 509fd4
		log.Printf("[AWS] 🎥 Sharing ec2 snapshot")
Packit Service 509fd4
		var userIds []*string
Packit Service 509fd4
		for _, v := range shareWith {
Packit Service 509fd4
			userIds = append(userIds, &v)
Packit Service 509fd4
		}
Packit Service 509fd4
		_, err := a.ec2.ModifySnapshotAttribute(
Packit Service 509fd4
			&ec2.ModifySnapshotAttributeInput{
Packit Service 509fd4
				Attribute:     aws.String("createVolumePermission"),
Packit Service 509fd4
				OperationType: aws.String("add"),
Packit Service 509fd4
				SnapshotId:    snapshotID,
Packit Service 509fd4
				UserIds:       userIds,
Packit Service 509fd4
			},
Packit Service 509fd4
		)
Packit Service 509fd4
		if err != nil {
Packit Service 509fd4
			return nil, err
Packit Service 509fd4
		}
Packit Service 509fd4
		log.Println("[AWS] 📨 Shared ec2 snapshot")
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	// Tag the snapshot with the image name.
Packit Service 509fd4
	req, _ := a.ec2.CreateTagsRequest(
Packit Service 509fd4
		&ec2.CreateTagsInput{
Packit Service 509fd4
			Resources: []*string{snapshotID},
Packit Service 509fd4
			Tags: []*ec2.Tag{
Packit Service 509fd4
				{
Packit Service 509fd4
					Key:   aws.String("Name"),
Packit Service 509fd4
					Value: aws.String(name),
Packit Service 509fd4
				},
Packit Service 509fd4
			},
Packit Service 509fd4
		},
Packit Service 509fd4
	)
Packit Service 509fd4
	err = req.Send()
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return nil, err
Packit Service 509fd4
	}
Packit Service 509fd4
Packit 63bb0d
	log.Printf("[AWS] 📋 Registering AMI from imported snapshot: %s", *snapshotID)
Packit Service 509fd4
	registerOutput, err := a.ec2.RegisterImage(
Packit 63bb0d
		&ec2.RegisterImageInput{
Packit Service 509fd4
			Architecture:       aws.String(ec2Arch),
Packit 63bb0d
			VirtualizationType: aws.String("hvm"),
Packit 63bb0d
			Name:               aws.String(name),
Packit 63bb0d
			RootDeviceName:     aws.String("/dev/sda1"),
Packit 63bb0d
			EnaSupport:         aws.Bool(true),
Packit 63bb0d
			BlockDeviceMappings: []*ec2.BlockDeviceMapping{
Packit 63bb0d
				{
Packit 63bb0d
					DeviceName: aws.String("/dev/sda1"),
Packit 63bb0d
					Ebs: &ec2.EbsBlockDevice{
Packit 63bb0d
						SnapshotId: snapshotID,
Packit 63bb0d
					},
Packit 63bb0d
				},
Packit 63bb0d
			},
Packit 63bb0d
		},
Packit 63bb0d
	)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return nil, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	log.Printf("[AWS] 🎉 AMI registered: %s", *registerOutput.ImageId)
Packit Service 509fd4
Packit Service 509fd4
	if len(shareWith) > 0 {
Packit Service 509fd4
		log.Println("[AWS] 💿 Sharing ec2 AMI")
Packit Service 509fd4
		var launchPerms []*ec2.LaunchPermission
Packit Service 509fd4
		for _, id := range shareWith {
Packit Service 509fd4
			launchPerms = append(launchPerms, &ec2.LaunchPermission{
Packit Service 509fd4
				UserId: &id,
Packit Service 509fd4
			})
Packit Service 509fd4
		}
Packit Service 509fd4
		_, err := a.ec2.ModifyImageAttribute(
Packit Service 509fd4
			&ec2.ModifyImageAttributeInput{
Packit Service 509fd4
				ImageId: registerOutput.ImageId,
Packit Service 509fd4
				LaunchPermission: &ec2.LaunchPermissionModifications{
Packit Service 509fd4
					Add: launchPerms,
Packit Service 509fd4
				},
Packit Service 509fd4
			},
Packit Service 509fd4
		)
Packit Service 509fd4
		if err != nil {
Packit Service 509fd4
			return nil, err
Packit Service 509fd4
		}
Packit Service 509fd4
		log.Println("[AWS] 💿 Shared AMI")
Packit Service 509fd4
	}
Packit Service 509fd4
Packit 63bb0d
	return registerOutput.ImageId, nil
Packit 63bb0d
}