From 3dcd8a27300bf7e25b7e82c08e05121843e5e998 Mon Sep 17 00:00:00 2001
From: Dave Tucker
Date: Mon, 5 Jun 2017 14:54:28 +0100
Subject: [PATCH] linuxkit: Add run support for AWS
Signed-off-by: Dave Tucker
---
src/cmd/linuxkit/run.go | 3 +
src/cmd/linuxkit/run_aws.go | 186 ++++++++++++++++++++++++++++++++++++
2 files changed, 189 insertions(+)
create mode 100644 src/cmd/linuxkit/run_aws.go
diff --git a/src/cmd/linuxkit/run.go b/src/cmd/linuxkit/run.go
index b86d6219e..21a9325a9 100644
--- a/src/cmd/linuxkit/run.go
+++ b/src/cmd/linuxkit/run.go
@@ -16,6 +16,7 @@ func runUsage() {
fmt.Printf("'backend' specifies the run backend.\n")
fmt.Printf("If not specified the platform specific default will be used\n")
fmt.Printf("Supported backends are (default platform in brackets):\n")
+ fmt.Printf(" aws\n")
fmt.Printf(" azure\n")
fmt.Printf(" gcp\n")
fmt.Printf(" hyperkit [macOS]\n")
@@ -40,6 +41,8 @@ func run(args []string) {
case "help", "-h", "-help", "--help":
runUsage()
os.Exit(0)
+ case "aws":
+ runAWS(args[1:])
case "hyperkit":
runHyperKit(args[1:])
case "azure":
diff --git a/src/cmd/linuxkit/run_aws.go b/src/cmd/linuxkit/run_aws.go
new file mode 100644
index 000000000..70172f5e4
--- /dev/null
+++ b/src/cmd/linuxkit/run_aws.go
@@ -0,0 +1,186 @@
+package main
+
+import (
+ "encoding/base64"
+ "flag"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ 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"
+)
+
+const (
+ defaultAWSMachine = "t2.micro"
+ defaultAWSDiskSize = 0
+ defaultAWSDiskType = "gp2"
+ defaultAWSZone = "a"
+ // Environment variables. Some are non-standard
+ awsMachineVar = "AWS_MACHINE" // non-standard
+ awsDiskSizeVar = "AWS_DISK_SIZE" // non-standard
+ awsDiskTypeVar = "AWS_DISK_TYPE" // non-standard
+ awsZoneVar = "AWS_ZONE" // non-standard
+)
+
+// Process the run arguments and execute run
+func runAWS(args []string) {
+ flags := flag.NewFlagSet("aws", flag.ExitOnError)
+ invoked := filepath.Base(os.Args[0])
+ flags.Usage = func() {
+ fmt.Printf("USAGE: %s run aws [options] [name]\n\n", invoked)
+ fmt.Printf("'name' is the name of an AWS image that has already been\n")
+ fmt.Printf(" uploaded using 'linuxkit push'\n\n")
+ fmt.Printf("Options:\n\n")
+ flags.PrintDefaults()
+ }
+ machineFlag := flags.String("machine", defaultAWSMachine, "AWS Machine Type")
+ diskSizeFlag := flags.Int("disk-size", 0, "Size of system disk in GB")
+ diskTypeFlag := flags.String("disk-type", defaultAWSDiskType, "AWS Disk Type")
+ zoneFlag := flags.String("zone", defaultAWSZone, "AWS Availability Zone")
+
+ if err := flags.Parse(args); err != nil {
+ log.Fatal("Unable to parse args")
+ }
+
+ remArgs := flags.Args()
+ if len(remArgs) == 0 {
+ fmt.Printf("Please specify the name of the image to boot\n")
+ flags.Usage()
+ os.Exit(1)
+ }
+ name := remArgs[0]
+
+ machine := getStringValue(awsMachineVar, *machineFlag, defaultAWSMachine)
+ diskSize := getIntValue(awsDiskSizeVar, *diskSizeFlag, defaultAWSDiskSize)
+ diskType := getStringValue(awsDiskTypeVar, *diskTypeFlag, defaultAWSDiskType)
+ zone := os.Getenv("AWS_REGION") + getStringValue(awsZoneVar, *zoneFlag, defaultAWSZone)
+
+ sess := session.Must(session.NewSession())
+ compute := ec2.New(sess)
+
+ // 1. Find AMI
+ filter := &ec2.DescribeImagesInput{
+ Filters: []*ec2.Filter{
+ {
+ Name: aws.String("name"),
+ Values: []*string{aws.String(name)},
+ },
+ },
+ }
+ results, err := compute.DescribeImages(filter)
+ if err != nil {
+ log.Fatalf("Unable to describe images: %s", err)
+ }
+ if len(results.Images) < 1 {
+ log.Fatalf("Unable to find image with name %s", name)
+ }
+ if len(results.Images) > 1 {
+ log.Warnf("Found multiple images with the same name, using the first one")
+ }
+ imageID := results.Images[0].ImageId
+
+ // 2. Create Instance
+ params := &ec2.RunInstancesInput{
+ ImageId: imageID,
+ InstanceType: aws.String(machine),
+ MinCount: aws.Int64(1),
+ MaxCount: aws.Int64(1),
+ }
+ runResult, err := compute.RunInstances(params)
+ if err != nil {
+ log.Fatalf("Unable to run instance: %s", err)
+
+ }
+ instanceID := runResult.Instances[0].InstanceId
+ log.Infof("Created instance %s", *instanceID)
+
+ instanceFilter := &ec2.DescribeInstancesInput{
+ Filters: []*ec2.Filter{
+ {
+ Name: aws.String("instance-id"),
+ Values: []*string{instanceID},
+ },
+ },
+ }
+
+ if err = compute.WaitUntilInstanceRunning(instanceFilter); err != nil {
+ log.Fatalf("Error waiting for instance to start: %s", err)
+ }
+ log.Infof("Instance %s is running", *instanceID)
+
+ if diskSize > 0 {
+ // 3. Create EBS Volume
+ diskParams := &ec2.CreateVolumeInput{
+ AvailabilityZone: aws.String(zone),
+ Size: aws.Int64(int64(diskSize)),
+ VolumeType: aws.String(diskType),
+ }
+ log.Debugf("CreateVolume:\n%v\n", diskParams)
+
+ volume, err := compute.CreateVolume(diskParams)
+ if err != nil {
+ log.Fatalf("Error creating volume: %s", err)
+ }
+
+ waitVol := &ec2.DescribeVolumesInput{
+ Filters: []*ec2.Filter{
+ {
+ Name: aws.String("volume-id"),
+ Values: []*string{volume.VolumeId},
+ },
+ },
+ }
+
+ log.Infof("Waiting for volume %s to be available", *volume.VolumeId)
+
+ if err := compute.WaitUntilVolumeAvailable(waitVol); err != nil {
+ log.Fatalf("Error waiting for volume to be available: %s", err)
+ }
+
+ log.Infof("Attaching volume %s to instance %s", *volume.VolumeId, *instanceID)
+ volParams := &ec2.AttachVolumeInput{
+ Device: aws.String("/dev/sda2"),
+ InstanceId: instanceID,
+ VolumeId: volume.VolumeId,
+ }
+ _, err = compute.AttachVolume(volParams)
+ if err != nil {
+ log.Fatalf("Error attaching volume to instance: %s", err)
+ }
+ }
+
+ log.Warnf("AWS doesn't stream serial console output.\n Please use the AWS Management Console to obtain this output \n Console ouput will be displayed when the instance has been stopped.")
+ log.Warn("Waiting for instance to stop...")
+
+ if err = compute.WaitUntilInstanceStopped(instanceFilter); err != nil {
+ log.Fatalf("Error waiting for instance to stop: %s", err)
+ }
+
+ consoleParams := &ec2.GetConsoleOutputInput{
+ InstanceId: instanceID,
+ }
+ output, err := compute.GetConsoleOutput(consoleParams)
+ if err != nil {
+ log.Fatalf("Error getting output from instance %s: %s", *instanceID, err)
+ }
+
+ out, err := base64.StdEncoding.DecodeString(*output.Output)
+ if err != nil {
+ log.Fatalf("Error decoding output: %s", err)
+ }
+ fmt.Printf(string(out) + "\n")
+
+ log.Infof("Terminating instance %s", *instanceID)
+ terminateParams := &ec2.TerminateInstancesInput{
+ InstanceIds: []*string{instanceID},
+ }
+ if _, err := compute.TerminateInstances(terminateParams); err != nil {
+ log.Fatalf("Error terminating instance %s", *instanceID)
+ }
+ if err = compute.WaitUntilInstanceTerminated(instanceFilter); err != nil {
+ log.Fatalf("Error waiting for instance to terminate: %s", err)
+ }
+}