From a226edd4a9a722f8d91ad68ebfcc2584d0a14774 Mon Sep 17 00:00:00 2001 From: Frank Schroeder Date: Wed, 3 May 2017 22:48:22 +0200 Subject: [PATCH] Move GCE discovery code to command/agent/config_gce.go --- command/agent/command.go | 151 ---------------------------------- command/agent/config_gce.go | 159 ++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 151 deletions(-) create mode 100644 command/agent/config_gce.go diff --git a/command/agent/command.go b/command/agent/command.go index 2b0d4effc9..3b0f7617fa 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -1,13 +1,9 @@ package agent import ( - "context" "fmt" "io" - "io/ioutil" - "log" "net" - "net/http" "os" "os/signal" "path/filepath" @@ -30,9 +26,6 @@ import ( "github.com/hashicorp/logutils" "github.com/hashicorp/scada-client/scada" "github.com/mitchellh/cli" - "golang.org/x/oauth2" - "golang.org/x/oauth2/google" - compute "google.golang.org/api/compute/v1" ) // gracefulTimeout controls how long we wait before forcefully terminating @@ -436,150 +429,6 @@ func (c *Command) readConfig() *Config { return config } -// of instance ips that match the tags given in GCETags. -func (c *Config) discoverGCEHosts(logger *log.Logger) ([]string, error) { - config := c.RetryJoinGCE - ctx := oauth2.NoContext - var client *http.Client - var err error - - logger.Printf("[INFO] agent: Initializing GCE client") - if config.CredentialsFile != "" { - logger.Printf("[INFO] agent: Loading credentials from %s", config.CredentialsFile) - key, err := ioutil.ReadFile(config.CredentialsFile) - if err != nil { - return nil, err - } - jwtConfig, err := google.JWTConfigFromJSON(key, compute.ComputeScope) - if err != nil { - return nil, err - } - client = jwtConfig.Client(ctx) - } else { - logger.Printf("[INFO] agent: Using default credential chain") - client, err = google.DefaultClient(ctx, compute.ComputeScope) - if err != nil { - return nil, err - } - } - - computeService, err := compute.New(client) - if err != nil { - return nil, err - } - - if config.ProjectName == "" { - logger.Printf("[INFO] agent: No GCE project provided, will discover from metadata.") - config.ProjectName, err = gceProjectIDFromMetadata(logger) - if err != nil { - return nil, err - } - } else { - logger.Printf("[INFO] agent: Using pre-defined GCE project name: %s", config.ProjectName) - } - - zones, err := gceDiscoverZones(ctx, logger, computeService, config.ProjectName, config.ZonePattern) - if err != nil { - return nil, err - } - - logger.Printf("[INFO] agent: Discovering GCE hosts with tag %s in zones: %s", config.TagValue, strings.Join(zones, ", ")) - - var servers []string - for _, zone := range zones { - addresses, err := gceInstancesAddressesForZone(ctx, logger, computeService, config.ProjectName, zone, config.TagValue) - if err != nil { - return nil, err - } - if len(addresses) > 0 { - logger.Printf("[INFO] agent: Discovered %d instances in %s/%s: %v", len(addresses), config.ProjectName, zone, addresses) - } - servers = append(servers, addresses...) - } - - return servers, nil -} - -// gceProjectIDFromMetadata queries the metadata service on GCE to get the -// project ID (name) of an instance. -func gceProjectIDFromMetadata(logger *log.Logger) (string, error) { - logger.Printf("[INFO] agent: Attempting to discover GCE project from metadata.") - client := &http.Client{} - - 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 := client.Do(req) - if err != nil { - return "", err - } - - defer resp.Body.Close() - - project, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - - logger.Printf("[INFO] agent: GCE project discovered as: %s", project) - return string(project), nil -} - -// gceDiscoverZones discovers a list of zones from a supplied zone pattern, or -// all of the zones available to a project. -func gceDiscoverZones(ctx context.Context, logger *log.Logger, computeService *compute.Service, project, pattern string) ([]string, error) { - var zones []string - - if pattern != "" { - logger.Printf("[INFO] agent: Discovering zones for project %s matching pattern: %s", project, pattern) - } else { - logger.Printf("[INFO] agent: Discovering all zones available to project: %s", project) - } - - call := computeService.Zones.List(project) - if pattern != "" { - call = call.Filter(fmt.Sprintf("name eq %s", pattern)) - } - - if err := call.Pages(ctx, func(page *compute.ZoneList) error { - for _, v := range page.Items { - zones = append(zones, v.Name) - } - return nil - }); err != nil { - return zones, err - } - - logger.Printf("[INFO] agent: Discovered GCE zones: %s", strings.Join(zones, ", ")) - return zones, nil -} - -// gceInstancesAddressesForZone locates all instances within a specific project -// and zone, matching the supplied tag. Only the private IP addresses are -// returned, but ID is also logged. -func gceInstancesAddressesForZone(ctx context.Context, logger *log.Logger, computeService *compute.Service, project, zone, tag string) ([]string, error) { - var addresses []string - call := computeService.Instances.List(project, zone) - if err := call.Pages(ctx, func(page *compute.InstanceList) error { - for _, v := range page.Items { - for _, t := range v.Tags.Items { - if t == tag && len(v.NetworkInterfaces) > 0 && v.NetworkInterfaces[0].NetworkIP != "" { - addresses = append(addresses, v.NetworkInterfaces[0].NetworkIP) - } - } - } - return nil - }); err != nil { - return addresses, err - } - - return addresses, nil -} - // setupAgent is used to start the agent and various interfaces func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *logger.LogWriter) error { c.UI.Output("Starting Consul agent...") diff --git a/command/agent/config_gce.go b/command/agent/config_gce.go new file mode 100644 index 0000000000..16622048dc --- /dev/null +++ b/command/agent/config_gce.go @@ -0,0 +1,159 @@ +package agent + +import ( + "context" + "fmt" + "io/ioutil" + "log" + "net/http" + "strings" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + compute "google.golang.org/api/compute/v1" +) + +// discoverGCEHosts searches a Google Compute Engine region, returning a list +// of instance ips that match the tags given in GCETags. +func (c *Config) discoverGCEHosts(logger *log.Logger) ([]string, error) { + config := c.RetryJoinGCE + ctx := oauth2.NoContext + var client *http.Client + var err error + + logger.Printf("[INFO] agent: Initializing GCE client") + if config.CredentialsFile != "" { + logger.Printf("[INFO] agent: Loading credentials from %s", config.CredentialsFile) + key, err := ioutil.ReadFile(config.CredentialsFile) + if err != nil { + return nil, err + } + jwtConfig, err := google.JWTConfigFromJSON(key, compute.ComputeScope) + if err != nil { + return nil, err + } + client = jwtConfig.Client(ctx) + } else { + logger.Printf("[INFO] agent: Using default credential chain") + client, err = google.DefaultClient(ctx, compute.ComputeScope) + if err != nil { + return nil, err + } + } + + computeService, err := compute.New(client) + if err != nil { + return nil, err + } + + if config.ProjectName == "" { + logger.Printf("[INFO] agent: No GCE project provided, will discover from metadata.") + config.ProjectName, err = gceProjectIDFromMetadata(logger) + if err != nil { + return nil, err + } + } else { + logger.Printf("[INFO] agent: Using pre-defined GCE project name: %s", config.ProjectName) + } + + zones, err := gceDiscoverZones(ctx, logger, computeService, config.ProjectName, config.ZonePattern) + if err != nil { + return nil, err + } + + logger.Printf("[INFO] agent: Discovering GCE hosts with tag %s in zones: %s", config.TagValue, strings.Join(zones, ", ")) + + var servers []string + for _, zone := range zones { + addresses, err := gceInstancesAddressesForZone(ctx, logger, computeService, config.ProjectName, zone, config.TagValue) + if err != nil { + return nil, err + } + if len(addresses) > 0 { + logger.Printf("[INFO] agent: Discovered %d instances in %s/%s: %v", len(addresses), config.ProjectName, zone, addresses) + } + servers = append(servers, addresses...) + } + + return servers, nil +} + +// gceProjectIDFromMetadata queries the metadata service on GCE to get the +// project ID (name) of an instance. +func gceProjectIDFromMetadata(logger *log.Logger) (string, error) { + logger.Printf("[INFO] agent: Attempting to discover GCE project from metadata.") + client := &http.Client{} + + 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 := client.Do(req) + if err != nil { + return "", err + } + + defer resp.Body.Close() + + project, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + logger.Printf("[INFO] agent: GCE project discovered as: %s", project) + return string(project), nil +} + +// gceDiscoverZones discovers a list of zones from a supplied zone pattern, or +// all of the zones available to a project. +func gceDiscoverZones(ctx context.Context, logger *log.Logger, computeService *compute.Service, project, pattern string) ([]string, error) { + var zones []string + + if pattern != "" { + logger.Printf("[INFO] agent: Discovering zones for project %s matching pattern: %s", project, pattern) + } else { + logger.Printf("[INFO] agent: Discovering all zones available to project: %s", project) + } + + call := computeService.Zones.List(project) + if pattern != "" { + call = call.Filter(fmt.Sprintf("name eq %s", pattern)) + } + + if err := call.Pages(ctx, func(page *compute.ZoneList) error { + for _, v := range page.Items { + zones = append(zones, v.Name) + } + return nil + }); err != nil { + return zones, err + } + + logger.Printf("[INFO] agent: Discovered GCE zones: %s", strings.Join(zones, ", ")) + return zones, nil +} + +// gceInstancesAddressesForZone locates all instances within a specific project +// and zone, matching the supplied tag. Only the private IP addresses are +// returned, but ID is also logged. +func gceInstancesAddressesForZone(ctx context.Context, logger *log.Logger, computeService *compute.Service, project, zone, tag string) ([]string, error) { + var addresses []string + call := computeService.Instances.List(project, zone) + if err := call.Pages(ctx, func(page *compute.InstanceList) error { + for _, v := range page.Items { + for _, t := range v.Tags.Items { + if t == tag && len(v.NetworkInterfaces) > 0 && v.NetworkInterfaces[0].NetworkIP != "" { + addresses = append(addresses, v.NetworkInterfaces[0].NetworkIP) + } + } + } + return nil + }); err != nil { + return addresses, err + } + + return addresses, nil +}