agent: use github.com/hashicorp/go-discover
Replace the provider specific node discovery code with go-discover to support AWS, Azure and GCE. Fixes #3282
This commit is contained in:
parent
5ddcdd41c2
commit
68e8f3d0f7
135
agent/config.go
135
agent/config.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -21,6 +22,7 @@ import (
|
||||||
"github.com/hashicorp/consul/tlsutil"
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
"github.com/hashicorp/consul/types"
|
"github.com/hashicorp/consul/types"
|
||||||
"github.com/hashicorp/consul/watch"
|
"github.com/hashicorp/consul/watch"
|
||||||
|
discover "github.com/hashicorp/go-discover"
|
||||||
"github.com/hashicorp/go-sockaddr/template"
|
"github.com/hashicorp/go-sockaddr/template"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
@ -562,7 +564,7 @@ type Config struct {
|
||||||
StartJoinWan []string `mapstructure:"start_join_wan"`
|
StartJoinWan []string `mapstructure:"start_join_wan"`
|
||||||
|
|
||||||
// RetryJoin is a list of addresses to join with retry enabled.
|
// RetryJoin is a list of addresses to join with retry enabled.
|
||||||
RetryJoin []string `mapstructure:"retry_join"`
|
RetryJoin []string `mapstructure:"retry_join" json:"-"`
|
||||||
|
|
||||||
// RetryMaxAttempts specifies the maximum number of times to retry joining a
|
// RetryMaxAttempts specifies the maximum number of times to retry joining a
|
||||||
// host on startup. This is useful for cases where we know the node will be
|
// host on startup. This is useful for cases where we know the node will be
|
||||||
|
@ -575,15 +577,6 @@ type Config struct {
|
||||||
RetryInterval time.Duration `mapstructure:"-" json:"-"`
|
RetryInterval time.Duration `mapstructure:"-" json:"-"`
|
||||||
RetryIntervalRaw string `mapstructure:"retry_interval"`
|
RetryIntervalRaw string `mapstructure:"retry_interval"`
|
||||||
|
|
||||||
// RetryJoinEC2 specifies the configuration for auto-join on EC2.
|
|
||||||
RetryJoinEC2 RetryJoinEC2 `mapstructure:"retry_join_ec2"`
|
|
||||||
|
|
||||||
// RetryJoinGCE specifies the configuration for auto-join on GCE.
|
|
||||||
RetryJoinGCE RetryJoinGCE `mapstructure:"retry_join_gce"`
|
|
||||||
|
|
||||||
// RetryJoinAzure specifies the configuration for auto-join on Azure.
|
|
||||||
RetryJoinAzure RetryJoinAzure `mapstructure:"retry_join_azure"`
|
|
||||||
|
|
||||||
// RetryJoinWan is a list of addresses to join -wan with retry enabled.
|
// RetryJoinWan is a list of addresses to join -wan with retry enabled.
|
||||||
RetryJoinWan []string `mapstructure:"retry_join_wan"`
|
RetryJoinWan []string `mapstructure:"retry_join_wan"`
|
||||||
|
|
||||||
|
@ -786,6 +779,9 @@ type Config struct {
|
||||||
DeprecatedAtlasJoin bool `mapstructure:"atlas_join" json:"-"`
|
DeprecatedAtlasJoin bool `mapstructure:"atlas_join" json:"-"`
|
||||||
DeprecatedAtlasEndpoint string `mapstructure:"atlas_endpoint" json:"-"`
|
DeprecatedAtlasEndpoint string `mapstructure:"atlas_endpoint" json:"-"`
|
||||||
DeprecatedHTTPAPIResponseHeaders map[string]string `mapstructure:"http_api_response_headers"`
|
DeprecatedHTTPAPIResponseHeaders map[string]string `mapstructure:"http_api_response_headers"`
|
||||||
|
DeprecatedRetryJoinEC2 RetryJoinEC2 `mapstructure:"retry_join_ec2"`
|
||||||
|
DeprecatedRetryJoinGCE RetryJoinGCE `mapstructure:"retry_join_gce"`
|
||||||
|
DeprecatedRetryJoinAzure RetryJoinAzure `mapstructure:"retry_join_azure"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncomingHTTPSConfig returns the TLS configuration for HTTPS
|
// IncomingHTTPSConfig returns the TLS configuration for HTTPS
|
||||||
|
@ -1185,6 +1181,65 @@ func DecodeConfig(r io.Reader) (*Config, error) {
|
||||||
"is no longer used. Please remove it from your configuration.")
|
"is no longer used. Please remove it from your configuration.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(result.DeprecatedRetryJoinEC2, RetryJoinEC2{}) {
|
||||||
|
m := discover.Config{
|
||||||
|
"provider": "aws",
|
||||||
|
"region": result.DeprecatedRetryJoinEC2.Region,
|
||||||
|
"tag_key": result.DeprecatedRetryJoinEC2.TagKey,
|
||||||
|
"tag_value": result.DeprecatedRetryJoinEC2.TagValue,
|
||||||
|
"access_key_id": result.DeprecatedRetryJoinEC2.AccessKeyID,
|
||||||
|
"secret_access_key": result.DeprecatedRetryJoinEC2.SecretAccessKey,
|
||||||
|
}
|
||||||
|
result.RetryJoin = append(result.RetryJoin, m.String())
|
||||||
|
result.DeprecatedRetryJoinEC2 = RetryJoinEC2{}
|
||||||
|
|
||||||
|
// redact m before output
|
||||||
|
m["access_key_id"] = "<hidden>"
|
||||||
|
m["secret_access_key"] = "<hidden>"
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "==> DEPRECATION: retry_join_ec2 is deprecated."+
|
||||||
|
"Please add %q to retry_join\n", m)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result.DeprecatedRetryJoinAzure, RetryJoinAzure{}) {
|
||||||
|
m := discover.Config{
|
||||||
|
"provider": "azure",
|
||||||
|
"tag_name": result.DeprecatedRetryJoinAzure.TagName,
|
||||||
|
"tag_value": result.DeprecatedRetryJoinAzure.TagValue,
|
||||||
|
"subscription_id": result.DeprecatedRetryJoinAzure.SubscriptionID,
|
||||||
|
"tenant_id": result.DeprecatedRetryJoinAzure.TenantID,
|
||||||
|
"client_id": result.DeprecatedRetryJoinAzure.ClientID,
|
||||||
|
"secret_access_key": result.DeprecatedRetryJoinAzure.SecretAccessKey,
|
||||||
|
}
|
||||||
|
result.RetryJoin = append(result.RetryJoin, m.String())
|
||||||
|
result.DeprecatedRetryJoinAzure = RetryJoinAzure{}
|
||||||
|
|
||||||
|
// redact m before output
|
||||||
|
m["subscription_id"] = "<hidden>"
|
||||||
|
m["tenant_id"] = "<hidden>"
|
||||||
|
m["client_id"] = "<hidden>"
|
||||||
|
m["secret_access_key"] = "<hidden>"
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "==> DEPRECATION: retry_join_azure is deprecated."+
|
||||||
|
"Please add %q to retry_join\n", m)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result.DeprecatedRetryJoinGCE, RetryJoinGCE{}) {
|
||||||
|
m := discover.Config{
|
||||||
|
"provider": "gce",
|
||||||
|
"project_name": result.DeprecatedRetryJoinGCE.ProjectName,
|
||||||
|
"zone_pattern": result.DeprecatedRetryJoinGCE.ZonePattern,
|
||||||
|
"tag_value": result.DeprecatedRetryJoinGCE.TagValue,
|
||||||
|
"credentials_file": result.DeprecatedRetryJoinGCE.CredentialsFile,
|
||||||
|
}
|
||||||
|
result.RetryJoin = append(result.RetryJoin, m.String())
|
||||||
|
result.DeprecatedRetryJoinGCE = RetryJoinGCE{}
|
||||||
|
|
||||||
|
// redact m before output
|
||||||
|
m["credentials_file"] = "<hidden>"
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "==> DEPRECATION: retry_join_gce is deprecated."+
|
||||||
|
"Please add %q to retry_join\n", m)
|
||||||
|
}
|
||||||
|
|
||||||
// Check unused fields and verify that no bad configuration options were
|
// Check unused fields and verify that no bad configuration options were
|
||||||
// passed to Consul. There are a few additional fields which don't directly
|
// passed to Consul. There are a few additional fields which don't directly
|
||||||
// use mapstructure decoding, so we need to account for those as well. These
|
// use mapstructure decoding, so we need to account for those as well. These
|
||||||
|
@ -1843,50 +1898,50 @@ func MergeConfig(a, b *Config) *Config {
|
||||||
if b.RetryInterval != 0 {
|
if b.RetryInterval != 0 {
|
||||||
result.RetryInterval = b.RetryInterval
|
result.RetryInterval = b.RetryInterval
|
||||||
}
|
}
|
||||||
if b.RetryJoinEC2.AccessKeyID != "" {
|
if b.DeprecatedRetryJoinEC2.AccessKeyID != "" {
|
||||||
result.RetryJoinEC2.AccessKeyID = b.RetryJoinEC2.AccessKeyID
|
result.DeprecatedRetryJoinEC2.AccessKeyID = b.DeprecatedRetryJoinEC2.AccessKeyID
|
||||||
}
|
}
|
||||||
if b.RetryJoinEC2.SecretAccessKey != "" {
|
if b.DeprecatedRetryJoinEC2.SecretAccessKey != "" {
|
||||||
result.RetryJoinEC2.SecretAccessKey = b.RetryJoinEC2.SecretAccessKey
|
result.DeprecatedRetryJoinEC2.SecretAccessKey = b.DeprecatedRetryJoinEC2.SecretAccessKey
|
||||||
}
|
}
|
||||||
if b.RetryJoinEC2.Region != "" {
|
if b.DeprecatedRetryJoinEC2.Region != "" {
|
||||||
result.RetryJoinEC2.Region = b.RetryJoinEC2.Region
|
result.DeprecatedRetryJoinEC2.Region = b.DeprecatedRetryJoinEC2.Region
|
||||||
}
|
}
|
||||||
if b.RetryJoinEC2.TagKey != "" {
|
if b.DeprecatedRetryJoinEC2.TagKey != "" {
|
||||||
result.RetryJoinEC2.TagKey = b.RetryJoinEC2.TagKey
|
result.DeprecatedRetryJoinEC2.TagKey = b.DeprecatedRetryJoinEC2.TagKey
|
||||||
}
|
}
|
||||||
if b.RetryJoinEC2.TagValue != "" {
|
if b.DeprecatedRetryJoinEC2.TagValue != "" {
|
||||||
result.RetryJoinEC2.TagValue = b.RetryJoinEC2.TagValue
|
result.DeprecatedRetryJoinEC2.TagValue = b.DeprecatedRetryJoinEC2.TagValue
|
||||||
}
|
}
|
||||||
if b.RetryJoinGCE.ProjectName != "" {
|
if b.DeprecatedRetryJoinGCE.ProjectName != "" {
|
||||||
result.RetryJoinGCE.ProjectName = b.RetryJoinGCE.ProjectName
|
result.DeprecatedRetryJoinGCE.ProjectName = b.DeprecatedRetryJoinGCE.ProjectName
|
||||||
}
|
}
|
||||||
if b.RetryJoinGCE.ZonePattern != "" {
|
if b.DeprecatedRetryJoinGCE.ZonePattern != "" {
|
||||||
result.RetryJoinGCE.ZonePattern = b.RetryJoinGCE.ZonePattern
|
result.DeprecatedRetryJoinGCE.ZonePattern = b.DeprecatedRetryJoinGCE.ZonePattern
|
||||||
}
|
}
|
||||||
if b.RetryJoinGCE.TagValue != "" {
|
if b.DeprecatedRetryJoinGCE.TagValue != "" {
|
||||||
result.RetryJoinGCE.TagValue = b.RetryJoinGCE.TagValue
|
result.DeprecatedRetryJoinGCE.TagValue = b.DeprecatedRetryJoinGCE.TagValue
|
||||||
}
|
}
|
||||||
if b.RetryJoinGCE.CredentialsFile != "" {
|
if b.DeprecatedRetryJoinGCE.CredentialsFile != "" {
|
||||||
result.RetryJoinGCE.CredentialsFile = b.RetryJoinGCE.CredentialsFile
|
result.DeprecatedRetryJoinGCE.CredentialsFile = b.DeprecatedRetryJoinGCE.CredentialsFile
|
||||||
}
|
}
|
||||||
if b.RetryJoinAzure.TagName != "" {
|
if b.DeprecatedRetryJoinAzure.TagName != "" {
|
||||||
result.RetryJoinAzure.TagName = b.RetryJoinAzure.TagName
|
result.DeprecatedRetryJoinAzure.TagName = b.DeprecatedRetryJoinAzure.TagName
|
||||||
}
|
}
|
||||||
if b.RetryJoinAzure.TagValue != "" {
|
if b.DeprecatedRetryJoinAzure.TagValue != "" {
|
||||||
result.RetryJoinAzure.TagValue = b.RetryJoinAzure.TagValue
|
result.DeprecatedRetryJoinAzure.TagValue = b.DeprecatedRetryJoinAzure.TagValue
|
||||||
}
|
}
|
||||||
if b.RetryJoinAzure.SubscriptionID != "" {
|
if b.DeprecatedRetryJoinAzure.SubscriptionID != "" {
|
||||||
result.RetryJoinAzure.SubscriptionID = b.RetryJoinAzure.SubscriptionID
|
result.DeprecatedRetryJoinAzure.SubscriptionID = b.DeprecatedRetryJoinAzure.SubscriptionID
|
||||||
}
|
}
|
||||||
if b.RetryJoinAzure.TenantID != "" {
|
if b.DeprecatedRetryJoinAzure.TenantID != "" {
|
||||||
result.RetryJoinAzure.TenantID = b.RetryJoinAzure.TenantID
|
result.DeprecatedRetryJoinAzure.TenantID = b.DeprecatedRetryJoinAzure.TenantID
|
||||||
}
|
}
|
||||||
if b.RetryJoinAzure.ClientID != "" {
|
if b.DeprecatedRetryJoinAzure.ClientID != "" {
|
||||||
result.RetryJoinAzure.ClientID = b.RetryJoinAzure.ClientID
|
result.DeprecatedRetryJoinAzure.ClientID = b.DeprecatedRetryJoinAzure.ClientID
|
||||||
}
|
}
|
||||||
if b.RetryJoinAzure.SecretAccessKey != "" {
|
if b.DeprecatedRetryJoinAzure.SecretAccessKey != "" {
|
||||||
result.RetryJoinAzure.SecretAccessKey = b.RetryJoinAzure.SecretAccessKey
|
result.DeprecatedRetryJoinAzure.SecretAccessKey = b.DeprecatedRetryJoinAzure.SecretAccessKey
|
||||||
}
|
}
|
||||||
if b.RetryMaxAttemptsWan != 0 {
|
if b.RetryMaxAttemptsWan != 0 {
|
||||||
result.RetryMaxAttemptsWan = b.RetryMaxAttemptsWan
|
result.RetryMaxAttemptsWan = b.RetryMaxAttemptsWan
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/defaults"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// discoverEc2Hosts searches an AWS region, returning a list of instance ips
|
|
||||||
// where EC2TagKey = EC2TagValue
|
|
||||||
func (c *Config) discoverEc2Hosts(logger *log.Logger) ([]string, error) {
|
|
||||||
config := c.RetryJoinEC2
|
|
||||||
|
|
||||||
ec2meta := ec2metadata.New(session.New())
|
|
||||||
if config.Region == "" {
|
|
||||||
logger.Printf("[INFO] agent: No EC2 region provided, querying instance metadata endpoint...")
|
|
||||||
identity, err := ec2meta.GetInstanceIdentityDocument()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Region = identity.Region
|
|
||||||
}
|
|
||||||
|
|
||||||
awsConfig := &aws.Config{
|
|
||||||
Region: &config.Region,
|
|
||||||
Credentials: credentials.NewChainCredentials(
|
|
||||||
[]credentials.Provider{
|
|
||||||
&credentials.StaticProvider{
|
|
||||||
Value: credentials.Value{
|
|
||||||
AccessKeyID: config.AccessKeyID,
|
|
||||||
SecretAccessKey: config.SecretAccessKey,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&credentials.EnvProvider{},
|
|
||||||
&credentials.SharedCredentialsProvider{},
|
|
||||||
defaults.RemoteCredProvider(*(defaults.Config()), defaults.Handlers()),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
svc := ec2.New(session.New(), awsConfig)
|
|
||||||
|
|
||||||
resp, err := svc.DescribeInstances(&ec2.DescribeInstancesInput{
|
|
||||||
Filters: []*ec2.Filter{
|
|
||||||
{
|
|
||||||
Name: aws.String("tag:" + config.TagKey),
|
|
||||||
Values: []*string{
|
|
||||||
aws.String(config.TagValue),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var servers []string
|
|
||||||
for i := range resp.Reservations {
|
|
||||||
for _, instance := range resp.Reservations[i].Instances {
|
|
||||||
// Terminated instances don't have the PrivateIpAddress field
|
|
||||||
if instance.PrivateIpAddress != nil {
|
|
||||||
servers = append(servers, *instance.PrivateIpAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return servers, nil
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/arm/network"
|
|
||||||
"github.com/Azure/go-autorest/autorest"
|
|
||||||
"github.com/Azure/go-autorest/autorest/azure"
|
|
||||||
)
|
|
||||||
|
|
||||||
// discoverAzureHosts searches an Azure Subscription, returning a list of instance ips
|
|
||||||
// where AzureTag_Name = AzureTag_Value
|
|
||||||
func (c *Config) discoverAzureHosts(logger *log.Logger) ([]string, error) {
|
|
||||||
var servers []string
|
|
||||||
// Only works for the Azure PublicCLoud for now; no ability to test other Environment
|
|
||||||
oauthConfig, err := azure.PublicCloud.OAuthConfigForTenant(c.RetryJoinAzure.TenantID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Get the ServicePrincipalToken for use searching the NetworkInterfaces
|
|
||||||
sbt, tokerr := azure.NewServicePrincipalToken(*oauthConfig,
|
|
||||||
c.RetryJoinAzure.ClientID,
|
|
||||||
c.RetryJoinAzure.SecretAccessKey,
|
|
||||||
azure.PublicCloud.ResourceManagerEndpoint,
|
|
||||||
)
|
|
||||||
if tokerr != nil {
|
|
||||||
return nil, tokerr
|
|
||||||
}
|
|
||||||
// Setup the client using autorest; followed the structure from Terraform
|
|
||||||
vmnet := network.NewInterfacesClient(c.RetryJoinAzure.SubscriptionID)
|
|
||||||
vmnet.Client.UserAgent = fmt.Sprint("Hashicorp-Consul")
|
|
||||||
vmnet.Authorizer = sbt
|
|
||||||
vmnet.Sender = autorest.CreateSender(autorest.WithLogging(logger))
|
|
||||||
// Get all Network interfaces across ResourceGroups unless there is a compelling reason to restrict
|
|
||||||
netres, neterr := vmnet.ListAll()
|
|
||||||
if neterr != nil {
|
|
||||||
return nil, neterr
|
|
||||||
}
|
|
||||||
// For now, ignore Primary interfaces, choose any PrivateIPAddress with the matching tags
|
|
||||||
for _, oneint := range *netres.Value {
|
|
||||||
// Make it a little more robust just in case there is actually no Tags
|
|
||||||
if oneint.Tags != nil {
|
|
||||||
tv := (*oneint.Tags)[c.RetryJoinAzure.TagName]
|
|
||||||
if tv != nil && *tv == c.RetryJoinAzure.TagValue {
|
|
||||||
// Make it a little more robust just in case IPConfigurations nil
|
|
||||||
if oneint.IPConfigurations != nil {
|
|
||||||
for _, onecfg := range *oneint.IPConfigurations {
|
|
||||||
// fmt.Println("Internal FQDN: ", *onecfg.Name, " IP: ", *onecfg.PrivateIPAddress)
|
|
||||||
// Only get the address if there is private IP address
|
|
||||||
if onecfg.PrivateIPAddress != nil {
|
|
||||||
servers = append(servers, *onecfg.PrivateIPAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers, nil
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDiscoverAzureHosts(t *testing.T) {
|
|
||||||
subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID")
|
|
||||||
tenantID := os.Getenv("ARM_TENANT_ID")
|
|
||||||
clientID := os.Getenv("ARM_CLIENT_ID")
|
|
||||||
clientSecret := os.Getenv("ARM_CLIENT_SECRET")
|
|
||||||
environment := os.Getenv("ARM_ENVIRONMENT")
|
|
||||||
|
|
||||||
if subscriptionID == "" || clientID == "" || clientSecret == "" || tenantID == "" {
|
|
||||||
t.Skip("ARM_SUBSCRIPTION_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET and ARM_TENANT_ID " +
|
|
||||||
"must be set to test Discover Azure Hosts")
|
|
||||||
}
|
|
||||||
|
|
||||||
if environment == "" {
|
|
||||||
t.Log("Environments other than Public not supported at the moment")
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &Config{
|
|
||||||
RetryJoinAzure: RetryJoinAzure{
|
|
||||||
SubscriptionID: subscriptionID,
|
|
||||||
ClientID: clientID,
|
|
||||||
SecretAccessKey: clientSecret,
|
|
||||||
TenantID: tenantID,
|
|
||||||
TagName: "type",
|
|
||||||
TagValue: "Foundation",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
servers, err := c.discoverAzureHosts(log.New(os.Stderr, "", log.LstdFlags))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(servers) != 3 {
|
|
||||||
t.Fatalf("bad: %v", servers)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDiscoverEC2Hosts(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
if os.Getenv("AWS_REGION") == "" {
|
|
||||||
t.Skip("AWS_REGION not set, skipping")
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Getenv("AWS_ACCESS_KEY_ID") == "" {
|
|
||||||
t.Skip("AWS_ACCESS_KEY_ID not set, skipping")
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Getenv("AWS_SECRET_ACCESS_KEY") == "" {
|
|
||||||
t.Skip("AWS_SECRET_ACCESS_KEY not set, skipping")
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &Config{
|
|
||||||
RetryJoinEC2: RetryJoinEC2{
|
|
||||||
Region: os.Getenv("AWS_REGION"),
|
|
||||||
TagKey: "ConsulRole",
|
|
||||||
TagValue: "Server",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
servers, err := c.discoverEc2Hosts(&log.Logger{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(servers) != 3 {
|
|
||||||
t.Fatalf("bad: %v", servers)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDiscoverGCEHosts(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
if os.Getenv("GCE_PROJECT") == "" {
|
|
||||||
t.Skip("GCE_PROJECT not set, skipping")
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" && os.Getenv("GCE_CONFIG_CREDENTIALS") == "" {
|
|
||||||
t.Skip("GOOGLE_APPLICATION_CREDENTIALS or GCE_CONFIG_CREDENTIALS not set, skipping")
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &Config{
|
|
||||||
RetryJoinGCE: RetryJoinGCE{
|
|
||||||
ProjectName: os.Getenv("GCE_PROJECT"),
|
|
||||||
ZonePattern: os.Getenv("GCE_ZONE"),
|
|
||||||
TagValue: "consulrole-server",
|
|
||||||
CredentialsFile: os.Getenv("GCE_CONFIG_CREDENTIALS"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
servers, err := c.discoverGCEHosts(log.New(os.Stderr, "", log.LstdFlags))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(servers) != 3 {
|
|
||||||
t.Fatalf("bad: %v", servers)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -512,63 +512,63 @@ func TestDecodeConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_azure":{"client_id":"a"}}`,
|
in: `{"retry_join_azure":{"client_id":"a"}}`,
|
||||||
c: &Config{RetryJoinAzure: RetryJoinAzure{ClientID: "a"}},
|
c: &Config{RetryJoin: []string{"provider=azure client_id=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_azure":{"tag_name":"a"}}`,
|
in: `{"retry_join_azure":{"tag_name":"a"}}`,
|
||||||
c: &Config{RetryJoinAzure: RetryJoinAzure{TagName: "a"}},
|
c: &Config{RetryJoin: []string{"provider=azure tag_name=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_azure":{"tag_value":"a"}}`,
|
in: `{"retry_join_azure":{"tag_value":"a"}}`,
|
||||||
c: &Config{RetryJoinAzure: RetryJoinAzure{TagValue: "a"}},
|
c: &Config{RetryJoin: []string{"provider=azure tag_value=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_azure":{"secret_access_key":"a"}}`,
|
in: `{"retry_join_azure":{"secret_access_key":"a"}}`,
|
||||||
c: &Config{RetryJoinAzure: RetryJoinAzure{SecretAccessKey: "a"}},
|
c: &Config{RetryJoin: []string{"provider=azure secret_access_key=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_azure":{"subscription_id":"a"}}`,
|
in: `{"retry_join_azure":{"subscription_id":"a"}}`,
|
||||||
c: &Config{RetryJoinAzure: RetryJoinAzure{SubscriptionID: "a"}},
|
c: &Config{RetryJoin: []string{"provider=azure subscription_id=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_azure":{"tenant_id":"a"}}`,
|
in: `{"retry_join_azure":{"tenant_id":"a"}}`,
|
||||||
c: &Config{RetryJoinAzure: RetryJoinAzure{TenantID: "a"}},
|
c: &Config{RetryJoin: []string{"provider=azure tenant_id=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_ec2":{"access_key_id":"a"}}`,
|
in: `{"retry_join_ec2":{"access_key_id":"a"}}`,
|
||||||
c: &Config{RetryJoinEC2: RetryJoinEC2{AccessKeyID: "a"}},
|
c: &Config{RetryJoin: []string{"provider=aws access_key_id=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_ec2":{"region":"a"}}`,
|
in: `{"retry_join_ec2":{"region":"a"}}`,
|
||||||
c: &Config{RetryJoinEC2: RetryJoinEC2{Region: "a"}},
|
c: &Config{RetryJoin: []string{"provider=aws region=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_ec2":{"tag_key":"a"}}`,
|
in: `{"retry_join_ec2":{"tag_key":"a"}}`,
|
||||||
c: &Config{RetryJoinEC2: RetryJoinEC2{TagKey: "a"}},
|
c: &Config{RetryJoin: []string{"provider=aws tag_key=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_ec2":{"tag_value":"a"}}`,
|
in: `{"retry_join_ec2":{"tag_value":"a"}}`,
|
||||||
c: &Config{RetryJoinEC2: RetryJoinEC2{TagValue: "a"}},
|
c: &Config{RetryJoin: []string{"provider=aws tag_value=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_ec2":{"secret_access_key":"a"}}`,
|
in: `{"retry_join_ec2":{"secret_access_key":"a"}}`,
|
||||||
c: &Config{RetryJoinEC2: RetryJoinEC2{SecretAccessKey: "a"}},
|
c: &Config{RetryJoin: []string{"provider=aws secret_access_key=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_gce":{"credentials_file":"a"}}`,
|
in: `{"retry_join_gce":{"credentials_file":"a"}}`,
|
||||||
c: &Config{RetryJoinGCE: RetryJoinGCE{CredentialsFile: "a"}},
|
c: &Config{RetryJoin: []string{"provider=gce credentials_file=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_gce":{"project_name":"a"}}`,
|
in: `{"retry_join_gce":{"project_name":"a"}}`,
|
||||||
c: &Config{RetryJoinGCE: RetryJoinGCE{ProjectName: "a"}},
|
c: &Config{RetryJoin: []string{"provider=gce project_name=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_gce":{"tag_value":"a"}}`,
|
in: `{"retry_join_gce":{"tag_value":"a"}}`,
|
||||||
c: &Config{RetryJoinGCE: RetryJoinGCE{TagValue: "a"}},
|
c: &Config{RetryJoin: []string{"provider=gce tag_value=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_gce":{"zone_pattern":"a"}}`,
|
in: `{"retry_join_gce":{"zone_pattern":"a"}}`,
|
||||||
c: &Config{RetryJoinGCE: RetryJoinGCE{ZonePattern: "a"}},
|
c: &Config{RetryJoin: []string{"provider=gce zone_pattern=a"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
in: `{"retry_join_wan":["a","b"]}`,
|
in: `{"retry_join_wan":["a","b"]}`,
|
||||||
|
@ -1316,7 +1316,7 @@ func TestMergeConfig(t *testing.T) {
|
||||||
CheckUpdateIntervalRaw: "8m",
|
CheckUpdateIntervalRaw: "8m",
|
||||||
RetryIntervalRaw: "10s",
|
RetryIntervalRaw: "10s",
|
||||||
RetryIntervalWanRaw: "10s",
|
RetryIntervalWanRaw: "10s",
|
||||||
RetryJoinEC2: RetryJoinEC2{
|
DeprecatedRetryJoinEC2: RetryJoinEC2{
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
TagKey: "Key1",
|
TagKey: "Key1",
|
||||||
TagValue: "Value1",
|
TagValue: "Value1",
|
||||||
|
@ -1465,7 +1465,7 @@ func TestMergeConfig(t *testing.T) {
|
||||||
Perms: "0700",
|
Perms: "0700",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RetryJoinEC2: RetryJoinEC2{
|
DeprecatedRetryJoinEC2: RetryJoinEC2{
|
||||||
Region: "us-east-2",
|
Region: "us-east-2",
|
||||||
TagKey: "Key2",
|
TagKey: "Key2",
|
||||||
TagValue: "Value2",
|
TagValue: "Value2",
|
||||||
|
|
|
@ -2,59 +2,62 @@ package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
discover "github.com/hashicorp/go-discover"
|
||||||
|
|
||||||
|
// support retry-join only for the following providers
|
||||||
|
// to add more providers import additional packages or 'all'
|
||||||
|
// to support all providers of go-discover
|
||||||
|
_ "github.com/hashicorp/go-discover/provider/aws"
|
||||||
|
_ "github.com/hashicorp/go-discover/provider/azure"
|
||||||
|
_ "github.com/hashicorp/go-discover/provider/gce"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RetryJoin is used to handle retrying a join until it succeeds or all
|
// RetryJoin is used to handle retrying a join until it succeeds or all
|
||||||
// retries are exhausted.
|
// retries are exhausted.
|
||||||
func (a *Agent) retryJoin() {
|
func (a *Agent) retryJoin() {
|
||||||
cfg := a.config
|
cfg := a.config
|
||||||
|
if len(cfg.RetryJoin) == 0 {
|
||||||
ec2Enabled := cfg.RetryJoinEC2.TagKey != "" && cfg.RetryJoinEC2.TagValue != ""
|
|
||||||
gceEnabled := cfg.RetryJoinGCE.TagValue != ""
|
|
||||||
azureEnabled := cfg.RetryJoinAzure.TagName != "" && cfg.RetryJoinAzure.TagValue != ""
|
|
||||||
|
|
||||||
if len(cfg.RetryJoin) == 0 && !ec2Enabled && !gceEnabled && !azureEnabled {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.logger.Printf("[INFO] agent: Supporting retry join for %v", discover.ProviderNames())
|
||||||
a.logger.Printf("[INFO] agent: Joining cluster...")
|
a.logger.Printf("[INFO] agent: Joining cluster...")
|
||||||
attempt := 0
|
attempt := 0
|
||||||
for {
|
for {
|
||||||
var servers []string
|
var addrs []string
|
||||||
var err error
|
var err error
|
||||||
switch {
|
|
||||||
case ec2Enabled:
|
for _, addr := range cfg.RetryJoin {
|
||||||
servers, err = cfg.discoverEc2Hosts(a.logger)
|
switch {
|
||||||
if err != nil {
|
case strings.Contains(addr, "provider="):
|
||||||
a.logger.Printf("[ERR] agent: Unable to query EC2 instances: %s", err)
|
servers, err := discover.Addrs(addr, a.logger)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Printf("[ERR] agent: %s", err)
|
||||||
|
} else {
|
||||||
|
addrs = append(addrs, servers...)
|
||||||
|
a.logger.Printf("[INFO] agent: Discovered servers: %s", strings.Join(servers, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
addrs = append(addrs, addr)
|
||||||
}
|
}
|
||||||
a.logger.Printf("[INFO] agent: Discovered %d servers from EC2", len(servers))
|
|
||||||
case gceEnabled:
|
|
||||||
servers, err = cfg.discoverGCEHosts(a.logger)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Printf("[ERR] agent: Unable to query GCE instances: %s", err)
|
|
||||||
}
|
|
||||||
a.logger.Printf("[INFO] agent: Discovered %d servers from GCE", len(servers))
|
|
||||||
case azureEnabled:
|
|
||||||
servers, err = cfg.discoverAzureHosts(a.logger)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Printf("[ERR] agent: Unable to query Azure instances: %s", err)
|
|
||||||
}
|
|
||||||
a.logger.Printf("[INFO] agent: Discovered %d servers from Azure", len(servers))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
servers = append(servers, cfg.RetryJoin...)
|
if len(addrs) > 0 {
|
||||||
if len(servers) == 0 {
|
n, err := a.JoinLAN(addrs)
|
||||||
err = fmt.Errorf("No servers to join")
|
|
||||||
} else {
|
|
||||||
n, err := a.JoinLAN(servers)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.logger.Printf("[INFO] agent: Join completed. Synced with %d initial agents", n)
|
a.logger.Printf("[INFO] agent: Join completed. Synced with %d initial agents", n)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
err = fmt.Errorf("No servers to join")
|
||||||
|
}
|
||||||
|
|
||||||
attempt++
|
attempt++
|
||||||
if cfg.RetryMaxAttempts > 0 && attempt > cfg.RetryMaxAttempts {
|
if cfg.RetryMaxAttempts > 0 && attempt > cfg.RetryMaxAttempts {
|
||||||
a.retryJoinCh <- fmt.Errorf("agent: max join retry exhausted, exiting")
|
a.retryJoinCh <- fmt.Errorf("agent: max join retry exhausted, exiting")
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
discover "github.com/hashicorp/go-discover"
|
||||||
|
)
|
||||||
|
|
||||||
|
// if this test fails check the _ imports of go-discover/provider/* packages
|
||||||
|
// in retry_join.go
|
||||||
|
func TestGoDiscoverRegistration(t *testing.T) {
|
||||||
|
got := discover.ProviderNames()
|
||||||
|
want := []string{"aws", "azure", "gce"}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("got go-discover providers %v want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
|
@ -135,23 +135,23 @@ func (cmd *AgentCommand) readConfig() *agent.Config {
|
||||||
"Maximum number of join attempts. Defaults to 0, which will retry indefinitely.")
|
"Maximum number of join attempts. Defaults to 0, which will retry indefinitely.")
|
||||||
f.StringVar(&retryInterval, "retry-interval", "",
|
f.StringVar(&retryInterval, "retry-interval", "",
|
||||||
"Time to wait between join attempts.")
|
"Time to wait between join attempts.")
|
||||||
f.StringVar(&cmdCfg.RetryJoinEC2.Region, "retry-join-ec2-region", "",
|
f.StringVar(&cmdCfg.DeprecatedRetryJoinEC2.Region, "retry-join-ec2-region", "",
|
||||||
"EC2 Region to discover servers in.")
|
"EC2 Region to discover servers in.")
|
||||||
f.StringVar(&cmdCfg.RetryJoinEC2.TagKey, "retry-join-ec2-tag-key", "",
|
f.StringVar(&cmdCfg.DeprecatedRetryJoinEC2.TagKey, "retry-join-ec2-tag-key", "",
|
||||||
"EC2 tag key to filter on for server discovery.")
|
"EC2 tag key to filter on for server discovery.")
|
||||||
f.StringVar(&cmdCfg.RetryJoinEC2.TagValue, "retry-join-ec2-tag-value", "",
|
f.StringVar(&cmdCfg.DeprecatedRetryJoinEC2.TagValue, "retry-join-ec2-tag-value", "",
|
||||||
"EC2 tag value to filter on for server discovery.")
|
"EC2 tag value to filter on for server discovery.")
|
||||||
f.StringVar(&cmdCfg.RetryJoinGCE.ProjectName, "retry-join-gce-project-name", "",
|
f.StringVar(&cmdCfg.DeprecatedRetryJoinGCE.ProjectName, "retry-join-gce-project-name", "",
|
||||||
"Google Compute Engine project to discover servers in.")
|
"Google Compute Engine project to discover servers in.")
|
||||||
f.StringVar(&cmdCfg.RetryJoinGCE.ZonePattern, "retry-join-gce-zone-pattern", "",
|
f.StringVar(&cmdCfg.DeprecatedRetryJoinGCE.ZonePattern, "retry-join-gce-zone-pattern", "",
|
||||||
"Google Compute Engine region or zone to discover servers in (regex pattern).")
|
"Google Compute Engine region or zone to discover servers in (regex pattern).")
|
||||||
f.StringVar(&cmdCfg.RetryJoinGCE.TagValue, "retry-join-gce-tag-value", "",
|
f.StringVar(&cmdCfg.DeprecatedRetryJoinGCE.TagValue, "retry-join-gce-tag-value", "",
|
||||||
"Google Compute Engine tag value to filter on for server discovery.")
|
"Google Compute Engine tag value to filter on for server discovery.")
|
||||||
f.StringVar(&cmdCfg.RetryJoinGCE.CredentialsFile, "retry-join-gce-credentials-file", "",
|
f.StringVar(&cmdCfg.DeprecatedRetryJoinGCE.CredentialsFile, "retry-join-gce-credentials-file", "",
|
||||||
"Path to credentials JSON file to use with Google Compute Engine.")
|
"Path to credentials JSON file to use with Google Compute Engine.")
|
||||||
f.StringVar(&cmdCfg.RetryJoinAzure.TagName, "retry-join-azure-tag-name", "",
|
f.StringVar(&cmdCfg.DeprecatedRetryJoinAzure.TagName, "retry-join-azure-tag-name", "",
|
||||||
"Azure tag name to filter on for server discovery.")
|
"Azure tag name to filter on for server discovery.")
|
||||||
f.StringVar(&cmdCfg.RetryJoinAzure.TagValue, "retry-join-azure-tag-value", "",
|
f.StringVar(&cmdCfg.DeprecatedRetryJoinAzure.TagValue, "retry-join-azure-tag-value", "",
|
||||||
"Azure tag value to filter on for server discovery.")
|
"Azure tag value to filter on for server discovery.")
|
||||||
f.Var((*configutil.AppendSliceValue)(&cmdCfg.RetryJoinWan), "retry-join-wan",
|
f.Var((*configutil.AppendSliceValue)(&cmdCfg.RetryJoinWan), "retry-join-wan",
|
||||||
"Address of an agent to join -wan at start time with retries enabled. "+
|
"Address of an agent to join -wan at start time with retries enabled. "+
|
||||||
|
@ -430,20 +430,6 @@ func (cmd *AgentCommand) readConfig() *agent.Config {
|
||||||
cmd.UI.Error("WARNING: Bootstrap mode enabled! Do not enable unless necessary")
|
cmd.UI.Error("WARNING: Bootstrap mode enabled! Do not enable unless necessary")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need both tag key and value for EC2 discovery
|
|
||||||
if cfg.RetryJoinEC2.TagKey != "" || cfg.RetryJoinEC2.TagValue != "" {
|
|
||||||
if cfg.RetryJoinEC2.TagKey == "" || cfg.RetryJoinEC2.TagValue == "" {
|
|
||||||
cmd.UI.Error("tag key and value are both required for EC2 retry-join")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EC2 and GCE discovery are mutually exclusive
|
|
||||||
if cfg.RetryJoinEC2.TagKey != "" && cfg.RetryJoinEC2.TagValue != "" && cfg.RetryJoinGCE.TagValue != "" {
|
|
||||||
cmd.UI.Error("EC2 and GCE discovery are mutually exclusive. Please provide one or the other.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the node metadata entries are valid
|
// Verify the node metadata entries are valid
|
||||||
if err := structs.ValidateMetadata(cfg.Meta); err != nil {
|
if err := structs.ValidateMetadata(cfg.Meta); err != nil {
|
||||||
cmd.UI.Error(fmt.Sprintf("Failed to parse node metadata: %v", err))
|
cmd.UI.Error(fmt.Sprintf("Failed to parse node metadata: %v", err))
|
||||||
|
|
|
@ -85,4 +85,4 @@
|
||||||
{"checksumSHA1":"vlicYp+fe4ECQ+5QqpAk36VRA3s=","path":"golang.org/x/sys/unix","revision":"cd2c276457edda6df7fb04895d3fd6a6add42926","revisionTime":"2017-07-17T10:05:24Z"}
|
{"checksumSHA1":"vlicYp+fe4ECQ+5QqpAk36VRA3s=","path":"golang.org/x/sys/unix","revision":"cd2c276457edda6df7fb04895d3fd6a6add42926","revisionTime":"2017-07-17T10:05:24Z"}
|
||||||
],
|
],
|
||||||
"rootPath": "github.com/hashicorp/consul"
|
"rootPath": "github.com/hashicorp/consul"
|
||||||
}
|
}
|
|
@ -199,6 +199,68 @@ will exit with an error at startup.
|
||||||
port number — for example: `[::1]:8301`. This is useful for cases where we
|
port number — for example: `[::1]:8301`. This is useful for cases where we
|
||||||
know the address will become available eventually.
|
know the address will become available eventually.
|
||||||
|
|
||||||
|
As of Consul 0.9.1 the cloud provider specific discovery of nodes has been
|
||||||
|
moved to the https://github.com/hashicorp/go-discover library which provides
|
||||||
|
a unified query interface for different providers. To use retry join for a
|
||||||
|
supported cloud provider provide a `-retry-join 'provider=xxx key=val key=val
|
||||||
|
...'` parameter with the provider specific values as described below. This
|
||||||
|
can be combined with static IP addresses and names or even multiple
|
||||||
|
`go-discover` configurations for different providers. This deprecates and
|
||||||
|
replaces the `-retry-join-ec2-*`, `-retry-join-azure-*` and
|
||||||
|
`-retry-join-gce-*` parameters and their usage will be translated to a
|
||||||
|
corresponding `go-discover` config string.
|
||||||
|
|
||||||
|
The supported providers for retry join at this point are Amazon EC2,
|
||||||
|
Microsoft Azure, Google Cloud and Softlayer.
|
||||||
|
|
||||||
|
* For Amazon EC2 use:
|
||||||
|
|
||||||
|
`provider=aws tag_key=xxx tag_value=xxx [region=xxx] [access_key_id=xxx] [secret_access_key=xxx]`
|
||||||
|
|
||||||
|
This returns the first private IP address of all servers in the given region
|
||||||
|
which have the given `tag_key` and `tag_value`. If the region is omitted it
|
||||||
|
will be discovered through the local instance's [EC2 metadata
|
||||||
|
endpoint](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html).
|
||||||
|
|
||||||
|
Authentication is handled in the following order:
|
||||||
|
|
||||||
|
- Static credentials `acesss_key_id=xxx secret_access_key=xxx`
|
||||||
|
- Environment variables (`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`)
|
||||||
|
- Shared credentials file (`~/.aws/credentials` or the path specified by `AWS_SHARED_CREDENTIALS_FILE`)
|
||||||
|
- ECS task role metadata (container-specific).
|
||||||
|
- EC2 instance role metadata.
|
||||||
|
|
||||||
|
The only required IAM permission is `ec2:DescribeInstances`, and it is
|
||||||
|
recommended that you make a dedicated key used only for auto-joining.
|
||||||
|
|
||||||
|
* For Microsoft Azure use:
|
||||||
|
|
||||||
|
`provider=azure tag_name=xxx tag_value=xxx tenant_id=xxx client_id=xxx subscription_id=xxx secret_access_key=xxx`
|
||||||
|
|
||||||
|
This returns the first private IP address of all servers for the given
|
||||||
|
tenant/client/sucbscription with the given `tag_name` and `tag_value`.
|
||||||
|
|
||||||
|
* For Google Cloud (GCE) use:
|
||||||
|
|
||||||
|
`provider=gce project_name=xxx tag_value=xxx [zone_pattern=xxx] [credentials_file=xxx]`
|
||||||
|
|
||||||
|
This returns the first private IP address of all servers in the given project
|
||||||
|
which have the given `tag_value`. The list of zones can be restricted through
|
||||||
|
an RE2 compatible regular expression. If omitted, servers in all zones are
|
||||||
|
returned.
|
||||||
|
|
||||||
|
The discovery requires a
|
||||||
|
[GCE Service Account](https://cloud.google.com/compute/docs/access/service-accounts)
|
||||||
|
for which the credentials are searched in the following locations:
|
||||||
|
|
||||||
|
- Use credentials from `credentials_file`, if provided.
|
||||||
|
- Use JSON file from `GOOGLE_APPLICATION_CREDENTIALS` environment variable.
|
||||||
|
- 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`.
|
||||||
|
- On Google Compute Engine, use credentials from the metadata
|
||||||
|
server. In this final case any provided scopes are ignored.
|
||||||
|
|
||||||
* <a name="_retry_join_ec2_tag_key"></a><a href="#_retry_join_ec2_tag_key">`-retry-join-ec2-tag-key`
|
* <a name="_retry_join_ec2_tag_key"></a><a href="#_retry_join_ec2_tag_key">`-retry-join-ec2-tag-key`
|
||||||
</a> - The Amazon EC2 instance tag key to filter on. When used with
|
</a> - The Amazon EC2 instance tag key to filter on. When used with
|
||||||
[`-retry-join-ec2-tag-value`](#_retry_join_ec2_tag_value), Consul will attempt to join EC2
|
[`-retry-join-ec2-tag-value`](#_retry_join_ec2_tag_value), Consul will attempt to join EC2
|
||||||
|
@ -213,32 +275,44 @@ will exit with an error at startup.
|
||||||
The only required IAM permission is `ec2:DescribeInstances`, and it is recommended you make a dedicated
|
The only required IAM permission is `ec2:DescribeInstances`, and it is recommended you make a dedicated
|
||||||
key used only for auto-joining.
|
key used only for auto-joining.
|
||||||
|
|
||||||
|
This parameter has been deprecated as of Consul 0.9.1. See [-retry-join](#_retry_join) for details.
|
||||||
|
|
||||||
* <a name="_retry_join_ec2_tag_value"></a><a href="#_retry_join_ec2_tag_value">`-retry-join-ec2-tag-value`
|
* <a name="_retry_join_ec2_tag_value"></a><a href="#_retry_join_ec2_tag_value">`-retry-join-ec2-tag-value`
|
||||||
</a> - The Amazon EC2 instance tag value to filter on.
|
</a> - The Amazon EC2 instance tag value to filter on.
|
||||||
|
|
||||||
|
This parameter has been deprecated as of Consul 0.9.1. See [-retry-join](#_retry_join) for details.
|
||||||
|
|
||||||
* <a name="_retry_join_ec2_region"></a><a href="#_retry_join_ec2_region">`-retry-join-ec2-region`
|
* <a name="_retry_join_ec2_region"></a><a href="#_retry_join_ec2_region">`-retry-join-ec2-region`
|
||||||
</a> - (Optional) The Amazon EC2 region to use. If not specified, Consul
|
</a> - (Optional) The Amazon EC2 region to use. If not specified, Consul
|
||||||
will use the local instance's [EC2 metadata endpoint](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html)
|
will use the local instance's [EC2 metadata endpoint](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html)
|
||||||
to discover the region.
|
to discover the region.
|
||||||
|
|
||||||
|
This parameter has been deprecated as of Consul 0.9.1. See [-retry-join](#_retry_join) for details.
|
||||||
|
|
||||||
* <a name="_retry_join_gce_tag_value"></a><a href="#_retry_join_gce_tag_value">`-retry-join-gce-tag-value`
|
* <a name="_retry_join_gce_tag_value"></a><a href="#_retry_join_gce_tag_value">`-retry-join-gce-tag-value`
|
||||||
</a> - A Google Compute Engine instance tag to filter on. Much like the
|
</a> - A Google Compute Engine instance tag to filter on. Much like the
|
||||||
`-retry-join-ec2-*` options, this gives Consul the option of doing server
|
`-retry-join-ec2-*` options, this gives Consul the option of doing server
|
||||||
discovery on [Google Compute Engine](https://cloud.google.com/compute/) by
|
discovery on [Google Compute Engine](https://cloud.google.com/compute/) by
|
||||||
searching the tags assigned to any particular instance.
|
searching the tags assigned to any particular instance.
|
||||||
|
|
||||||
|
This parameter has been deprecated as of Consul 0.9.1. See [-retry-join](#_retry_join) for details.
|
||||||
|
|
||||||
* <a name="_retry_join_gce_project_name"></a><a href="#_retry_join_gce_project_name">`-retry-join-gce-project-name`
|
* <a name="_retry_join_gce_project_name"></a><a href="#_retry_join_gce_project_name">`-retry-join-gce-project-name`
|
||||||
</a> - The project to search in for the tag supplied by
|
</a> - The project to search in for the tag supplied by
|
||||||
[`-retry-join-gce-tag-value`](#_retry_join_gce_tag_value). If this is run
|
[`-retry-join-gce-tag-value`](#_retry_join_gce_tag_value). If this is run
|
||||||
from within a GCE instance, the default is the project the instance is
|
from within a GCE instance, the default is the project the instance is
|
||||||
located in.
|
located in.
|
||||||
|
|
||||||
|
This parameter has been deprecated as of Consul 0.9.1. See [-retry-join](#_retry_join) for details.
|
||||||
|
|
||||||
* <a name="_retry_join_gce_zone_pattern"></a><a href="#_retry_join_gce_zone_pattern">`-retry-join-gce-zone-pattern`
|
* <a name="_retry_join_gce_zone_pattern"></a><a href="#_retry_join_gce_zone_pattern">`-retry-join-gce-zone-pattern`
|
||||||
</a> - A regular expression that indicates the zones the tag should be
|
</a> - A regular expression that indicates the zones the tag should be
|
||||||
searched in. For example, while `us-west1-a` would only search in
|
searched in. For example, while `us-west1-a` would only search in
|
||||||
`us-west1-a`, `us-west1-.*` would search in `us-west1-a` and `us-west1-b`.
|
`us-west1-a`, `us-west1-.*` would search in `us-west1-a` and `us-west1-b`.
|
||||||
The default is to search globally.
|
The default is to search globally.
|
||||||
|
|
||||||
|
This parameter has been deprecated as of Consul 0.9.1. See [-retry-join](#_retry_join) for details.
|
||||||
|
|
||||||
* <a name="_retry_join_gce_credentials_file"></a><a href="#_retry_join_gce_credentials_file">`-retry-join-gce-credentials-file`
|
* <a name="_retry_join_gce_credentials_file"></a><a href="#_retry_join_gce_credentials_file">`-retry-join-gce-credentials-file`
|
||||||
</a> - The path to the JSON credentials file of the [GCE Service
|
</a> - The path to the JSON credentials file of the [GCE Service
|
||||||
Account](https://cloud.google.com/compute/docs/access/service-accounts) that
|
Account](https://cloud.google.com/compute/docs/access/service-accounts) that
|
||||||
|
@ -252,6 +326,8 @@ will exit with an error at startup.
|
||||||
- If none of these exist and discovery is being run from a GCE instance, the
|
- If none of these exist and discovery is being run from a GCE instance, the
|
||||||
instance's configured service account will be used.
|
instance's configured service account will be used.
|
||||||
|
|
||||||
|
This parameter has been deprecated as of Consul 0.9.1. See [-retry-join](#_retry_join) for details.
|
||||||
|
|
||||||
* <a name="_retry_join_azure_tag_name"></a><a href="#_retry_join_azure_tag_name">`-retry-join-azure-tag-name`
|
* <a name="_retry_join_azure_tag_name"></a><a href="#_retry_join_azure_tag_name">`-retry-join-azure-tag-name`
|
||||||
</a> - The Azure instance tag name to filter on. When used with
|
</a> - The Azure instance tag name to filter on. When used with
|
||||||
[`-retry-join-azure-tag-value`](#_retry_join_azure_tag_value), Consul will attempt to join Azure
|
[`-retry-join-azure-tag-value`](#_retry_join_azure_tag_value), Consul will attempt to join Azure
|
||||||
|
@ -261,9 +337,13 @@ will exit with an error at startup.
|
||||||
|
|
||||||
The only permission needed is the ListAll method for NetworkInterfaces. It is recommended you make a dedicated key used only for auto-joining.
|
The only permission needed is the ListAll method for NetworkInterfaces. It is recommended you make a dedicated key used only for auto-joining.
|
||||||
|
|
||||||
|
This parameter has been deprecated as of Consul 0.9.1. See [-retry-join](#_retry_join) for details.
|
||||||
|
|
||||||
* <a name="_retry_join_azure_tag_value"></a><a href="#_retry_join_azure_tag_value">`-retry-join-azure-tag-value`
|
* <a name="_retry_join_azure_tag_value"></a><a href="#_retry_join_azure_tag_value">`-retry-join-azure-tag-value`
|
||||||
</a> - The Azure instance tag value to filter on.
|
</a> - The Azure instance tag value to filter on.
|
||||||
|
|
||||||
|
This parameter has been deprecated as of Consul 0.9.1. See [-retry-join](#_retry_join) for details.
|
||||||
|
|
||||||
* <a name="_retry_interval"></a><a href="#_retry_interval">`-retry-interval`</a> - Time
|
* <a name="_retry_interval"></a><a href="#_retry_interval">`-retry-interval`</a> - Time
|
||||||
to wait between join attempts. Defaults to 30s.
|
to wait between join attempts. Defaults to 30s.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue