github.com/coreos/mantle@v0.13.0/platform/api/gcloud/compute.go (about)

     1  // Copyright 2016 CoreOS, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gcloud
    16  
    17  import (
    18  	"crypto/rand"
    19  	"fmt"
    20  	"strings"
    21  	"time"
    22  
    23  	"golang.org/x/crypto/ssh/agent"
    24  	"google.golang.org/api/compute/v1"
    25  )
    26  
    27  func (a *API) vmname() string {
    28  	b := make([]byte, 10)
    29  	rand.Read(b)
    30  	return fmt.Sprintf("%s-%x", a.options.BaseName, b)
    31  }
    32  
    33  // Taken from: https://github.com/golang/build/blob/master/buildlet/gce.go
    34  func (a *API) mkinstance(userdata, name string, keys []*agent.Key) *compute.Instance {
    35  	mantle := "mantle"
    36  	metadataItems := []*compute.MetadataItems{
    37  		&compute.MetadataItems{
    38  			// this should be done with a label instead, but
    39  			// our old vendored Go binding doesn't support those
    40  			Key:   "created-by",
    41  			Value: &mantle,
    42  		},
    43  	}
    44  	if len(keys) > 0 {
    45  		var sshKeys string
    46  		for i, key := range keys {
    47  			sshKeys += fmt.Sprintf("%d:%s\n", i, key)
    48  		}
    49  
    50  		metadataItems = append(metadataItems, &compute.MetadataItems{
    51  			Key:   "ssh-keys",
    52  			Value: &sshKeys,
    53  		})
    54  	}
    55  
    56  	instancePrefix := "https://www.googleapis.com/compute/v1/projects/" + a.options.Project
    57  
    58  	instance := &compute.Instance{
    59  		Name:        name,
    60  		MachineType: instancePrefix + "/zones/" + a.options.Zone + "/machineTypes/" + a.options.MachineType,
    61  		Metadata: &compute.Metadata{
    62  			Items: metadataItems,
    63  		},
    64  		Tags: &compute.Tags{
    65  			// Apparently you need this tag in addition to the
    66  			// firewall rules to open the port because these ports
    67  			// are special?
    68  			Items: []string{"https-server", "http-server"},
    69  		},
    70  		Disks: []*compute.AttachedDisk{
    71  			{
    72  				AutoDelete: true,
    73  				Boot:       true,
    74  				Type:       "PERSISTENT",
    75  				InitializeParams: &compute.AttachedDiskInitializeParams{
    76  					DiskName:    name,
    77  					SourceImage: a.options.Image,
    78  					DiskType:    "/zones/" + a.options.Zone + "/diskTypes/" + a.options.DiskType,
    79  					DiskSizeGb:  12,
    80  				},
    81  			},
    82  		},
    83  		NetworkInterfaces: []*compute.NetworkInterface{
    84  			&compute.NetworkInterface{
    85  				AccessConfigs: []*compute.AccessConfig{
    86  					&compute.AccessConfig{
    87  						Type: "ONE_TO_ONE_NAT",
    88  						Name: "External NAT",
    89  					},
    90  				},
    91  				Network: instancePrefix + "/global/networks/" + a.options.Network,
    92  			},
    93  		},
    94  	}
    95  	// add cloud config
    96  	if userdata != "" {
    97  		instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{
    98  			Key:   "user-data",
    99  			Value: &userdata,
   100  		})
   101  	}
   102  
   103  	return instance
   104  
   105  }
   106  
   107  // CreateInstance creates a Google Compute Engine instance.
   108  func (a *API) CreateInstance(userdata string, keys []*agent.Key) (*compute.Instance, error) {
   109  	name := a.vmname()
   110  	inst := a.mkinstance(userdata, name, keys)
   111  
   112  	plog.Debugf("Creating instance %q", name)
   113  
   114  	op, err := a.compute.Instances.Insert(a.options.Project, a.options.Zone, inst).Do()
   115  	if err != nil {
   116  		return nil, fmt.Errorf("failed to request new GCE instance: %v\n", err)
   117  	}
   118  
   119  	doable := a.compute.ZoneOperations.Get(a.options.Project, a.options.Zone, op.Name)
   120  	if err := a.NewPending(op.Name, doable).Wait(); err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	inst, err = a.compute.Instances.Get(a.options.Project, a.options.Zone, name).Do()
   125  	if err != nil {
   126  		return nil, fmt.Errorf("failed getting instance %s details after creation: %v", name, err)
   127  	}
   128  
   129  	plog.Debugf("Created instance %q", name)
   130  
   131  	return inst, nil
   132  }
   133  
   134  func (a *API) TerminateInstance(name string) error {
   135  	plog.Debugf("Terminating instance %q", name)
   136  
   137  	_, err := a.compute.Instances.Delete(a.options.Project, a.options.Zone, name).Do()
   138  	return err
   139  }
   140  
   141  func (a *API) ListInstances(prefix string) ([]*compute.Instance, error) {
   142  	var instances []*compute.Instance
   143  
   144  	list, err := a.compute.Instances.List(a.options.Project, a.options.Zone).Do()
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	for _, inst := range list.Items {
   150  		if !strings.HasPrefix(inst.Name, prefix) {
   151  			continue
   152  		}
   153  
   154  		instances = append(instances, inst)
   155  	}
   156  
   157  	return instances, nil
   158  }
   159  
   160  func (a *API) GetConsoleOutput(name string) (string, error) {
   161  	out, err := a.compute.Instances.GetSerialPortOutput(a.options.Project, a.options.Zone, name).Do()
   162  	if err != nil {
   163  		return "", fmt.Errorf("failed to retrieve console output for %q: %v", name, err)
   164  	}
   165  	return out.Contents, nil
   166  }
   167  
   168  // Taken from: https://github.com/golang/build/blob/master/buildlet/gce.go
   169  func InstanceIPs(inst *compute.Instance) (intIP, extIP string) {
   170  	for _, iface := range inst.NetworkInterfaces {
   171  		if strings.HasPrefix(iface.NetworkIP, "10.") {
   172  			intIP = iface.NetworkIP
   173  		}
   174  		for _, accessConfig := range iface.AccessConfigs {
   175  			if accessConfig.Type == "ONE_TO_ONE_NAT" {
   176  				extIP = accessConfig.NatIP
   177  			}
   178  		}
   179  	}
   180  	return
   181  }
   182  
   183  func (a *API) gcInstances(gracePeriod time.Duration) error {
   184  	threshold := time.Now().Add(-gracePeriod)
   185  
   186  	list, err := a.compute.Instances.List(a.options.Project, a.options.Zone).Do()
   187  	if err != nil {
   188  		return err
   189  	}
   190  	for _, instance := range list.Items {
   191  		// check metadata because our vendored Go binding
   192  		// doesn't support labels
   193  		if instance.Metadata == nil {
   194  			continue
   195  		}
   196  		isMantle := false
   197  		for _, item := range instance.Metadata.Items {
   198  			if item.Key == "created-by" && item.Value != nil && *item.Value == "mantle" {
   199  				isMantle = true
   200  				break
   201  			}
   202  		}
   203  		if !isMantle {
   204  			continue
   205  		}
   206  
   207  		created, err := time.Parse(time.RFC3339, instance.CreationTimestamp)
   208  		if err != nil {
   209  			return fmt.Errorf("couldn't parse %q: %v", instance.CreationTimestamp, err)
   210  		}
   211  		if created.After(threshold) {
   212  			continue
   213  		}
   214  
   215  		switch instance.Status {
   216  		case "TERMINATED":
   217  			continue
   218  		}
   219  
   220  		if err := a.TerminateInstance(instance.Name); err != nil {
   221  			return fmt.Errorf("couldn't terminate instance %q: %v", instance.Name, err)
   222  		}
   223  	}
   224  
   225  	return nil
   226  }