From 0737694eb7d4ef637207403bb89d023d7377fc03 Mon Sep 17 00:00:00 2001
From: Dave Tucker
Date: Mon, 5 Jun 2017 12:26:57 +0100
Subject: [PATCH] linuxkit: Add push support for AWS
Signed-off-by: Dave Tucker
---
src/cmd/linuxkit/push.go | 4 +
src/cmd/linuxkit/push_aws.go | 165 +++++++++++++++++++++++++++++++++++
2 files changed, 169 insertions(+)
create mode 100644 src/cmd/linuxkit/push_aws.go
diff --git a/src/cmd/linuxkit/push.go b/src/cmd/linuxkit/push.go
index b4d9af056..f7295bdfe 100644
--- a/src/cmd/linuxkit/push.go
+++ b/src/cmd/linuxkit/push.go
@@ -17,6 +17,8 @@ func pushUsage() {
fmt.Printf(" gcp\n")
fmt.Printf(" vcenter\n")
fmt.Printf(" azure\n")
+ fmt.Printf(" aws\n")
+ fmt.Printf("\n")
fmt.Printf("'options' are the backend specific options.\n")
fmt.Printf("See '%s push [backend] --help' for details.\n\n", invoked)
fmt.Printf("'prefix' specifies the path to the VM image.\n")
@@ -38,6 +40,8 @@ func push(args []string) {
pushVCenter(args[1:])
case "azure":
pushAzure(args[1:])
+ case "aws":
+ pushAWS(args[1:])
default:
log.Errorf("No 'push' backend specified.")
}
diff --git a/src/cmd/linuxkit/push_aws.go b/src/cmd/linuxkit/push_aws.go
new file mode 100644
index 000000000..d4a9faefe
--- /dev/null
+++ b/src/cmd/linuxkit/push_aws.go
@@ -0,0 +1,165 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "time"
+
+ log "github.com/Sirupsen/logrus"
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/aws/aws-sdk-go/service/ec2"
+ "github.com/aws/aws-sdk-go/service/s3"
+)
+
+const timeoutVar = "LINUXKIT_UPLOAD_TIMEOUT"
+
+func pushAWS(args []string) {
+ awsCmd := flag.NewFlagSet("aws", flag.ExitOnError)
+ invoked := filepath.Base(os.Args[0])
+ awsCmd.Usage = func() {
+ fmt.Printf("USAGE: %s push aws [options] [name]\n\n", invoked)
+ fmt.Printf("'name' specifies the full path of an image file which will be uploaded\n")
+ fmt.Printf("Options:\n\n")
+ awsCmd.PrintDefaults()
+ }
+ timeoutFlag := awsCmd.Int("timeout", 0, "Upload timeout in seconds")
+ bucketFlag := awsCmd.String("bucket", "", "S3 Bucket to upload to. *Required*")
+ nameFlag := awsCmd.String("img-name", "", "Overrides the Name used to identify the file in Amazon S3 and Image. Defaults to [name] with the file extension removed.")
+
+ if err := awsCmd.Parse(args); err != nil {
+ log.Fatal("Unable to parse args")
+ }
+
+ remArgs := awsCmd.Args()
+ if len(remArgs) == 0 {
+ fmt.Printf("Please specify the path to the image to push\n")
+ awsCmd.Usage()
+ os.Exit(1)
+ }
+ src := remArgs[0]
+
+ timeout := getIntValue(timeoutVar, *timeoutFlag, 600)
+ bucket := getStringValue(bucketVar, *bucketFlag, "")
+ name := getStringValue(nameVar, *nameFlag, "")
+
+ sess := session.Must(session.NewSession())
+ storage := s3.New(sess)
+
+ ctx, cancelFn := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
+ defer cancelFn()
+
+ if bucket == "" {
+ log.Fatalf("No bucket specified. Please provide one using the -bucket flag")
+ }
+
+ f, err := os.Open(src)
+ if err != nil {
+ log.Fatalf("Error opening file: %s", err)
+ }
+ defer f.Close()
+
+ if name == "" {
+ name = strings.TrimSuffix(name, filepath.Ext(src))
+ }
+
+ content, err := ioutil.ReadAll(f)
+ if err != nil {
+ log.Fatalf("error reading file: %s", err)
+ }
+
+ dst := name + filepath.Ext(src)
+ putParams := &s3.PutObjectInput{
+ Bucket: aws.String(bucket),
+ Key: aws.String(dst),
+ Body: f,
+ ContentLength: aws.Int64(int64(len(content))),
+ ContentType: aws.String("application/octet-stream"),
+ }
+ log.Debugf("PutObject:\n%v", putParams)
+
+ _, err = storage.PutObjectWithContext(ctx, putParams)
+ if err != nil {
+ log.Fatalf("Error uploading to S3: %s", err)
+ }
+
+ compute := ec2.New(sess)
+
+ importParams := &ec2.ImportSnapshotInput{
+ Description: aws.String(fmt.Sprintf("LinuxKit: %s", name)),
+ DiskContainer: &ec2.SnapshotDiskContainer{
+ Description: aws.String(fmt.Sprintf("LinuxKit: %s disk", name)),
+ Format: aws.String("raw"),
+ UserBucket: &ec2.UserBucket{
+ S3Bucket: aws.String(bucket),
+ S3Key: aws.String(dst),
+ },
+ },
+ }
+ log.Debugf("ImportSnapshot:\n%v", importParams)
+
+ resp, err := compute.ImportSnapshot(importParams)
+ if err != nil {
+ log.Fatalf("Error importing snapshot: %s", err)
+ }
+
+ var snapshotID *string
+ for {
+ describeParams := &ec2.DescribeImportSnapshotTasksInput{
+ ImportTaskIds: []*string{
+ resp.ImportTaskId,
+ },
+ }
+ log.Debugf("DescribeImportSnapshotTask:\n%v", describeParams)
+ status, err := compute.DescribeImportSnapshotTasks(describeParams)
+ if err != nil {
+ log.Fatalf("Error getting import snapshot status: %s", err)
+ }
+ if len(status.ImportSnapshotTasks) == 0 {
+ log.Fatalf("Unable to get import snapshot task status")
+ }
+ if *status.ImportSnapshotTasks[0].SnapshotTaskDetail.Status != "completed" {
+ progress := "0"
+ if status.ImportSnapshotTasks[0].SnapshotTaskDetail.Progress != nil {
+ progress = *status.ImportSnapshotTasks[0].SnapshotTaskDetail.Progress
+ }
+ log.Debugf("Task %s is %s%% complete. Waiting 60 seconds...\n", *resp.ImportTaskId, progress)
+ time.Sleep(60 * time.Second)
+ continue
+ }
+ snapshotID = status.ImportSnapshotTasks[0].SnapshotTaskDetail.SnapshotId
+ break
+ }
+
+ if snapshotID == nil {
+ log.Fatalf("SnapshotID unavailable after import completed")
+ }
+
+ regParams := &ec2.RegisterImageInput{
+ Name: aws.String(name), // Required
+ Architecture: aws.String("x86_64"),
+ BlockDeviceMappings: []*ec2.BlockDeviceMapping{
+ {
+ DeviceName: aws.String("/dev/sda1"),
+ Ebs: &ec2.EbsBlockDevice{
+ DeleteOnTermination: aws.Bool(true),
+ SnapshotId: snapshotID,
+ VolumeType: aws.String("standard"),
+ },
+ },
+ },
+ Description: aws.String(fmt.Sprintf("LinuxKit: %s image", name)),
+ RootDeviceName: aws.String("/dev/sda1"),
+ VirtualizationType: aws.String("hvm"),
+ }
+ log.Debugf("RegisterImage:\n%v", regParams)
+ regResp, err := compute.RegisterImage(regParams)
+ if err != nil {
+ log.Fatalf("Error registering the image: %s", err)
+ }
+ log.Infof("Created AMI: %s", *regResp.ImageId)
+}