mirror of
				https://github.com/linuxkit/linuxkit.git
				synced 2025-10-31 01:37:42 +00:00 
			
		
		
		
	run: Add gcp backend
This commit implements `moby run gcp` which allows for testing of moby images on the Google Cloud Platform This backend attaches (via SSH) to the serial console. It generates instance-only SSH keys and adds the public key to the image metadata. These are used by the `moby` tool only. It will also automatically upload a file and creates an image if the prefix given to `moby run` is a filename Signed-off-by: Dave Tucker <dt@docker.com>
This commit is contained in:
		
							
								
								
									
										24
									
								
								docs/gcp.md
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								docs/gcp.md
									
									
									
									
									
								
							| @@ -4,6 +4,13 @@ This is a quick guide to run Moby on GCP. | |||||||
|  |  | ||||||
| ## Setup | ## Setup | ||||||
|  |  | ||||||
|  | You have two choices for authentication with Google Cloud | ||||||
|  |  | ||||||
|  | 1. You can use [Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials) | ||||||
|  | 2. You can use a Service Account | ||||||
|  |  | ||||||
|  | ### Application Default Credentials | ||||||
|  |  | ||||||
| You need the [Google Cloud SDK](https://cloud.google.com/sdk/) | You need the [Google Cloud SDK](https://cloud.google.com/sdk/) | ||||||
| installed.  Either install it from the URL or view `brew` (on a Mac): | installed.  Either install it from the URL or view `brew` (on a Mac): | ||||||
| ```shell | ```shell | ||||||
| @@ -21,9 +28,16 @@ The authentication will redirect to a browser with Google login. | |||||||
|  |  | ||||||
| Also authenticate local applications with | Also authenticate local applications with | ||||||
| ``` | ``` | ||||||
| gcloud beta auth application-default login | gcloud auth application-default login | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | ### Service Account | ||||||
|  |  | ||||||
|  | You can use [this guide](https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#createanewserviceaccount) | ||||||
|  | to create a Service Account. | ||||||
|  |  | ||||||
|  | Make sure to download the credentials in JSON format and store them somewhere safe. | ||||||
|  |  | ||||||
| ## Build a moby image | ## Build a moby image | ||||||
|  |  | ||||||
| Add a `gcp` output line to your yaml config, see the example in `examples/gcp.yml`. | Add a `gcp` output line to your yaml config, see the example in `examples/gcp.yml`. | ||||||
| @@ -38,10 +52,6 @@ specified bucket, and create a bootable image. | |||||||
| With the image created, we can now create an instance and connect to | With the image created, we can now create an instance and connect to | ||||||
| the serial port. | the serial port. | ||||||
|  |  | ||||||
| ```shell | ``` | ||||||
| gcloud compute instances create my-node \ | moby run gcp -project myproject-1234 myfile | ||||||
|   --image="myfile" --metadata serial-port-enable=true \ |  | ||||||
|   --machine-type="g1-small" --boot-disk-size=200 |  | ||||||
|  |  | ||||||
| gcloud compute connect-to-serial-port my-node |  | ||||||
| ``` | ``` | ||||||
|   | |||||||
| @@ -1,13 +1,18 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"crypto/rsa" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	"github.com/docker/docker/pkg/term" | ||||||
|  | 	"golang.org/x/crypto/ssh" | ||||||
| 	"golang.org/x/net/context" | 	"golang.org/x/net/context" | ||||||
| 	"golang.org/x/oauth2/google" | 	"golang.org/x/oauth2/google" | ||||||
| 	"google.golang.org/api/compute/v1" | 	"google.golang.org/api/compute/v1" | ||||||
| @@ -15,6 +20,9 @@ import ( | |||||||
| 	"google.golang.org/api/storage/v1" | 	"google.golang.org/api/storage/v1" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | const pollingInterval = 500 * time.Millisecond | ||||||
|  | const timeout = 300 | ||||||
|  |  | ||||||
| // GCPClient contains state required for communication with GCP | // GCPClient contains state required for communication with GCP | ||||||
| type GCPClient struct { | type GCPClient struct { | ||||||
| 	client      *http.Client | 	client      *http.Client | ||||||
| @@ -22,6 +30,7 @@ type GCPClient struct { | |||||||
| 	storage     *storage.Service | 	storage     *storage.Service | ||||||
| 	projectName string | 	projectName string | ||||||
| 	fileName    string | 	fileName    string | ||||||
|  | 	privKey     *rsa.PrivateKey | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewGCPClient creates a new GCP client | // NewGCPClient creates a new GCP client | ||||||
| @@ -80,6 +89,12 @@ func NewGCPClient(keys, projectName string) (*GCPClient, error) { | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	log.Debugf("Generating SSH Keypair") | ||||||
|  | 	client.privKey, err = rsa.GenerateKey(rand.Reader, 2048) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return client, nil | 	return client, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -110,21 +125,9 @@ func (g GCPClient) UploadFile(filename, bucketName string, public bool) error { | |||||||
| // CreateImage creates a GCE image using the a source from Google Storage | // CreateImage creates a GCE image using the a source from Google Storage | ||||||
| func (g GCPClient) CreateImage(filename, storageURL, family string, replace bool) error { | func (g GCPClient) CreateImage(filename, storageURL, family string, replace bool) error { | ||||||
| 	if replace { | 	if replace { | ||||||
| 		var notFound bool | 		if err := g.DeleteImage(filename); err != nil { | ||||||
| 		op, err := g.compute.Images.Delete(g.projectName, filename).Do() |  | ||||||
| 		if err != nil { |  | ||||||
| 			if err.(*googleapi.Error).Code != 404 { |  | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 			notFound = true |  | ||||||
| 		} |  | ||||||
| 		if !notFound { |  | ||||||
| 			log.Infof("Deleting existing image...") |  | ||||||
| 			if err := g.pollOperationStatus(op.Name); err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			log.Infof("Image %s deleted", filename) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	log.Infof("Creating image: %s", filename) | 	log.Infof("Creating image: %s", filename) | ||||||
| @@ -151,6 +154,260 @@ func (g GCPClient) CreateImage(filename, storageURL, family string, replace bool | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // DeleteImage deletes and image | ||||||
|  | func (g GCPClient) DeleteImage(filename string) error { | ||||||
|  | 	var notFound bool | ||||||
|  | 	op, err := g.compute.Images.Delete(g.projectName, filename).Do() | ||||||
|  | 	if err != nil { | ||||||
|  | 		if err.(*googleapi.Error).Code != 404 { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		notFound = true | ||||||
|  | 	} | ||||||
|  | 	if !notFound { | ||||||
|  | 		log.Infof("Deleting existing image...") | ||||||
|  | 		if err := g.pollOperationStatus(op.Name); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		log.Infof("Image %s deleted", filename) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CreateInstance creates and starts an instance on GCE | ||||||
|  | func (g GCPClient) CreateInstance(image, zone, machineType string, replace bool) error { | ||||||
|  | 	if replace { | ||||||
|  | 		if err := g.DeleteInstance(image, zone, true); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	log.Infof("Creating instance %s", image) | ||||||
|  | 	enabled := new(string) | ||||||
|  | 	*enabled = "1" | ||||||
|  |  | ||||||
|  | 	k, err := ssh.NewPublicKey(g.privKey.Public()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	sshKey := new(string) | ||||||
|  | 	*sshKey = fmt.Sprintf("moby:%s moby", string(ssh.MarshalAuthorizedKey(k))) | ||||||
|  |  | ||||||
|  | 	instanceObj := &compute.Instance{ | ||||||
|  | 		MachineType: fmt.Sprintf("zones/%s/machineTypes/%s", zone, machineType), | ||||||
|  | 		Name:        image, | ||||||
|  | 		Disks: []*compute.AttachedDisk{ | ||||||
|  | 			{ | ||||||
|  | 				AutoDelete: true, | ||||||
|  | 				Boot:       true, | ||||||
|  | 				InitializeParams: &compute.AttachedDiskInitializeParams{ | ||||||
|  | 					SourceImage: fmt.Sprintf("global/images/%s", image), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		NetworkInterfaces: []*compute.NetworkInterface{ | ||||||
|  | 			{ | ||||||
|  | 				Network: "global/networks/default", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Metadata: &compute.Metadata{ | ||||||
|  | 			Items: []*compute.MetadataItems{ | ||||||
|  | 				{ | ||||||
|  | 					Key:   "serial-port-enable", | ||||||
|  | 					Value: enabled, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Key:   "ssh-keys", | ||||||
|  | 					Value: sshKey, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Don't wait for operation to complete! | ||||||
|  | 	// A headstart is needed as by the time we've polled for this event to be | ||||||
|  | 	// completed, the instance may have already terminated | ||||||
|  | 	_, err = g.compute.Instances.Insert(g.projectName, zone, instanceObj).Do() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	log.Infof("Instance created") | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DeleteInstance removes an instance | ||||||
|  | func (g GCPClient) DeleteInstance(instance, zone string, wait bool) error { | ||||||
|  | 	var notFound bool | ||||||
|  | 	op, err := g.compute.Instances.Delete(g.projectName, zone, instance).Do() | ||||||
|  | 	if err != nil { | ||||||
|  | 		if err.(*googleapi.Error).Code != 404 { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		notFound = true | ||||||
|  | 	} | ||||||
|  | 	if !notFound && wait { | ||||||
|  | 		log.Infof("Deleting existing instance...") | ||||||
|  | 		if err := g.pollZoneOperationStatus(op.Name, zone); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		log.Infof("Instance %s deleted", instance) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetInstanceSerialOutput streams the serial output of an instance | ||||||
|  | func (g GCPClient) GetInstanceSerialOutput(instance, zone string) error { | ||||||
|  | 	log.Infof("Getting serial port output for instance %s", instance) | ||||||
|  | 	var next int64 | ||||||
|  | 	for { | ||||||
|  | 		res, err := g.compute.Instances.GetSerialPortOutput(g.projectName, zone, instance).Start(next).Do() | ||||||
|  | 		if err != nil { | ||||||
|  | 			if err.(*googleapi.Error).Code == 400 { | ||||||
|  | 				// Instance may not be ready yet... | ||||||
|  | 				time.Sleep(pollingInterval) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			if err.(*googleapi.Error).Code == 503 { | ||||||
|  | 				// Timeout received when the instance has terminated | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		fmt.Printf(res.Contents) | ||||||
|  | 		next = res.Next | ||||||
|  | 		// When the instance has been stopped, Start and Next will both be 0 | ||||||
|  | 		if res.Start > 0 && next == 0 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ConnectToInstanceSerialPort uses SSH to connect to the serial port of the instance | ||||||
|  | func (g GCPClient) ConnectToInstanceSerialPort(instance, zone string) error { | ||||||
|  | 	log.Infof("Connecting to serial port of instance %s", instance) | ||||||
|  | 	gPubKeyURL := "https://cloud-certs.storage.googleapis.com/google-cloud-serialport-host-key.pub" | ||||||
|  | 	resp, err := http.Get(gPubKeyURL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 	body, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	gPubKey, _, _, _, err := ssh.ParseAuthorizedKey(body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	signer, err := ssh.NewSignerFromKey(g.privKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	config := &ssh.ClientConfig{ | ||||||
|  | 		User: fmt.Sprintf("%s.%s.%s.moby", g.projectName, zone, instance), | ||||||
|  | 		Auth: []ssh.AuthMethod{ | ||||||
|  | 			ssh.PublicKeys(signer), | ||||||
|  | 		}, | ||||||
|  | 		HostKeyCallback: ssh.FixedHostKey(gPubKey), | ||||||
|  | 		Timeout:         5 * time.Second, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var conn *ssh.Client | ||||||
|  | 	// Retry connection as VM may not be ready yet | ||||||
|  | 	for i := 0; i < timeout; i++ { | ||||||
|  | 		conn, err = ssh.Dial("tcp", "ssh-serialport.googleapis.com:9600", config) | ||||||
|  | 		if err != nil { | ||||||
|  | 			time.Sleep(pollingInterval) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  | 	if conn == nil { | ||||||
|  | 		return fmt.Errorf(err.Error()) | ||||||
|  | 	} | ||||||
|  | 	defer conn.Close() | ||||||
|  |  | ||||||
|  | 	session, err := conn.NewSession() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer session.Close() | ||||||
|  |  | ||||||
|  | 	stdin, err := session.StdinPipe() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("Unable to setup stdin for session: %v", err) | ||||||
|  | 	} | ||||||
|  | 	go io.Copy(stdin, os.Stdin) | ||||||
|  |  | ||||||
|  | 	stdout, err := session.StdoutPipe() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("Unable to setup stdout for session: %v", err) | ||||||
|  | 	} | ||||||
|  | 	go io.Copy(os.Stdout, stdout) | ||||||
|  |  | ||||||
|  | 	stderr, err := session.StderrPipe() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("Unable to setup stderr for session: %v", err) | ||||||
|  | 	} | ||||||
|  | 	go io.Copy(os.Stderr, stderr) | ||||||
|  | 	/* | ||||||
|  | 		c := make(chan os.Signal, 1) | ||||||
|  | 		exit := make(chan bool, 1) | ||||||
|  | 		signal.Notify(c) | ||||||
|  | 		go func(exit <-chan bool, c <-chan os.Signal) { | ||||||
|  | 			select { | ||||||
|  | 			case <-exit: | ||||||
|  | 				return | ||||||
|  | 			case s := <-c: | ||||||
|  | 				switch s { | ||||||
|  | 				// CTRL+C | ||||||
|  | 				case os.Interrupt: | ||||||
|  | 					session.Signal(ssh.SIGINT) | ||||||
|  | 				// CTRL+\ | ||||||
|  | 				case os.Kill: | ||||||
|  | 					session.Signal(ssh.SIGQUIT) | ||||||
|  | 				default: | ||||||
|  | 					log.Debugf("Received signal %s but not forwarding to ssh", s) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}(exit, c) | ||||||
|  | 	*/ | ||||||
|  | 	var termWidth, termHeight int | ||||||
|  | 	fd := os.Stdin.Fd() | ||||||
|  |  | ||||||
|  | 	if term.IsTerminal(fd) { | ||||||
|  | 		oldState, err := term.MakeRaw(fd) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		defer term.RestoreTerminal(fd, oldState) | ||||||
|  |  | ||||||
|  | 		winsize, err := term.GetWinsize(fd) | ||||||
|  | 		if err != nil { | ||||||
|  | 			termWidth = 80 | ||||||
|  | 			termHeight = 24 | ||||||
|  | 		} else { | ||||||
|  | 			termWidth = int(winsize.Width) | ||||||
|  | 			termHeight = int(winsize.Height) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	session.RequestPty("xterm", termHeight, termWidth, ssh.TerminalModes{ | ||||||
|  | 		ssh.ECHO: 1, | ||||||
|  | 	}) | ||||||
|  | 	session.Shell() | ||||||
|  |  | ||||||
|  | 	err = session.Wait() | ||||||
|  | 	//exit <- true | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func (g *GCPClient) pollOperationStatus(operationName string) error { | func (g *GCPClient) pollOperationStatus(operationName string) error { | ||||||
| 	for i := 0; i < timeout; i++ { | 	for i := 0; i < timeout; i++ { | ||||||
| 		operation, err := g.compute.GlobalOperations.Get(g.projectName, operationName).Do() | 		operation, err := g.compute.GlobalOperations.Get(g.projectName, operationName).Do() | ||||||
| @@ -168,3 +425,19 @@ func (g *GCPClient) pollOperationStatus(operationName string) error { | |||||||
| 	return fmt.Errorf("timeout waiting for operation to finish") | 	return fmt.Errorf("timeout waiting for operation to finish") | ||||||
|  |  | ||||||
| } | } | ||||||
|  | func (g *GCPClient) pollZoneOperationStatus(operationName, zone string) error { | ||||||
|  | 	for i := 0; i < timeout; i++ { | ||||||
|  | 		operation, err := g.compute.ZoneOperations.Get(g.projectName, zone, operationName).Do() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error fetching operation status: %v", err) | ||||||
|  | 		} | ||||||
|  | 		if operation.Error != nil { | ||||||
|  | 			return fmt.Errorf("error running operation: %v", operation.Error) | ||||||
|  | 		} | ||||||
|  | 		if operation.Status == "DONE" { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		time.Sleep(pollingInterval) | ||||||
|  | 	} | ||||||
|  | 	return fmt.Errorf("timeout waiting for operation to finish") | ||||||
|  | } | ||||||
|   | |||||||
| @@ -37,6 +37,8 @@ func run(args []string) { | |||||||
| 		runHyperKit(args[1:]) | 		runHyperKit(args[1:]) | ||||||
| 	case "vmware": | 	case "vmware": | ||||||
| 		runVMware(args[1:]) | 		runVMware(args[1:]) | ||||||
|  | 	case "gcp": | ||||||
|  | 		runGcp(args[1:]) | ||||||
| 	default: | 	default: | ||||||
| 		switch runtime.GOOS { | 		switch runtime.GOOS { | ||||||
| 		case "darwin": | 		case "darwin": | ||||||
|   | |||||||
							
								
								
									
										73
									
								
								src/cmd/moby/run_gcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/cmd/moby/run_gcp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	log "github.com/Sirupsen/logrus" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Process the run arguments and execute run | ||||||
|  | func runGcp(args []string) { | ||||||
|  | 	gcpCmd := flag.NewFlagSet("gcp", flag.ExitOnError) | ||||||
|  | 	gcpCmd.Usage = func() { | ||||||
|  | 		fmt.Printf("USAGE: %s run gcp [options] [name]\n\n", os.Args[0]) | ||||||
|  | 		fmt.Printf("'name' specifies either the name of an already uploaded\n") | ||||||
|  | 		fmt.Printf("GCE image or the full path to a image file which will be\n") | ||||||
|  | 		fmt.Printf("uploaded before it is run.\n\n") | ||||||
|  | 		fmt.Printf("Options:\n\n") | ||||||
|  | 		gcpCmd.PrintDefaults() | ||||||
|  | 	} | ||||||
|  | 	zone := gcpCmd.String("zone", "europe-west1-d", "GCP Zone") | ||||||
|  | 	machine := gcpCmd.String("machine", "g1-small", "GCE Machine Type") | ||||||
|  | 	keys := gcpCmd.String("keys", "", "Path to Service Account JSON key file") | ||||||
|  | 	project := gcpCmd.String("project", "", "GCP Project Name") | ||||||
|  | 	bucket := gcpCmd.String("bucket", "", "GS Bucket to upload to. *Required* when 'prefix' is a filename") | ||||||
|  | 	public := gcpCmd.Bool("public", false, "Select if file on GS should be public. *Optional* when 'prefix' is a filename") | ||||||
|  | 	family := gcpCmd.String("family", "", "GCE Image Family. A group of images where the family name points to the most recent image. *Optional* when 'prefix' is a filename") | ||||||
|  |  | ||||||
|  | 	gcpCmd.Parse(args) | ||||||
|  | 	remArgs := gcpCmd.Args() | ||||||
|  | 	if len(remArgs) == 0 { | ||||||
|  | 		fmt.Printf("Please specify the prefix to the image to boot\n") | ||||||
|  | 		gcpCmd.Usage() | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | 	prefix := remArgs[0] | ||||||
|  |  | ||||||
|  | 	client, err := NewGCPClient(*keys, *project) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Unable to connect to GCP") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	suffix := ".img.tar.gz" | ||||||
|  | 	if strings.HasSuffix(prefix, suffix) { | ||||||
|  | 		filename := prefix | ||||||
|  | 		prefix = prefix[:len(prefix)-len(suffix)] | ||||||
|  | 		if *bucket == "" { | ||||||
|  | 			log.Fatalf("No bucket specified. Please provide one using the -bucket flag") | ||||||
|  | 		} | ||||||
|  | 		err = client.UploadFile(filename, *bucket, *public) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("Error copying to Google Storage: %v", err) | ||||||
|  | 		} | ||||||
|  | 		err = client.CreateImage(prefix, "https://storage.googleapis.com/"+*bucket+"/"+prefix+".img.tar.gz", *family, true) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("Error creating Google Compute Image: %v", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = client.CreateInstance(prefix, *zone, *machine, true); err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = client.ConnectToInstanceSerialPort(prefix, *zone); err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = client.DeleteInstance(prefix, *zone, true); err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -30,3 +30,4 @@ outputs: | |||||||
|   - format: kernel+initrd |   - format: kernel+initrd | ||||||
|   - format: iso-bios |   - format: iso-bios | ||||||
|   - format: iso-efi |   - format: iso-efi | ||||||
|  |   - format: gce-img | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user