mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-20 09:39:08 +00:00
build: Use older GCP API and support service account auth
This commit uses the older GCP API as it supports both compute and storage. As a result, we can now use either Application Default Credentials that are generated using the `gcloud` tool or by supplying the service account credentials in JSON format Signed-off-by: Dave Tucker <dt@docker.com>
This commit is contained in:
parent
d50cc4dbeb
commit
d5a8e23cdd
@ -33,6 +33,7 @@ type Moby struct {
|
|||||||
Project string
|
Project string
|
||||||
Bucket string
|
Bucket string
|
||||||
Family string
|
Family string
|
||||||
|
Keys string
|
||||||
Public bool
|
Public bool
|
||||||
Replace bool
|
Replace bool
|
||||||
}
|
}
|
||||||
|
@ -1,100 +1,170 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"time"
|
||||||
|
|
||||||
"cloud.google.com/go/storage"
|
log "github.com/Sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
"google.golang.org/api/compute/v1"
|
||||||
|
"google.golang.org/api/googleapi"
|
||||||
|
"google.golang.org/api/storage/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func uploadGS(filename, project, bucket string, public bool) error {
|
// GCPClient contains state required for communication with GCP
|
||||||
if project != "" {
|
type GCPClient struct {
|
||||||
err := os.Setenv("GOOGLE_CLOUD_PROJECT", project)
|
client *http.Client
|
||||||
if err != nil {
|
compute *compute.Service
|
||||||
return err
|
storage *storage.Service
|
||||||
}
|
projectName string
|
||||||
}
|
fileName string
|
||||||
if os.Getenv("GOOGLE_CLOUD_PROJECT") == "" {
|
}
|
||||||
return errors.New("GOOGLE_CLOUD_PROJECT environment variable must be set or project specified in config")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// NewGCPClient creates a new GCP client
|
||||||
|
func NewGCPClient(keys, projectName string) (*GCPClient, error) {
|
||||||
|
log.Debugf("Connecting to GCP")
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client, err := storage.NewClient(ctx)
|
var client *GCPClient
|
||||||
|
if keys != "" {
|
||||||
|
log.Debugf("Using Keys %s", keys)
|
||||||
|
f, err := os.Open(keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsonKey, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := google.JWTConfigFromJSON(jsonKey,
|
||||||
|
storage.DevstorageReadWriteScope,
|
||||||
|
compute.ComputeScope,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client = &GCPClient{
|
||||||
|
client: config.Client(ctx),
|
||||||
|
projectName: projectName,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Debugf("Using Application Default crednetials")
|
||||||
|
gc, err := google.DefaultClient(
|
||||||
|
ctx,
|
||||||
|
storage.DevstorageReadWriteScope,
|
||||||
|
compute.ComputeScope,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client = &GCPClient{
|
||||||
|
client: gc,
|
||||||
|
projectName: projectName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
client.compute, err = compute.New(client.client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.storage, err = storage.New(client.client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadFile uploads a file to Google Storage
|
||||||
|
func (g GCPClient) UploadFile(filename, bucketName string, public bool) error {
|
||||||
|
log.Infof("Uploading file %s to Google Storage", filename)
|
||||||
f, err := os.Open(filename)
|
f, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
obj := client.Bucket(bucket).Object(filename)
|
objectCall := g.storage.Objects.Insert(bucketName, &storage.Object{Name: filename}).Media(f)
|
||||||
wc := obj.NewWriter(ctx)
|
|
||||||
_, err = io.Copy(wc, f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = wc.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if public {
|
if public {
|
||||||
err = obj.ACL().Set(ctx, storage.AllUsers, storage.RoleReader)
|
objectCall.PredefinedAcl("publicRead")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = objectCall.Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
log.Infof("Upload Complete!")
|
||||||
|
fmt.Println("gs://" + bucketName + "/" + filename)
|
||||||
fmt.Println("gs://" + bucket + "/" + filename)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func imageGS(filename, project, storage, family string, replace bool) error {
|
// CreateImage creates a GCE image using the a source from Google Storage
|
||||||
if project != "" {
|
func (g GCPClient) CreateImage(filename, storageURL, family string, replace bool) error {
|
||||||
err := os.Setenv("GOOGLE_CLOUD_PROJECT", project)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if os.Getenv("GOOGLE_CLOUD_PROJECT") == "" {
|
|
||||||
return errors.New("GOOGLE_CLOUD_PROJECT environment variable must be set or project specified in config")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO do not shell out to gcloud tool, use the API
|
|
||||||
|
|
||||||
gcloud, err := exec.LookPath("gcloud")
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("Please install the gcloud binary")
|
|
||||||
}
|
|
||||||
|
|
||||||
if replace {
|
if replace {
|
||||||
args := []string{"compute", "images", "delete", filename}
|
var notFound bool
|
||||||
cmd := exec.Command(gcloud, args...)
|
op, err := g.compute.Images.Delete(g.projectName, filename).Do()
|
||||||
// ignore failures; it may not exist
|
|
||||||
_ = cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{"compute", "images", "create", "--source-uri", storage}
|
|
||||||
if family != "" {
|
|
||||||
args = append(args, "--family", family)
|
|
||||||
}
|
|
||||||
args = append(args, filename)
|
|
||||||
cmd := exec.Command(gcloud, args...)
|
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Image creation failed: %v - %s", err, string(out))
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(filename)
|
log.Infof("Creating image: %s", filename)
|
||||||
|
imgObj := &compute.Image{
|
||||||
|
RawDisk: &compute.ImageRawDisk{
|
||||||
|
Source: storageURL,
|
||||||
|
},
|
||||||
|
Name: filename,
|
||||||
|
}
|
||||||
|
|
||||||
|
if family != "" {
|
||||||
|
imgObj.Family = family
|
||||||
|
}
|
||||||
|
|
||||||
|
op, err := g.compute.Images.Insert(g.projectName, imgObj).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.pollOperationStatus(op.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("Image %s created", filename)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GCPClient) pollOperationStatus(operationName string) error {
|
||||||
|
for i := 0; i < timeout; i++ {
|
||||||
|
operation, err := g.compute.GlobalOperations.Get(g.projectName, 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")
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -51,7 +51,11 @@ func outputs(m *Moby, base string, bzimage []byte, initrd []byte) error {
|
|||||||
if o.Bucket == "" {
|
if o.Bucket == "" {
|
||||||
return fmt.Errorf("No bucket specified for GCE output")
|
return fmt.Errorf("No bucket specified for GCE output")
|
||||||
}
|
}
|
||||||
err = uploadGS(base+".img.tar.gz", o.Project, o.Bucket, o.Public)
|
gClient, err := NewGCPClient(o.Keys, o.Project)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to connect to GCP")
|
||||||
|
}
|
||||||
|
err = gClient.UploadFile(base+".img.tar.gz", o.Bucket, o.Public)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error copying to Google Storage: %v", err)
|
return fmt.Errorf("Error copying to Google Storage: %v", err)
|
||||||
}
|
}
|
||||||
@ -63,11 +67,15 @@ func outputs(m *Moby, base string, bzimage []byte, initrd []byte) error {
|
|||||||
if o.Bucket == "" {
|
if o.Bucket == "" {
|
||||||
return fmt.Errorf("No bucket specified for GCE output")
|
return fmt.Errorf("No bucket specified for GCE output")
|
||||||
}
|
}
|
||||||
err = uploadGS(base+".img.tar.gz", o.Project, o.Bucket, o.Public)
|
gClient, err := NewGCPClient(o.Keys, o.Project)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to connect to GCP")
|
||||||
|
}
|
||||||
|
err = gClient.UploadFile(base+".img.tar.gz", o.Bucket, o.Public)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error copying to Google Storage: %v", err)
|
return fmt.Errorf("Error copying to Google Storage: %v", err)
|
||||||
}
|
}
|
||||||
err = imageGS(base, o.Project, "https://storage.googleapis.com/"+o.Bucket+"/"+base+".img.tar.gz", o.Family, o.Replace)
|
err = gClient.CreateImage(base, "https://storage.googleapis.com/"+o.Bucket+"/"+base+".img.tar.gz", o.Family, o.Replace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error creating Google Compute Image: %v", err)
|
return fmt.Errorf("Error creating Google Compute Image: %v", err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user