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 }