* #7013 add tls config support to consul provider * #7013 add acceptance tests * #7013 use GFM tables * #7013 require one of {CONSUL_ADDRESS,CONSUL_HTTP_ADDR} when running consul acc tests
This commit is contained in:
parent
45219dbb4f
commit
c072c0dfbb
|
@ -2,6 +2,7 @@ package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
consulapi "github.com/hashicorp/consul/api"
|
consulapi "github.com/hashicorp/consul/api"
|
||||||
)
|
)
|
||||||
|
@ -9,8 +10,11 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Datacenter string `mapstructure:"datacenter"`
|
Datacenter string `mapstructure:"datacenter"`
|
||||||
Address string `mapstructure:"address"`
|
Address string `mapstructure:"address"`
|
||||||
Token string `mapstructure:"token"`
|
|
||||||
Scheme string `mapstructure:"scheme"`
|
Scheme string `mapstructure:"scheme"`
|
||||||
|
Token string `mapstructure:"token"`
|
||||||
|
CAFile string `mapstructure:"ca_file"`
|
||||||
|
CertFile string `mapstructure:"cert_file"`
|
||||||
|
KeyFile string `mapstructure:"key_file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client() returns a new client for accessing consul.
|
// Client() returns a new client for accessing consul.
|
||||||
|
@ -26,9 +30,21 @@ func (c *Config) Client() (*consulapi.Client, error) {
|
||||||
if c.Scheme != "" {
|
if c.Scheme != "" {
|
||||||
config.Scheme = c.Scheme
|
config.Scheme = c.Scheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tlsConfig := &consulapi.TLSConfig{}
|
||||||
|
tlsConfig.CAFile = c.CAFile
|
||||||
|
tlsConfig.CertFile = c.CertFile
|
||||||
|
tlsConfig.KeyFile = c.KeyFile
|
||||||
|
cc, err := consulapi.SetupTLSConfig(tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.HttpClient.Transport.(*http.Transport).TLSClientConfig = cc
|
||||||
|
|
||||||
if c.Token != "" {
|
if c.Token != "" {
|
||||||
config.Token = c.Token
|
config.Token = c.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := consulapi.NewClient(config)
|
client, err := consulapi.NewClient(config)
|
||||||
|
|
||||||
log.Printf("[INFO] Consul Client configured with address: '%s', scheme: '%s', datacenter: '%s'",
|
log.Printf("[INFO] Consul Client configured with address: '%s', scheme: '%s', datacenter: '%s'",
|
||||||
|
|
|
@ -20,11 +20,34 @@ func Provider() terraform.ResourceProvider {
|
||||||
"address": &schema.Schema{
|
"address": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
|
||||||
|
"CONSUL_ADDRESS",
|
||||||
|
"CONSUL_HTTP_ADDR",
|
||||||
|
}, nil),
|
||||||
},
|
},
|
||||||
|
|
||||||
"scheme": &schema.Schema{
|
"scheme": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("CONSUL_SCHEME", nil),
|
||||||
|
},
|
||||||
|
|
||||||
|
"ca_file": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("CONSUL_CA_FILE", nil),
|
||||||
|
},
|
||||||
|
|
||||||
|
"cert_file": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("CONSUL_CERT_FILE", nil),
|
||||||
|
},
|
||||||
|
|
||||||
|
"key_file": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("CONSUL_KEY_FILE", nil),
|
||||||
},
|
},
|
||||||
|
|
||||||
"token": &schema.Schema{
|
"token": &schema.Schema{
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
consulapi "github.com/hashicorp/consul/api"
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
@ -18,12 +17,6 @@ func init() {
|
||||||
testAccProviders = map[string]terraform.ResourceProvider{
|
testAccProviders = map[string]terraform.ResourceProvider{
|
||||||
"consul": testAccProvider,
|
"consul": testAccProvider,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the demo address for the acceptance tests
|
|
||||||
testAccProvider.ConfigureFunc = func(d *schema.ResourceData) (interface{}, error) {
|
|
||||||
conf := consulapi.DefaultConfig()
|
|
||||||
return consulapi.NewClient(conf)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceProvider(t *testing.T) {
|
func TestResourceProvider(t *testing.T) {
|
||||||
|
@ -56,8 +49,35 @@ func TestResourceProvider_Configure(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResourceProvider_ConfigureTLS(t *testing.T) {
|
||||||
|
rp := Provider()
|
||||||
|
|
||||||
|
raw := map[string]interface{}{
|
||||||
|
"address": "demo.consul.io:80",
|
||||||
|
"ca_file": "test-fixtures/cacert.pem",
|
||||||
|
"cert_file": "test-fixtures/usercert.pem",
|
||||||
|
"datacenter": "nyc3",
|
||||||
|
"key_file": "test-fixtures/userkey.pem",
|
||||||
|
"scheme": "https",
|
||||||
|
}
|
||||||
|
|
||||||
|
rawConfig, err := config.NewRawConfig(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rp.Configure(terraform.NewResourceConfig(rawConfig))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testAccPreCheck(t *testing.T) {
|
func testAccPreCheck(t *testing.T) {
|
||||||
if v := os.Getenv("CONSUL_HTTP_ADDR"); v == "" {
|
if v := os.Getenv("CONSUL_HTTP_ADDR"); v != "" {
|
||||||
t.Fatal("CONSUL_HTTP_ADDR must be set for acceptance tests")
|
return
|
||||||
}
|
}
|
||||||
|
if v := os.Getenv("CONSUL_ADDRESS"); v != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatal("Either CONSUL_ADDRESS or CONSUL_HTTP_ADDR must be set for acceptance tests")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Running Consul for Terraform Acceptance Tests
|
||||||
|
|
||||||
|
## TLS
|
||||||
|
|
||||||
|
Some of the acceptance tests for the `consul` provider use
|
||||||
|
TLS. To service these tests, a Consul server must be started
|
||||||
|
with HTTPS enabled with TLS certificates.
|
||||||
|
|
||||||
|
### Test fixtures
|
||||||
|
|
||||||
|
File | Description
|
||||||
|
--- | ---
|
||||||
|
`agent.json.example` | Configures the Consul agent to respond to HTTPS requests, and verifies the authenticity of HTTPS requests
|
||||||
|
`agentcert.pem` | A PEM-encoded certificate used by the Consul agent, valid only for 127.0.0.1 signed by `cacert.pem`, expires 2026
|
||||||
|
`agentkey.pem` | A PEM-encoded private key used by the Consul agent
|
||||||
|
`cacert.pem` | A PEM-encoded Certificate Authority, expires 2036
|
||||||
|
`usercert.pem` | A PEM-encoded certificate used by the Terraform acceptance tests, signed by `cacert.pem`, expires 2026
|
||||||
|
`userkey.pem` | A PEM-encoded private key used by the Terraform acceptance tests
|
||||||
|
|
||||||
|
### Start
|
||||||
|
|
||||||
|
Start a Consul server configured to serve HTTP traffic, and validate incoming
|
||||||
|
HTTPS requests.
|
||||||
|
|
||||||
|
~/.go/src/github.com/hashicorp/terraform> consul agent \
|
||||||
|
-bind 127.0.0.1 \
|
||||||
|
-data-dir=/tmp \
|
||||||
|
-dev \
|
||||||
|
-config-file=builtin/providers/consul/text-fixtures/agent.json.example \
|
||||||
|
-server
|
||||||
|
|
||||||
|
### Test
|
||||||
|
|
||||||
|
With TLS, `CONSUL_HTTP_ADDR` must match the Common Name of the agent certificate.
|
||||||
|
|
||||||
|
~/.go/src/github.com/hashicorp/terraform> CONSUL_CERT_FILE=test-fixtures/usercert.pem \
|
||||||
|
CONSUL_KEY_FILE=test-fixtures/userkey.pem \
|
||||||
|
CONSUL_CA_FILE=test-fixtures/cacert.pem \
|
||||||
|
CONSUL_SCHEME=https \
|
||||||
|
CONSUL_HTTP_ADDR=127.0.0.1:8943 \
|
||||||
|
make testacc TEST=./builtin/providers/consul/
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"ca_file": "./cacert.pem",
|
||||||
|
"cert_file": "./agentcert.pem",
|
||||||
|
"datacenter": "dc1",
|
||||||
|
"domain": "hashicorp.test",
|
||||||
|
"key_file": "./agentkey.pem",
|
||||||
|
"ports": {
|
||||||
|
"https": 8943
|
||||||
|
},
|
||||||
|
"verify_incoming": true
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEjjCCA3agAwIBAgICEAMwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCVVMx
|
||||||
|
EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UE
|
||||||
|
CwwJSGFzaGlDb3JwMRowGAYDVQQDDBFIYXNoaUNvcnAgVGVzdCBDQTAgFw0xNjA4
|
||||||
|
MTEwNTEwMDRaGA8yMDY2MDczMDA1MTAwNFowXjELMAkGA1UEBhMCVVMxEzARBgNV
|
||||||
|
BAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UECwwJSGFz
|
||||||
|
aGlDb3JwMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||||
|
DwAwggEKAoIBAQDQiR2zwLdfCd95LIrKekZlDKo3YF5nMZFzR3OR1Mc8jCAaYLz/
|
||||||
|
ZFr8hVSAsygwZ+tHzoHP0U3FxeYemPtjLAPE077C6h+v6pHiTLxOkd22GtyalgMZ
|
||||||
|
E4ACGSogqDUvwssxxDUsG2ItzhVCB0GXTlfo/6XhApyRqvnEto+ZJ+zk6MiHnAmc
|
||||||
|
eN9sx0c5K097+Nq7PZgtk6HOxbKSvMWEkTtHrOBrhc9lTfwVSiWHdZ2X0wpOL1Ra
|
||||||
|
pFnBMDxnWyH2ivVMknarzKz2pBBDJwTGvsJcC1ymprqU+SRyjs75BfNv2BKJrhb4
|
||||||
|
vBj3YEGMBEhHKtnObniGqV8W4o9jBIwocFpfAgMBAAGjggFKMIIBRjAPBgNVHREE
|
||||||
|
CDAGhwR/AAABMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG
|
||||||
|
+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYD
|
||||||
|
VR0OBBYEFNzoTM6XceaITc2lVIDrXaYBKJolMIGRBgNVHSMEgYkwgYaAFANEnP7/
|
||||||
|
5Iil24eKuYJTt/IJPfamoWqkaDBmMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2Fs
|
||||||
|
aWZvcm5pYTESMBAGA1UECgwJSGFzaGlDb3JwMRIwEAYDVQQLDAlIYXNoaUNvcnAx
|
||||||
|
GjAYBgNVBAMMEUhhc2hpQ29ycCBUZXN0IENBggIQADAOBgNVHQ8BAf8EBAMCBaAw
|
||||||
|
HQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IB
|
||||||
|
AQB4p5sWE+p+sheXYgkg/PsATRMxYDPRTw0Zvd2AKOrodqF4IlibSb4mVVh0fPtx
|
||||||
|
2VX3z/WOZb8wgXNnEUhVcijf7LgGvw/SvQGgW5mXYSCcHeb4ETFJ1yuKZj5yn5tl
|
||||||
|
vZx1Sq/fGFkjHn3mgL+gzyQlNk1Wt0p3fLsIfpMOgpntSdRq3IUvf+W+oH5BUrTl
|
||||||
|
WgaXUD3lkdx3R9h3uLX4nxJWpMPViPCpr3ADW9oEwoHHQbe3LC7iJI2Us/qIH73n
|
||||||
|
Du7mUk+/HSkajjFsxnVoFCF1+RMqf1i9w7tXaAwWYT+vaP46fq3M/Bmsv/gDc5ur
|
||||||
|
8p48hpQ61Sfj0oU38Ftzzcs+
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEA0Ikds8C3XwnfeSyKynpGZQyqN2BeZzGRc0dzkdTHPIwgGmC8
|
||||||
|
/2Ra/IVUgLMoMGfrR86Bz9FNxcXmHpj7YywDxNO+wuofr+qR4ky8TpHdthrcmpYD
|
||||||
|
GROAAhkqIKg1L8LLMcQ1LBtiLc4VQgdBl05X6P+l4QKckar5xLaPmSfs5OjIh5wJ
|
||||||
|
nHjfbMdHOStPe/jauz2YLZOhzsWykrzFhJE7R6zga4XPZU38FUolh3Wdl9MKTi9U
|
||||||
|
WqRZwTA8Z1sh9or1TJJ2q8ys9qQQQycExr7CXAtcpqa6lPkkco7O+QXzb9gSia4W
|
||||||
|
+LwY92BBjARIRyrZzm54hqlfFuKPYwSMKHBaXwIDAQABAoIBAFQxyAg3GtIITm3C
|
||||||
|
ChdN3vYVcvQAuJy5apw8kPCkE/ziJmQAAs6qWgHyYvfDXcqNanUHb2nUe64KBKr9
|
||||||
|
4SFdN/hT9YUEud5wuo2/pZejVPydQ8w2HPIW6WvvdQ7SWwb5gsiJC17Pf4g22GZc
|
||||||
|
P6MzQlMURIjgYQ5/FXDStI+FiyOwDhHDbLoMaIOfToWJupd+sGREccSKOVmJdGY/
|
||||||
|
7/n1AGvfbgUToy2sMEz7HqTtgRJW/Knko2dD3ZWh7KqFS2GUYsJ3Ake1CG7xT6sA
|
||||||
|
4MWQvfR/+t7xSpDDU2WlNgFi9sQ8UjrIhaMYaiFhV6h2bTVeGl1tvBmbE77Z1Lne
|
||||||
|
jcobwKECgYEA6SuDF0wuu8WnDXnCpNrd83gONy1pEp9s7vbf/GrMXGaavQmqb6x1
|
||||||
|
sLZTXho1srqRLXGSAvbDS/yO6wRUd/CdLB8WBda3lcH9y/II1BDynEygGpipGa83
|
||||||
|
7Ti+D2hMSMLhX1vsUcCwz3fz61wzBqvdvrjdymmivPLu3rMINd8twl0CgYEA5PQi
|
||||||
|
jwi483icPPOc1S/5CsKnj6nJwKVQz9dDeT6QLVDCoVh5u0X9WZAAnMdrR9yZS6ax
|
||||||
|
ZAF453DPlK6Ct7vcBPEFC1W6QrRhjJrzvRb/PLnzaRMY+YoEg2qmGreb+30jrx+4
|
||||||
|
jkTLkz4Qag+jOdR3t4104Pix1CkpQUX0chD5u+sCgYAiuI8Bxh9jaLBSimIYqFrK
|
||||||
|
qYL8Zm+yDTlscCi0brbVv5WlNq5BiN3RnaTWa3K5lZyOts22UUaNpyMlDfUCEztk
|
||||||
|
WZCu9+VIkKWZXAZChe+KpMJmk3sCzxu14HA03SQW5aYnzAlptxbdHhCdaJJUmP0h
|
||||||
|
LGgifw5zsn0tfl1noD8xJQKBgBKSSwtXJcl6CxJWoG4aihT5XSYmG5tozXlOeMao
|
||||||
|
8ID8gA0eZCFwt/A/4gzVkDowBq9AQjteczQyzmO9FBVbQ6mS81nMBmPKxe7l0seP
|
||||||
|
yfxfCQOI7QmwzFTsnbSlGB36NJ7L7+h6ZBj5e9NemVrjhSJ6cvSct7AB9rq4te9a
|
||||||
|
uScpAoGBAOIjcv2lQsJ3GWBTHWCh23jC/0XPE4bJg9DjliHQDAB/Yp49oV1giWs6
|
||||||
|
xI0SBsovtJqJxOd6F8e6HuQTt1X1kQ8Q1Itb78Wx9Rs4bvN51pxj4L+DTxLBMl5g
|
||||||
|
xXsS+Zfm5O2HGxU5t60CsxRyF0EVNVEtgKkIiQE+ZaQ1d0WJC8RI
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDrTCCApWgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCVVMx
|
||||||
|
EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UE
|
||||||
|
CwwJSGFzaGlDb3JwMRowGAYDVQQDDBFIYXNoaUNvcnAgVGVzdCBDQTAgFw0xNjA4
|
||||||
|
MTEwNTA1MDZaGA8yMTE2MDcxODA1MDUwNlowZjELMAkGA1UEBhMCVVMxEzARBgNV
|
||||||
|
BAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UECwwJSGFz
|
||||||
|
aGlDb3JwMRowGAYDVQQDDBFIYXNoaUNvcnAgVGVzdCBDQTCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBAKglYmf9Tv1u6e1ulQZNpmvUHi+D+/PBHyg9Ft60
|
||||||
|
HiZaeBGyNPZX9uVuM1jN3o/qpwBQxhq3ojQafU3WDSU6Y0GZ1e8AcLsObeNUjma4
|
||||||
|
eLjZy+059Vt7DKcp6LA+7JqELToK83QzqNdYuocze5v9hPt5W6Q3Dp5rsmVjOFim
|
||||||
|
6LxcN/TAcmW+ZrykOGOT4QyYFkamp4uMJkpX1UwO3djdQF7CllnOboUUYqGyOt9e
|
||||||
|
BBudhCsSvWpJa/wNcAH2AxzaIVu85Dmg3G0Erekcget5ewebsnhGs3emfWO/XQht
|
||||||
|
uwKdz60mz1vAIK3UR5eYCbxnLrXM0WfcYKFqhuQpqqONWtUCAwEAAaNjMGEwHQYD
|
||||||
|
VR0OBBYEFANEnP7/5Iil24eKuYJTt/IJPfamMB8GA1UdIwQYMBaAFANEnP7/5Iil
|
||||||
|
24eKuYJTt/IJPfamMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0G
|
||||||
|
CSqGSIb3DQEBCwUAA4IBAQBzzvX4GiBalxuW5YxMiuFAljNB+tW20Frz0s7bq0+Z
|
||||||
|
1+ErQIW26NUHH14RUU4vbisX09QMm4p62oJOpo/5nW1VqsyoTCQJXaolGF6UidFy
|
||||||
|
l/2bgOy8QbCOqrS0jt0MFQFDr9Z/m8dBgbjFzv8gfsnpxDQvi+iKkVSuzlIfcvoo
|
||||||
|
xlwtNnrD9lSsinP4Zo8sNqjhaRbih8zhsUdd0mUDDGczw2mY2CdMmeH0wflJMEVe
|
||||||
|
3hwR8650sCJlJfVuFUDsqy1K9T5j5NVv7i6RloeMvYOH2nwpIejE88lmjpXR6Bzw
|
||||||
|
g8geEjKOLBN8Nmak3jSvH2IewczZKSaKNSiv/4Izut/8
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,25 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEJjCCAw6gAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCVVMx
|
||||||
|
EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UE
|
||||||
|
CwwJSGFzaGlDb3JwMRowGAYDVQQDDBFIYXNoaUNvcnAgVGVzdCBDQTAgFw0xNjA4
|
||||||
|
MTEwNTA1MzFaGA8yMDY2MDczMDA1MDUzMVowcjELMAkGA1UEBhMCVVMxEzARBgNV
|
||||||
|
BAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UECwwJSGFz
|
||||||
|
aGlDb3JwMSYwJAYDVQQDDB1IYXNoaUNvcnAgVGVzdCBUZXJyYWZvcm0gVXNlcjCC
|
||||||
|
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrfzsj+gIM3pvuI+hdx2W5s
|
||||||
|
hTh2YXd4Q7cByLDzXPRgI0W1BFOIxOAdHy/zqxCKQPxiibxPqDCxzPnc7mSco8e0
|
||||||
|
zvihAysthiUmWcNdF1pIh6631SU9rE+Mis6XcW2beuh/IVloXBwI4dmSuX3Urb0D
|
||||||
|
Aw3Rb5kCJzXUTBG/g8KriR6KyNFTu0Wb/1NcrrCnNAteQmpDuuMtx75stfoMUnlr
|
||||||
|
xZfsCZXHVpe8GmVlwqr8Mw7NKmyeKgl0rH1Mef6+ce9BPnVBxdJMEYWl+UQfTSV+
|
||||||
|
pWoNtQTZxEbhbMFhYi410EJ5s0Nw6lyUnXrQ2/YglikIvnyfWj/CwLTZwaXlgAkC
|
||||||
|
AwEAAaOBzzCBzDAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAzBglghkgB
|
||||||
|
hvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgQ2xpZW50IENlcnRpZmljYXRlMB0G
|
||||||
|
A1UdDgQWBBRIgDbi1wLtW+u3PgrbVrNDkGfwGjAfBgNVHSMEGDAWgBQDRJz+/+SI
|
||||||
|
pduHirmCU7fyCT32pjAOBgNVHQ8BAf8EBAMCBeAwJwYDVR0lBCAwHgYIKwYBBQUH
|
||||||
|
AwIGCCsGAQUFBwMEBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEAi1pDjqy1
|
||||||
|
bN9cLknPyblWdgaO0xSXJAvBpEaFnz88wmEPOmsg1889x7jJKhrGjTpjDJMeq3kh
|
||||||
|
ziDVCpOJesJOlUsa8ejOhMbcdiqHOWSk12bQC7dBbnAAXwO1Tr583IdLhC+ej64r
|
||||||
|
J4dBk7/wLBx2Deh8wxW+6TrIFNCyVptcHw76K2AkO11fscqS0sL/WVxqi1mjA9rV
|
||||||
|
KNGDIEIzqu13jZ3t0Cxc5AZ6dGHBALGNkfhjJ9SCpuPf8CmWNYHGWeIV0N4AB2SQ
|
||||||
|
gAjRYUKY4+zU3e+lUuudgTYZIM+zark6hLUAfXTeNRk6kGHod7x/Q9NvLB4SLwlI
|
||||||
|
DAzXJ9QHZyO1vQ==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpgIBAAKCAQEA2t/OyP6Agzem+4j6F3HZbmyFOHZhd3hDtwHIsPNc9GAjRbUE
|
||||||
|
U4jE4B0fL/OrEIpA/GKJvE+oMLHM+dzuZJyjx7TO+KEDKy2GJSZZw10XWkiHrrfV
|
||||||
|
JT2sT4yKzpdxbZt66H8hWWhcHAjh2ZK5fdStvQMDDdFvmQInNdRMEb+DwquJHorI
|
||||||
|
0VO7RZv/U1yusKc0C15CakO64y3Hvmy1+gxSeWvFl+wJlcdWl7waZWXCqvwzDs0q
|
||||||
|
bJ4qCXSsfUx5/r5x70E+dUHF0kwRhaX5RB9NJX6lag21BNnERuFswWFiLjXQQnmz
|
||||||
|
Q3DqXJSdetDb9iCWKQi+fJ9aP8LAtNnBpeWACQIDAQABAoIBAQCRXS8zIoQrodyP
|
||||||
|
FkwzIfPseLqJ42WcOQ2QD+lATIEh9G+4rh5vdFh9GBpMeKLWW1wJw1AC90yW+p9O
|
||||||
|
G0NhIv9LdXQ4gIdgN93t8miPbdZCqgUjLwiqsSkttAPEbaRxzV915mk5vivemq+V
|
||||||
|
FvOG9Kdm7wcqODzL/DgaciMLboyNzrChltgybGKQIHAd9UFm+jE86IeeBsVcHuoL
|
||||||
|
0rEsYFKKzgdmIizjDOWPDSzVKL+dkiZ/8rYgoe1VtGV1DRWAWU5fawDFrdOxsGCh
|
||||||
|
Ob+rEmosTvONEQhB6GsdOQZ8++N6UTiJw32jqgieeP+Xj+K4XNG3nhP000DUIx/o
|
||||||
|
pRnj+KDhAoGBAPWXFbGHIMwEJCUV6OUXiY9Enb3A/Jf65f7cKbvo1i/nIbhzEv3v
|
||||||
|
LBtcUrsTmgTgNvuh3tF1RnwAUeYgedjdnALGJL16h3E0IWLAStaovoKgQyHHrtp9
|
||||||
|
CEnOIj3PcPTFJVe+2FeV0+/kLjTHsZj9gMljzfxgswNdYfeGjXp4o1lVAoGBAOQm
|
||||||
|
06TW3smWW0FIRyyDNBwhQjD8tg3Yn0QJ+zFP6WMk5qbu/LeJnJSevBpQt1PvJ6xQ
|
||||||
|
kCj6Bi90jtLeuCW/8XLQjP46jQLU+3a74m3Nszgu9JVofiK6EPIsx62TGlwtIJfJ
|
||||||
|
U4+C5D/Piw/3qy6MjDbA1NJlSE4i2hAgGA79cDvlAoGBAN2o2sSbkOdyyWjLiKPV
|
||||||
|
BaxQowrUN2e45YONFQHsGf2sYEwJWNfm2elr/6OoAnhqIlYleGWWsuJSq5jIMRGi
|
||||||
|
myAJ1LlL8Rkkkwl9Q07RiPl/SngfsVq0RRnQOimNpIbXtWen8b3DlkFLssSihFHw
|
||||||
|
ZB/gu9cRNCFSVIzDXchvQAftAoGBAL0EzeOLgRhSUVhMoWrnaIzFoSkktU/TYF/m
|
||||||
|
RQ4dvqY9NDqpVQZaJDedKwpCRSBsytmgBU9tlSJL1ugtTTM5Srhsv+MAb0MhYRSF
|
||||||
|
pJqECS9K96ew4o+yx8dcAjJz5Sro2E/opCoJr0COmg+oiVIPbzsNl0SYVMcnaLJj
|
||||||
|
ZItGvW1hAoGBALeVUiqLgEDNQAIvprRlpJhU/ANpKm01ja9v66cgvmg62P9fcqb+
|
||||||
|
yYPuJ2CwFDlb70KIDys6q9sTKUFykPDiKQgAFvEBQgAyOb3kdl4SXoUPbVDhMqwB
|
||||||
|
OfPznsXM6Y5LFNLzEi4n0QP4KsLc+wM52On5vnj7Mgvt5h2QlllPPTXy
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -23,6 +23,7 @@ type AgentService struct {
|
||||||
Tags []string
|
Tags []string
|
||||||
Port int
|
Port int
|
||||||
Address string
|
Address string
|
||||||
|
EnableTagOverride bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// AgentMember represents a cluster member known to the agent
|
// AgentMember represents a cluster member known to the agent
|
||||||
|
@ -47,6 +48,7 @@ type AgentServiceRegistration struct {
|
||||||
Tags []string `json:",omitempty"`
|
Tags []string `json:",omitempty"`
|
||||||
Port int `json:",omitempty"`
|
Port int `json:",omitempty"`
|
||||||
Address string `json:",omitempty"`
|
Address string `json:",omitempty"`
|
||||||
|
EnableTagOverride bool `json:",omitempty"`
|
||||||
Check *AgentServiceCheck
|
Check *AgentServiceCheck
|
||||||
Checks AgentServiceChecks
|
Checks AgentServiceChecks
|
||||||
}
|
}
|
||||||
|
@ -196,23 +198,43 @@ func (a *Agent) ServiceDeregister(serviceID string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PassTTL is used to set a TTL check to the passing state
|
// PassTTL is used to set a TTL check to the passing state.
|
||||||
|
//
|
||||||
|
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
||||||
|
// The client interface will be removed in 0.8 or changed to use
|
||||||
|
// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
|
||||||
func (a *Agent) PassTTL(checkID, note string) error {
|
func (a *Agent) PassTTL(checkID, note string) error {
|
||||||
return a.UpdateTTL(checkID, note, "pass")
|
return a.updateTTL(checkID, note, "pass")
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarnTTL is used to set a TTL check to the warning state
|
// WarnTTL is used to set a TTL check to the warning state.
|
||||||
|
//
|
||||||
|
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
||||||
|
// The client interface will be removed in 0.8 or changed to use
|
||||||
|
// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
|
||||||
func (a *Agent) WarnTTL(checkID, note string) error {
|
func (a *Agent) WarnTTL(checkID, note string) error {
|
||||||
return a.UpdateTTL(checkID, note, "warn")
|
return a.updateTTL(checkID, note, "warn")
|
||||||
}
|
}
|
||||||
|
|
||||||
// FailTTL is used to set a TTL check to the failing state
|
// FailTTL is used to set a TTL check to the failing state.
|
||||||
|
//
|
||||||
|
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
||||||
|
// The client interface will be removed in 0.8 or changed to use
|
||||||
|
// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
|
||||||
func (a *Agent) FailTTL(checkID, note string) error {
|
func (a *Agent) FailTTL(checkID, note string) error {
|
||||||
return a.UpdateTTL(checkID, note, "fail")
|
return a.updateTTL(checkID, note, "fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTTL is used to update the TTL of a check
|
// updateTTL is used to update the TTL of a check. This is the internal
|
||||||
func (a *Agent) UpdateTTL(checkID, note, status string) error {
|
// method that uses the old API that's present in Consul versions prior to
|
||||||
|
// 0.6.4. Since Consul didn't have an analogous "update" API before it seemed
|
||||||
|
// ok to break this (former) UpdateTTL in favor of the new UpdateTTL below,
|
||||||
|
// but keep the old Pass/Warn/Fail methods using the old API under the hood.
|
||||||
|
//
|
||||||
|
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
||||||
|
// The client interface will be removed in 0.8 and the server endpoints will
|
||||||
|
// be removed in 0.9.
|
||||||
|
func (a *Agent) updateTTL(checkID, note, status string) error {
|
||||||
switch status {
|
switch status {
|
||||||
case "pass":
|
case "pass":
|
||||||
case "warn":
|
case "warn":
|
||||||
|
@ -231,6 +253,51 @@ func (a *Agent) UpdateTTL(checkID, note, status string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkUpdate is the payload for a PUT for a check update.
|
||||||
|
type checkUpdate struct {
|
||||||
|
// Status is one of the api.Health* states: HealthPassing
|
||||||
|
// ("passing"), HealthWarning ("warning"), or HealthCritical
|
||||||
|
// ("critical").
|
||||||
|
Status string
|
||||||
|
|
||||||
|
// Output is the information to post to the UI for operators as the
|
||||||
|
// output of the process that decided to hit the TTL check. This is
|
||||||
|
// different from the note field that's associated with the check
|
||||||
|
// itself.
|
||||||
|
Output string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTTL is used to update the TTL of a check. This uses the newer API
|
||||||
|
// that was introduced in Consul 0.6.4 and later. We translate the old status
|
||||||
|
// strings for compatibility (though a newer version of Consul will still be
|
||||||
|
// required to use this API).
|
||||||
|
func (a *Agent) UpdateTTL(checkID, output, status string) error {
|
||||||
|
switch status {
|
||||||
|
case "pass", HealthPassing:
|
||||||
|
status = HealthPassing
|
||||||
|
case "warn", HealthWarning:
|
||||||
|
status = HealthWarning
|
||||||
|
case "fail", HealthCritical:
|
||||||
|
status = HealthCritical
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Invalid status: %s", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := fmt.Sprintf("/v1/agent/check/update/%s", checkID)
|
||||||
|
r := a.c.newRequest("PUT", endpoint)
|
||||||
|
r.obj = &checkUpdate{
|
||||||
|
Status: status,
|
||||||
|
Output: output,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resp, err := requireOK(a.c.doRequest(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CheckRegister is used to register a new check with
|
// CheckRegister is used to register a new check with
|
||||||
// the local agent
|
// the local agent
|
||||||
func (a *Agent) CheckRegister(check *AgentCheckRegistration) error {
|
func (a *Agent) CheckRegister(check *AgentCheckRegistration) error {
|
||||||
|
|
|
@ -3,9 +3,11 @@ package api
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -122,12 +124,58 @@ type Config struct {
|
||||||
Token string
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns a default configuration for the client
|
// TLSConfig is used to generate a TLSClientConfig that's useful for talking to
|
||||||
|
// Consul using TLS.
|
||||||
|
type TLSConfig struct {
|
||||||
|
// Address is the optional address of the Consul server. The port, if any
|
||||||
|
// will be removed from here and this will be set to the ServerName of the
|
||||||
|
// resulting config.
|
||||||
|
Address string
|
||||||
|
|
||||||
|
// CAFile is the optional path to the CA certificate used for Consul
|
||||||
|
// communication, defaults to the system bundle if not specified.
|
||||||
|
CAFile string
|
||||||
|
|
||||||
|
// CertFile is the optional path to the certificate for Consul
|
||||||
|
// communication. If this is set then you need to also set KeyFile.
|
||||||
|
CertFile string
|
||||||
|
|
||||||
|
// KeyFile is the optional path to the private key for Consul communication.
|
||||||
|
// If this is set then you need to also set CertFile.
|
||||||
|
KeyFile string
|
||||||
|
|
||||||
|
// InsecureSkipVerify if set to true will disable TLS host verification.
|
||||||
|
InsecureSkipVerify bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfig returns a default configuration for the client. By default this
|
||||||
|
// will pool and reuse idle connections to Consul. If you have a long-lived
|
||||||
|
// client object, this is the desired behavior and should make the most efficient
|
||||||
|
// use of the connections to Consul. If you don't reuse a client object , which
|
||||||
|
// is not recommended, then you may notice idle connections building up over
|
||||||
|
// time. To avoid this, use the DefaultNonPooledConfig() instead.
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
|
return defaultConfig(cleanhttp.DefaultPooledTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultNonPooledConfig returns a default configuration for the client which
|
||||||
|
// does not pool connections. This isn't a recommended configuration because it
|
||||||
|
// will reconnect to Consul on every request, but this is useful to avoid the
|
||||||
|
// accumulation of idle connections if you make many client objects during the
|
||||||
|
// lifetime of your application.
|
||||||
|
func DefaultNonPooledConfig() *Config {
|
||||||
|
return defaultConfig(cleanhttp.DefaultTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultConfig returns the default configuration for the client, using the
|
||||||
|
// given function to make the transport.
|
||||||
|
func defaultConfig(transportFn func() *http.Transport) *Config {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
Address: "127.0.0.1:8500",
|
Address: "127.0.0.1:8500",
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
HttpClient: cleanhttp.DefaultClient(),
|
HttpClient: &http.Client{
|
||||||
|
Transport: transportFn(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if addr := os.Getenv("CONSUL_HTTP_ADDR"); addr != "" {
|
if addr := os.Getenv("CONSUL_HTTP_ADDR"); addr != "" {
|
||||||
|
@ -172,10 +220,19 @@ func DefaultConfig() *Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !doVerify {
|
if !doVerify {
|
||||||
transport := cleanhttp.DefaultTransport()
|
tlsClientConfig, err := SetupTLSConfig(&TLSConfig{
|
||||||
transport.TLSClientConfig = &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// We don't expect this to fail given that we aren't
|
||||||
|
// parsing any of the input, but we panic just in case
|
||||||
|
// since this doesn't have an error return.
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transport := transportFn()
|
||||||
|
transport.TLSClientConfig = tlsClientConfig
|
||||||
config.HttpClient.Transport = transport
|
config.HttpClient.Transport = transport
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,6 +240,50 @@ func DefaultConfig() *Config {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TLSConfig is used to generate a TLSClientConfig that's useful for talking to
|
||||||
|
// Consul using TLS.
|
||||||
|
func SetupTLSConfig(tlsConfig *TLSConfig) (*tls.Config, error) {
|
||||||
|
tlsClientConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: tlsConfig.InsecureSkipVerify,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsConfig.Address != "" {
|
||||||
|
server := tlsConfig.Address
|
||||||
|
hasPort := strings.LastIndex(server, ":") > strings.LastIndex(server, "]")
|
||||||
|
if hasPort {
|
||||||
|
var err error
|
||||||
|
server, _, err = net.SplitHostPort(server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tlsClientConfig.ServerName = server
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsConfig.CertFile != "" && tlsConfig.KeyFile != "" {
|
||||||
|
tlsCert, err := tls.LoadX509KeyPair(tlsConfig.CertFile, tlsConfig.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsClientConfig.Certificates = []tls.Certificate{tlsCert}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsConfig.CAFile != "" {
|
||||||
|
data, err := ioutil.ReadFile(tlsConfig.CAFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read CA file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
caPool := x509.NewCertPool()
|
||||||
|
if !caPool.AppendCertsFromPEM(data) {
|
||||||
|
return nil, fmt.Errorf("failed to parse CA certificate")
|
||||||
|
}
|
||||||
|
tlsClientConfig.RootCAs = caPool
|
||||||
|
}
|
||||||
|
|
||||||
|
return tlsClientConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Client provides a client to the Consul API
|
// Client provides a client to the Consul API
|
||||||
type Client struct {
|
type Client struct {
|
||||||
config Config
|
config Config
|
||||||
|
|
|
@ -13,6 +13,7 @@ type CatalogService struct {
|
||||||
ServiceAddress string
|
ServiceAddress string
|
||||||
ServiceTags []string
|
ServiceTags []string
|
||||||
ServicePort int
|
ServicePort int
|
||||||
|
ServiceEnableTagOverride bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type CatalogNode struct {
|
type CatalogNode struct {
|
||||||
|
|
|
@ -4,6 +4,16 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HealthAny is special, and is used as a wild card,
|
||||||
|
// not as a specific state.
|
||||||
|
HealthAny = "any"
|
||||||
|
HealthUnknown = "unknown"
|
||||||
|
HealthPassing = "passing"
|
||||||
|
HealthWarning = "warning"
|
||||||
|
HealthCritical = "critical"
|
||||||
|
)
|
||||||
|
|
||||||
// HealthCheck is used to represent a single check
|
// HealthCheck is used to represent a single check
|
||||||
type HealthCheck struct {
|
type HealthCheck struct {
|
||||||
Node string
|
Node string
|
||||||
|
@ -85,7 +95,7 @@ func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions)
|
||||||
r.params.Set("tag", tag)
|
r.params.Set("tag", tag)
|
||||||
}
|
}
|
||||||
if passingOnly {
|
if passingOnly {
|
||||||
r.params.Set("passing", "1")
|
r.params.Set(HealthPassing, "1")
|
||||||
}
|
}
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -108,11 +118,11 @@ func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions)
|
||||||
// The wildcard "any" state can also be used for all checks.
|
// The wildcard "any" state can also be used for all checks.
|
||||||
func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||||
switch state {
|
switch state {
|
||||||
case "any":
|
case HealthAny:
|
||||||
case "warning":
|
case HealthWarning:
|
||||||
case "critical":
|
case HealthCritical:
|
||||||
case "passing":
|
case HealthPassing:
|
||||||
case "unknown":
|
case HealthUnknown:
|
||||||
default:
|
default:
|
||||||
return nil, nil, fmt.Errorf("Unsupported state: %v", state)
|
return nil, nil, fmt.Errorf("Unsupported state: %v", state)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,43 @@ type KVPair struct {
|
||||||
// KVPairs is a list of KVPair objects
|
// KVPairs is a list of KVPair objects
|
||||||
type KVPairs []*KVPair
|
type KVPairs []*KVPair
|
||||||
|
|
||||||
|
// KVOp constants give possible operations available in a KVTxn.
|
||||||
|
type KVOp string
|
||||||
|
|
||||||
|
const (
|
||||||
|
KVSet KVOp = "set"
|
||||||
|
KVDelete = "delete"
|
||||||
|
KVDeleteCAS = "delete-cas"
|
||||||
|
KVDeleteTree = "delete-tree"
|
||||||
|
KVCAS = "cas"
|
||||||
|
KVLock = "lock"
|
||||||
|
KVUnlock = "unlock"
|
||||||
|
KVGet = "get"
|
||||||
|
KVGetTree = "get-tree"
|
||||||
|
KVCheckSession = "check-session"
|
||||||
|
KVCheckIndex = "check-index"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KVTxnOp defines a single operation inside a transaction.
|
||||||
|
type KVTxnOp struct {
|
||||||
|
Verb string
|
||||||
|
Key string
|
||||||
|
Value []byte
|
||||||
|
Flags uint64
|
||||||
|
Index uint64
|
||||||
|
Session string
|
||||||
|
}
|
||||||
|
|
||||||
|
// KVTxnOps defines a set of operations to be performed inside a single
|
||||||
|
// transaction.
|
||||||
|
type KVTxnOps []*KVTxnOp
|
||||||
|
|
||||||
|
// KVTxnResponse has the outcome of a transaction.
|
||||||
|
type KVTxnResponse struct {
|
||||||
|
Results []*KVPair
|
||||||
|
Errors TxnErrors
|
||||||
|
}
|
||||||
|
|
||||||
// KV is used to manipulate the K/V API
|
// KV is used to manipulate the K/V API
|
||||||
type KV struct {
|
type KV struct {
|
||||||
c *Client
|
c *Client
|
||||||
|
@ -238,3 +275,122 @@ func (k *KV) deleteInternal(key string, params map[string]string, q *WriteOption
|
||||||
res := strings.Contains(string(buf.Bytes()), "true")
|
res := strings.Contains(string(buf.Bytes()), "true")
|
||||||
return res, qm, nil
|
return res, qm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TxnOp is the internal format we send to Consul. It's not specific to KV,
|
||||||
|
// though currently only KV operations are supported.
|
||||||
|
type TxnOp struct {
|
||||||
|
KV *KVTxnOp
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxnOps is a list of transaction operations.
|
||||||
|
type TxnOps []*TxnOp
|
||||||
|
|
||||||
|
// TxnResult is the internal format we receive from Consul.
|
||||||
|
type TxnResult struct {
|
||||||
|
KV *KVPair
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxnResults is a list of TxnResult objects.
|
||||||
|
type TxnResults []*TxnResult
|
||||||
|
|
||||||
|
// TxnError is used to return information about an operation in a transaction.
|
||||||
|
type TxnError struct {
|
||||||
|
OpIndex int
|
||||||
|
What string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxnErrors is a list of TxnError objects.
|
||||||
|
type TxnErrors []*TxnError
|
||||||
|
|
||||||
|
// TxnResponse is the internal format we receive from Consul.
|
||||||
|
type TxnResponse struct {
|
||||||
|
Results TxnResults
|
||||||
|
Errors TxnErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Txn is used to apply multiple KV operations in a single, atomic transaction.
|
||||||
|
//
|
||||||
|
// Note that Go will perform the required base64 encoding on the values
|
||||||
|
// automatically because the type is a byte slice. Transactions are defined as a
|
||||||
|
// list of operations to perform, using the KVOp constants and KVTxnOp structure
|
||||||
|
// to define operations. If any operation fails, none of the changes are applied
|
||||||
|
// to the state store. Note that this hides the internal raw transaction interface
|
||||||
|
// and munges the input and output types into KV-specific ones for ease of use.
|
||||||
|
// If there are more non-KV operations in the future we may break out a new
|
||||||
|
// transaction API client, but it will be easy to keep this KV-specific variant
|
||||||
|
// supported.
|
||||||
|
//
|
||||||
|
// Even though this is generally a write operation, we take a QueryOptions input
|
||||||
|
// and return a QueryMeta output. If the transaction contains only read ops, then
|
||||||
|
// Consul will fast-path it to a different endpoint internally which supports
|
||||||
|
// consistency controls, but not blocking. If there are write operations then
|
||||||
|
// the request will always be routed through raft and any consistency settings
|
||||||
|
// will be ignored.
|
||||||
|
//
|
||||||
|
// Here's an example:
|
||||||
|
//
|
||||||
|
// ops := KVTxnOps{
|
||||||
|
// &KVTxnOp{
|
||||||
|
// Verb: KVLock,
|
||||||
|
// Key: "test/lock",
|
||||||
|
// Session: "adf4238a-882b-9ddc-4a9d-5b6758e4159e",
|
||||||
|
// Value: []byte("hello"),
|
||||||
|
// },
|
||||||
|
// &KVTxnOp{
|
||||||
|
// Verb: KVGet,
|
||||||
|
// Key: "another/key",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// ok, response, _, err := kv.Txn(&ops, nil)
|
||||||
|
//
|
||||||
|
// If there is a problem making the transaction request then an error will be
|
||||||
|
// returned. Otherwise, the ok value will be true if the transaction succeeded
|
||||||
|
// or false if it was rolled back. The response is a structured return value which
|
||||||
|
// will have the outcome of the transaction. Its Results member will have entries
|
||||||
|
// for each operation. Deleted keys will have a nil entry in the, and to save
|
||||||
|
// space, the Value of each key in the Results will be nil unless the operation
|
||||||
|
// is a KVGet. If the transaction was rolled back, the Errors member will have
|
||||||
|
// entries referencing the index of the operation that failed along with an error
|
||||||
|
// message.
|
||||||
|
func (k *KV) Txn(txn KVTxnOps, q *QueryOptions) (bool, *KVTxnResponse, *QueryMeta, error) {
|
||||||
|
r := k.c.newRequest("PUT", "/v1/txn")
|
||||||
|
r.setQueryOptions(q)
|
||||||
|
|
||||||
|
// Convert into the internal format since this is an all-KV txn.
|
||||||
|
ops := make(TxnOps, 0, len(txn))
|
||||||
|
for _, kvOp := range txn {
|
||||||
|
ops = append(ops, &TxnOp{KV: kvOp})
|
||||||
|
}
|
||||||
|
r.obj = ops
|
||||||
|
rtt, resp, err := k.c.doRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
qm := &QueryMeta{}
|
||||||
|
parseQueryMeta(resp, qm)
|
||||||
|
qm.RequestTime = rtt
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusConflict {
|
||||||
|
var txnResp TxnResponse
|
||||||
|
if err := decodeBody(resp, &txnResp); err != nil {
|
||||||
|
return false, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from the internal format.
|
||||||
|
kvResp := KVTxnResponse{
|
||||||
|
Errors: txnResp.Errors,
|
||||||
|
}
|
||||||
|
for _, result := range txnResp.Results {
|
||||||
|
kvResp.Results = append(kvResp.Results, result.KV)
|
||||||
|
}
|
||||||
|
return resp.StatusCode == http.StatusOK, &kvResp, qm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
||||||
|
return false, nil, nil, fmt.Errorf("Failed to read response: %v", err)
|
||||||
|
}
|
||||||
|
return false, nil, nil, fmt.Errorf("Failed request: %s", buf.String())
|
||||||
|
}
|
||||||
|
|
|
@ -890,9 +890,11 @@
|
||||||
"revisionTime": "2016-07-26T16:33:11Z"
|
"revisionTime": "2016-07-26T16:33:11Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"checksumSHA1": "glOabn8rkJvz7tjz/xfX4lmt070=",
|
||||||
"comment": "v0.6.3-28-g3215b87",
|
"comment": "v0.6.3-28-g3215b87",
|
||||||
"path": "github.com/hashicorp/consul/api",
|
"path": "github.com/hashicorp/consul/api",
|
||||||
"revision": "3215b8727f44c778dd7045dcfd5ac42735c581a9"
|
"revision": "d4a8a43d2b600e662a50a75be70daed5fad8dd2d",
|
||||||
|
"revisionTime": "2016-06-04T06:35:46Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "github.com/hashicorp/errwrap",
|
"path": "github.com/hashicorp/errwrap",
|
||||||
|
|
|
@ -46,4 +46,8 @@ The following arguments are supported:
|
||||||
* `address` - (Optional) The HTTP(S) API address of the agent to use. Defaults to "127.0.0.1:8500".
|
* `address` - (Optional) The HTTP(S) API address of the agent to use. Defaults to "127.0.0.1:8500".
|
||||||
* `scheme` - (Optional) The URL scheme of the agent to use ("http" or "https"). Defaults to "http".
|
* `scheme` - (Optional) The URL scheme of the agent to use ("http" or "https"). Defaults to "http".
|
||||||
* `datacenter` - (Optional) The datacenter to use. Defaults to that of the agent.
|
* `datacenter` - (Optional) The datacenter to use. Defaults to that of the agent.
|
||||||
|
* `token` - (Optional) The ACL token to use by default when making requests to the agent.
|
||||||
|
* `ca_file` - (Optional) A path to a PEM-encoded certificate authority used to verify the remote agent's certificate.
|
||||||
|
* `cert_file` - (Optional) A path to a PEM-encoded certificate provided to the remote agent; requires use of `key_file`.
|
||||||
|
* `key_file`- (Optional) A path to a PEM-encoded private key, required if `cert_file` is specified.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue