1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
// Package gce provides node discovery for Google Cloud.
package gce
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
compute "google.golang.org/api/compute/v1"
)
type Provider struct {
userAgent string
}
func (p *Provider) SetUserAgent(s string) {
p.userAgent = s
}
func (p *Provider) Help() string {
return `Google Cloud:
provider: "gce"
project_name: The name of the project. discovered if not set
tag_value: The tag value for filtering instances
zone_pattern: A RE2 regular expression for filtering zones, e.g. us-west1-.*, or us-(?west|east).*
credentials_file: The path to the credentials file. See below for more details
The credentials for a GCE Service Account are required and are searched in
the following locations:
1. Use credentials from "credentials_file", if provided.
2. Use JSON file from GOOGLE_APPLICATION_CREDENTIALS environment variable.
3. Use JSON file in a location known to the gcloud command-line tool.
On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
On other systems, $HOME/.config/gcloud/application_default_credentials.json.
4. On Google Compute Engine, use credentials from the metadata
server. In this final case any provided scopes are ignored.
`
}
func (p *Provider) Addrs(args map[string]string, l *log.Logger) ([]string, error) {
if args["provider"] != "gce" {
return nil, fmt.Errorf("discover-gce: invalid provider " + args["provider"])
}
if l == nil {
l = log.New(ioutil.Discard, "", 0)
}
project := args["project_name"]
zone := args["zone_pattern"]
creds := args["credentials_file"]
tagValue := args["tag_value"]
// determine the project name
if project == "" {
l.Println("[INFO] discover-gce: Looking up project name")
p, err := lookupProject()
if err != nil {
return nil, fmt.Errorf("discover-gce: %s", err)
}
project = p
}
l.Printf("[INFO] discover-gce: Project name is %q", project)
// create an authenticated client
if creds != "" {
l.Printf("[INFO] discover-gce: Loading credentials from %s", creds)
}
client, err := client(creds)
if err != nil {
return nil, fmt.Errorf("discover-gce: %s", err)
}
svc, err := compute.New(client)
if err != nil {
return nil, fmt.Errorf("discover-gce: %s", err)
}
if p.userAgent != "" {
svc.UserAgent = p.userAgent
}
// lookup the project zones to look in
if zone != "" {
l.Printf("[INFO] discover-gce: Looking up zones matching %s", zone)
} else {
l.Printf("[INFO] discover-gce: Looking up all zones")
}
zones, err := lookupZones(svc, project, zone)
if err != nil {
return nil, fmt.Errorf("discover-gce: %s", err)
}
l.Printf("[INFO] discover-gce: Found zones %v", zones)
// lookup the instance addresses
var addrs []string
for _, zone := range zones {
a, err := lookupAddrs(svc, project, zone, tagValue)
if err != nil {
return nil, fmt.Errorf("discover-gce: %s", err)
}
l.Printf("[INFO] discover-gce: Zone %q has %v", zone, a)
addrs = append(addrs, a...)
}
return addrs, nil
}
// client returns an authenticated HTTP client for use with GCE.
func client(path string) (*http.Client, error) {
if path == "" {
return google.DefaultClient(oauth2.NoContext, compute.ComputeScope)
}
key, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
jwtConfig, err := google.JWTConfigFromJSON(key, compute.ComputeScope)
if err != nil {
return nil, err
}
return jwtConfig.Client(oauth2.NoContext), nil
}
// lookupProject retrieves the project name from the metadata of the current node.
func lookupProject() (string, error) {
req, err := http.NewRequest("GET", "http://metadata.google.internal/computeMetadata/v1/project/project-id", nil)
if err != nil {
return "", err
}
req.Header.Add("Metadata-Flavor", "Google")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("discover-gce: invalid status code %d when fetching project id", resp.StatusCode)
}
project, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(project), nil
}
// lookupZones retrieves the zones of the project and filters them by pattern.
func lookupZones(svc *compute.Service, project, pattern string) ([]string, error) {
call := svc.Zones.List(project)
if pattern != "" {
call = call.Filter("name eq " + pattern)
}
var zones []string
f := func(page *compute.ZoneList) error {
for _, v := range page.Items {
zones = append(zones, v.Name)
}
return nil
}
if err := call.Pages(oauth2.NoContext, f); err != nil {
return nil, err
}
return zones, nil
}
// lookupAddrs retrieves the private ip addresses of all instances in a given
// project and zone which have a matching tag value.
func lookupAddrs(svc *compute.Service, project, zone, tag string) ([]string, error) {
var addrs []string
f := func(page *compute.InstanceList) error {
for _, v := range page.Items {
if len(v.NetworkInterfaces) == 0 || v.NetworkInterfaces[0].NetworkIP == "" {
continue
}
for _, t := range v.Tags.Items {
if t == tag {
addrs = append(addrs, v.NetworkInterfaces[0].NetworkIP)
break
}
}
}
return nil
}
call := svc.Instances.List(project, zone)
if err := call.Pages(oauth2.NoContext, f); err != nil {
return nil, err
}
return addrs, nil
}
|