Update vault CA for latest api client
This commit is contained in:
parent
c3bd917650
commit
316600a685
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
connect: The Vault provider will now automatically renew the lease of the token used, if supported.
|
||||||
|
```
|
|
@ -92,7 +92,7 @@ func (v *VaultProvider) Configure(cfg ProviderConfig) error {
|
||||||
|
|
||||||
// Set up a renewer to renew the token automatically, if supported.
|
// Set up a renewer to renew the token automatically, if supported.
|
||||||
if token.Renewable {
|
if token.Renewable {
|
||||||
renewer, err := client.NewRenewer(&vaultapi.RenewerInput{
|
lifetimeWatcher, err := client.NewLifetimeWatcher(&vaultapi.LifetimeWatcherInput{
|
||||||
Secret: &vaultapi.Secret{
|
Secret: &vaultapi.Secret{
|
||||||
Auth: &vaultapi.SecretAuth{
|
Auth: &vaultapi.SecretAuth{
|
||||||
ClientToken: config.Token,
|
ClientToken: config.Token,
|
||||||
|
@ -100,7 +100,8 @@ func (v *VaultProvider) Configure(cfg ProviderConfig) error {
|
||||||
LeaseDuration: secret.LeaseDuration,
|
LeaseDuration: secret.LeaseDuration,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Increment: token.TTL,
|
Increment: token.TTL,
|
||||||
|
RenewBehavior: vaultapi.RenewBehaviorIgnoreErrors,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error beginning Vault provider token renewal: %v", err)
|
return fmt.Errorf("Error beginning Vault provider token renewal: %v", err)
|
||||||
|
@ -108,31 +109,31 @@ func (v *VaultProvider) Configure(cfg ProviderConfig) error {
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
v.shutdown = cancel
|
v.shutdown = cancel
|
||||||
go v.renewToken(ctx, renewer)
|
go v.renewToken(ctx, lifetimeWatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// renewToken uses a vaultapi.Renewer to repeatedly renew our token's lease.
|
// renewToken uses a vaultapi.Renewer to repeatedly renew our token's lease.
|
||||||
func (v *VaultProvider) renewToken(ctx context.Context, renewer *vaultapi.Renewer) {
|
func (v *VaultProvider) renewToken(ctx context.Context, watcher *vaultapi.LifetimeWatcher) {
|
||||||
go renewer.Renew()
|
go watcher.Start()
|
||||||
defer renewer.Stop()
|
defer watcher.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
|
|
||||||
case err := <-renewer.DoneCh():
|
case err := <-watcher.DoneCh():
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.logger.Error("Error renewing token for Vault provider", "error", err)
|
v.logger.Error("Error renewing token for Vault provider", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renewer routine has finished, so start it again.
|
// Watcher routine has finished, so start it again.
|
||||||
go renewer.Renew()
|
go watcher.Start()
|
||||||
|
|
||||||
case <-renewer.RenewCh():
|
case <-watcher.RenewCh():
|
||||||
v.logger.Error("Successfully renewed token for Vault provider")
|
v.logger.Error("Successfully renewed token for Vault provider")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -22,7 +22,7 @@ require (
|
||||||
github.com/docker/go-connections v0.3.0
|
github.com/docker/go-connections v0.3.0
|
||||||
github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0
|
github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0
|
||||||
github.com/envoyproxy/go-control-plane v0.9.5
|
github.com/envoyproxy/go-control-plane v0.9.5
|
||||||
github.com/go-ldap/ldap v3.0.2+incompatible // indirect
|
github.com/frankban/quicktest v1.11.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.1 // indirect
|
github.com/go-ole/go-ole v1.2.1 // indirect
|
||||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d
|
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d
|
||||||
github.com/golang/protobuf v1.3.5
|
github.com/golang/protobuf v1.3.5
|
||||||
|
@ -93,7 +93,6 @@ require (
|
||||||
google.golang.org/appengine v1.6.0 // indirect
|
google.golang.org/appengine v1.6.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
|
||||||
google.golang.org/grpc v1.25.1
|
google.golang.org/grpc v1.25.1
|
||||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1
|
gopkg.in/square/go-jose.v2 v2.5.1
|
||||||
k8s.io/api v0.16.9
|
k8s.io/api v0.16.9
|
||||||
k8s.io/apimachinery v0.16.9
|
k8s.io/apimachinery v0.16.9
|
||||||
|
|
24
go.sum
24
go.sum
|
@ -137,6 +137,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
||||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
|
github.com/frankban/quicktest v1.11.0 h1:Yyrghcw93e1jKo4DTZkRFTTFvBsVhzbblBUPNU1vW6Q=
|
||||||
|
github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
|
@ -145,7 +147,6 @@ github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkPro
|
||||||
github.com/go-check/check v0.0.0-20140225173054-eb6ee6f84d0a/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
github.com/go-check/check v0.0.0-20140225173054-eb6ee6f84d0a/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
|
||||||
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
|
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
@ -188,6 +189,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
|
@ -229,7 +232,6 @@ github.com/hashicorp/go-connlimit v0.3.0/go.mod h1:OUj9FGL1tPIhl/2RCfzYHrIiWj+VV
|
||||||
github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088 h1:jBvElOilnIl6mm8S6gva/dfeTJCcMs9TGO6/2C6k52E=
|
github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088 h1:jBvElOilnIl6mm8S6gva/dfeTJCcMs9TGO6/2C6k52E=
|
||||||
github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg=
|
github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg=
|
||||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
|
||||||
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||||
github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
|
github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
|
||||||
|
@ -253,13 +255,10 @@ github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn
|
||||||
github.com/hashicorp/go-raftchunking v0.6.1 h1:moEnaG3gcwsWNyIBJoD5PCByE+Ewkqxh6N05CT+MbwA=
|
github.com/hashicorp/go-raftchunking v0.6.1 h1:moEnaG3gcwsWNyIBJoD5PCByE+Ewkqxh6N05CT+MbwA=
|
||||||
github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0=
|
github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0=
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE=
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
|
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
|
||||||
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||||
github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo=
|
github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo=
|
||||||
github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||||
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
|
@ -300,12 +299,8 @@ github.com/hashicorp/serf v0.9.3 h1:AVF6JDQQens6nMHT9OGERBvK0f8rPrAGILnsKLr6lzM=
|
||||||
github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||||
github.com/hashicorp/serf v0.9.4 h1:xrZ4ZR0wT5Dz8oQHHdfOzr0ei1jMToWlFFz3hh/DI7I=
|
github.com/hashicorp/serf v0.9.4 h1:xrZ4ZR0wT5Dz8oQHHdfOzr0ei1jMToWlFFz3hh/DI7I=
|
||||||
github.com/hashicorp/serf v0.9.4/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
github.com/hashicorp/serf v0.9.4/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||||
github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU=
|
|
||||||
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
|
|
||||||
github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086 h1:OKsyxKi2sNmqm1Gv93adf2AID2FOBFdCbbZn9fGtIdg=
|
github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086 h1:OKsyxKi2sNmqm1Gv93adf2AID2FOBFdCbbZn9fGtIdg=
|
||||||
github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk=
|
github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk=
|
||||||
github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0sMLy8=
|
|
||||||
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
|
|
||||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 h1:e1ok06zGrWJW91rzRroyl5nRNqraaBe4d5hiKcVZuHM=
|
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 h1:e1ok06zGrWJW91rzRroyl5nRNqraaBe4d5hiKcVZuHM=
|
||||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
|
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
|
||||||
github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw=
|
github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw=
|
||||||
|
@ -346,6 +341,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
@ -555,8 +552,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
|
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
|
||||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
@ -646,7 +641,6 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||||
|
@ -688,7 +682,6 @@ google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOO
|
||||||
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
@ -705,7 +698,6 @@ google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
@ -716,10 +708,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
|
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
Consul API client
|
|
||||||
=================
|
|
||||||
|
|
||||||
This package provides the `api` package which attempts to
|
|
||||||
provide programmatic access to the full Consul API.
|
|
||||||
|
|
||||||
Currently, all of the Consul APIs included in version 0.6.0 are supported.
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
=============
|
|
||||||
|
|
||||||
The full documentation is available on [Godoc](https://godoc.org/github.com/hashicorp/consul/api)
|
|
||||||
|
|
||||||
Usage
|
|
||||||
=====
|
|
||||||
|
|
||||||
Below is an example of using the Consul client:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/hashicorp/consul/api"
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Get a new client
|
|
||||||
client, err := api.NewClient(api.DefaultConfig())
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a handle to the KV API
|
|
||||||
kv := client.KV()
|
|
||||||
|
|
||||||
// PUT a new KV pair
|
|
||||||
p := &api.KVPair{Key: "REDIS_MAXCLIENTS", Value: []byte("1000")}
|
|
||||||
_, err = kv.Put(p, nil)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup the pair
|
|
||||||
pair, _, err := kv.Get("REDIS_MAXCLIENTS", nil)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("KV: %v %s\n", pair.Key, pair.Value)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To run this example, start a Consul server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
consul agent -dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the code above into a file such as `main.go`.
|
|
||||||
|
|
||||||
Install and run. You'll see a key (`REDIS_MAXCLIENTS`) and value (`1000`) printed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ go get
|
|
||||||
$ go run main.go
|
|
||||||
KV: REDIS_MAXCLIENTS 1000
|
|
||||||
```
|
|
||||||
|
|
||||||
After running the code, you can also view the values in the Consul UI on your local machine at http://localhost:8500/ui/dc1/kv
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,337 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Weights struct {
|
|
||||||
Passing int
|
|
||||||
Warning int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Node struct {
|
|
||||||
ID string
|
|
||||||
Node string
|
|
||||||
Address string
|
|
||||||
Datacenter string
|
|
||||||
TaggedAddresses map[string]string
|
|
||||||
Meta map[string]string
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceAddress struct {
|
|
||||||
Address string
|
|
||||||
Port int
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogService struct {
|
|
||||||
ID string
|
|
||||||
Node string
|
|
||||||
Address string
|
|
||||||
Datacenter string
|
|
||||||
TaggedAddresses map[string]string
|
|
||||||
NodeMeta map[string]string
|
|
||||||
ServiceID string
|
|
||||||
ServiceName string
|
|
||||||
ServiceAddress string
|
|
||||||
ServiceTaggedAddresses map[string]ServiceAddress
|
|
||||||
ServiceTags []string
|
|
||||||
ServiceMeta map[string]string
|
|
||||||
ServicePort int
|
|
||||||
ServiceWeights Weights
|
|
||||||
ServiceEnableTagOverride bool
|
|
||||||
ServiceProxy *AgentServiceConnectProxyConfig
|
|
||||||
CreateIndex uint64
|
|
||||||
Checks HealthChecks
|
|
||||||
ModifyIndex uint64
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogNode struct {
|
|
||||||
Node *Node
|
|
||||||
Services map[string]*AgentService
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogNodeServiceList struct {
|
|
||||||
Node *Node
|
|
||||||
Services []*AgentService
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogRegistration struct {
|
|
||||||
ID string
|
|
||||||
Node string
|
|
||||||
Address string
|
|
||||||
TaggedAddresses map[string]string
|
|
||||||
NodeMeta map[string]string
|
|
||||||
Datacenter string
|
|
||||||
Service *AgentService
|
|
||||||
Check *AgentCheck
|
|
||||||
Checks HealthChecks
|
|
||||||
SkipNodeUpdate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogDeregistration struct {
|
|
||||||
Node string
|
|
||||||
Address string `json:",omitempty"` // Obsolete.
|
|
||||||
Datacenter string
|
|
||||||
ServiceID string
|
|
||||||
CheckID string
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompoundServiceName struct {
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Namespacing is a Consul Enterprise feature.
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GatewayService associates a gateway with a linked service.
|
|
||||||
// It also contains service-specific gateway configuration like ingress listener port and protocol.
|
|
||||||
type GatewayService struct {
|
|
||||||
Gateway CompoundServiceName
|
|
||||||
Service CompoundServiceName
|
|
||||||
GatewayKind ServiceKind
|
|
||||||
Port int `json:",omitempty"`
|
|
||||||
Protocol string `json:",omitempty"`
|
|
||||||
Hosts []string `json:",omitempty"`
|
|
||||||
CAFile string `json:",omitempty"`
|
|
||||||
CertFile string `json:",omitempty"`
|
|
||||||
KeyFile string `json:",omitempty"`
|
|
||||||
SNI string `json:",omitempty"`
|
|
||||||
FromWildcard bool `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Catalog can be used to query the Catalog endpoints
|
|
||||||
type Catalog struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Catalog returns a handle to the catalog endpoints
|
|
||||||
func (c *Client) Catalog() *Catalog {
|
|
||||||
return &Catalog{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) Register(reg *CatalogRegistration, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("PUT", "/v1/catalog/register")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = reg
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) Deregister(dereg *CatalogDeregistration, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("PUT", "/v1/catalog/deregister")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = dereg
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Datacenters is used to query for all the known datacenters
|
|
||||||
func (c *Catalog) Datacenters() ([]string, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/datacenters")
|
|
||||||
_, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out []string
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes is used to query all the known nodes
|
|
||||||
func (c *Catalog) Nodes(q *QueryOptions) ([]*Node, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/nodes")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*Node
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Services is used to query for all known services
|
|
||||||
func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/services")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out map[string][]string
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service is used to query catalog entries for a given service
|
|
||||||
func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
var tags []string
|
|
||||||
if tag != "" {
|
|
||||||
tags = []string{tag}
|
|
||||||
}
|
|
||||||
return c.service(service, tags, q, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supports multiple tags for filtering
|
|
||||||
func (c *Catalog) ServiceMultipleTags(service string, tags []string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
return c.service(service, tags, q, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect is used to query catalog entries for a given Connect-enabled service
|
|
||||||
func (c *Catalog) Connect(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
var tags []string
|
|
||||||
if tag != "" {
|
|
||||||
tags = []string{tag}
|
|
||||||
}
|
|
||||||
return c.service(service, tags, q, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supports multiple tags for filtering
|
|
||||||
func (c *Catalog) ConnectMultipleTags(service string, tags []string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
return c.service(service, tags, q, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) service(service string, tags []string, q *QueryOptions, connect bool) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
path := "/v1/catalog/service/" + service
|
|
||||||
if connect {
|
|
||||||
path = "/v1/catalog/connect/" + service
|
|
||||||
}
|
|
||||||
r := c.c.newRequest("GET", path)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
if len(tags) > 0 {
|
|
||||||
for _, tag := range tags {
|
|
||||||
r.params.Add("tag", tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*CatalogService
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is used to query for service information about a single node
|
|
||||||
func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/node/"+node)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out *CatalogNode
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeServiceList is used to query for service information about a single node. It differs from
|
|
||||||
// the Node function only in its return type which will contain a list of services as opposed to
|
|
||||||
// a map of service ids to services. This different structure allows for using the wildcard specifier
|
|
||||||
// '*' for the Namespace in the QueryOptions.
|
|
||||||
func (c *Catalog) NodeServiceList(node string, q *QueryOptions) (*CatalogNodeServiceList, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/node-services/"+node)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out *CatalogNodeServiceList
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GatewayServices is used to query the services associated with an ingress gateway or terminating gateway.
|
|
||||||
func (c *Catalog) GatewayServices(gateway string, q *QueryOptions) ([]*GatewayService, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/gateway-services/"+gateway)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*GatewayService
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseServiceAddr(addrPort string) (ServiceAddress, error) {
|
|
||||||
port := 0
|
|
||||||
host, portStr, err := net.SplitHostPort(addrPort)
|
|
||||||
if err == nil {
|
|
||||||
port, err = strconv.Atoi(portStr)
|
|
||||||
}
|
|
||||||
return ServiceAddress{Address: host, Port: port}, err
|
|
||||||
}
|
|
|
@ -1,353 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ServiceDefaults string = "service-defaults"
|
|
||||||
ProxyDefaults string = "proxy-defaults"
|
|
||||||
ServiceRouter string = "service-router"
|
|
||||||
ServiceSplitter string = "service-splitter"
|
|
||||||
ServiceResolver string = "service-resolver"
|
|
||||||
IngressGateway string = "ingress-gateway"
|
|
||||||
TerminatingGateway string = "terminating-gateway"
|
|
||||||
|
|
||||||
ProxyConfigGlobal string = "global"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigEntry interface {
|
|
||||||
GetKind() string
|
|
||||||
GetName() string
|
|
||||||
GetCreateIndex() uint64
|
|
||||||
GetModifyIndex() uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type MeshGatewayMode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// MeshGatewayModeDefault represents no specific mode and should
|
|
||||||
// be used to indicate that a different layer of the configuration
|
|
||||||
// chain should take precedence
|
|
||||||
MeshGatewayModeDefault MeshGatewayMode = ""
|
|
||||||
|
|
||||||
// MeshGatewayModeNone represents that the Upstream Connect connections
|
|
||||||
// should be direct and not flow through a mesh gateway.
|
|
||||||
MeshGatewayModeNone MeshGatewayMode = "none"
|
|
||||||
|
|
||||||
// MeshGatewayModeLocal represents that the Upstrea Connect connections
|
|
||||||
// should be made to a mesh gateway in the local datacenter. This is
|
|
||||||
MeshGatewayModeLocal MeshGatewayMode = "local"
|
|
||||||
|
|
||||||
// MeshGatewayModeRemote represents that the Upstream Connect connections
|
|
||||||
// should be made to a mesh gateway in a remote datacenter.
|
|
||||||
MeshGatewayModeRemote MeshGatewayMode = "remote"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MeshGatewayConfig controls how Mesh Gateways are used for upstream Connect
|
|
||||||
// services
|
|
||||||
type MeshGatewayConfig struct {
|
|
||||||
// Mode is the mode that should be used for the upstream connection.
|
|
||||||
Mode MeshGatewayMode `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExposeConfig describes HTTP paths to expose through Envoy outside of Connect.
|
|
||||||
// Users can expose individual paths and/or all HTTP/GRPC paths for checks.
|
|
||||||
type ExposeConfig struct {
|
|
||||||
// Checks defines whether paths associated with Consul checks will be exposed.
|
|
||||||
// This flag triggers exposing all HTTP and GRPC check paths registered for the service.
|
|
||||||
Checks bool `json:",omitempty"`
|
|
||||||
|
|
||||||
// Paths is the list of paths exposed through the proxy.
|
|
||||||
Paths []ExposePath `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExposePath struct {
|
|
||||||
// ListenerPort defines the port of the proxy's listener for exposed paths.
|
|
||||||
ListenerPort int `json:",omitempty" alias:"listener_port"`
|
|
||||||
|
|
||||||
// Path is the path to expose through the proxy, ie. "/metrics."
|
|
||||||
Path string `json:",omitempty"`
|
|
||||||
|
|
||||||
// LocalPathPort is the port that the service is listening on for the given path.
|
|
||||||
LocalPathPort int `json:",omitempty" alias:"local_path_port"`
|
|
||||||
|
|
||||||
// Protocol describes the upstream's service protocol.
|
|
||||||
// Valid values are "http" and "http2", defaults to "http"
|
|
||||||
Protocol string `json:",omitempty"`
|
|
||||||
|
|
||||||
// ParsedFromCheck is set if this path was parsed from a registered check
|
|
||||||
ParsedFromCheck bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceConfigEntry struct {
|
|
||||||
Kind string
|
|
||||||
Name string
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
Protocol string `json:",omitempty"`
|
|
||||||
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
|
||||||
Expose ExposeConfig `json:",omitempty"`
|
|
||||||
ExternalSNI string `json:",omitempty" alias:"external_sni"`
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServiceConfigEntry) GetKind() string {
|
|
||||||
return s.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServiceConfigEntry) GetName() string {
|
|
||||||
return s.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServiceConfigEntry) GetCreateIndex() uint64 {
|
|
||||||
return s.CreateIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServiceConfigEntry) GetModifyIndex() uint64 {
|
|
||||||
return s.ModifyIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyConfigEntry struct {
|
|
||||||
Kind string
|
|
||||||
Name string
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
Config map[string]interface{} `json:",omitempty"`
|
|
||||||
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
|
||||||
Expose ExposeConfig `json:",omitempty"`
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyConfigEntry) GetKind() string {
|
|
||||||
return p.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyConfigEntry) GetName() string {
|
|
||||||
return p.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyConfigEntry) GetCreateIndex() uint64 {
|
|
||||||
return p.CreateIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyConfigEntry) GetModifyIndex() uint64 {
|
|
||||||
return p.ModifyIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeConfigEntry(kind, name string) (ConfigEntry, error) {
|
|
||||||
switch kind {
|
|
||||||
case ServiceDefaults:
|
|
||||||
return &ServiceConfigEntry{Kind: kind, Name: name}, nil
|
|
||||||
case ProxyDefaults:
|
|
||||||
return &ProxyConfigEntry{Kind: kind, Name: name}, nil
|
|
||||||
case ServiceRouter:
|
|
||||||
return &ServiceRouterConfigEntry{Kind: kind, Name: name}, nil
|
|
||||||
case ServiceSplitter:
|
|
||||||
return &ServiceSplitterConfigEntry{Kind: kind, Name: name}, nil
|
|
||||||
case ServiceResolver:
|
|
||||||
return &ServiceResolverConfigEntry{Kind: kind, Name: name}, nil
|
|
||||||
case IngressGateway:
|
|
||||||
return &IngressGatewayConfigEntry{Kind: kind, Name: name}, nil
|
|
||||||
case TerminatingGateway:
|
|
||||||
return &TerminatingGatewayConfigEntry{Kind: kind, Name: name}, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeConfigEntry(kind, name string) (ConfigEntry, error) {
|
|
||||||
return makeConfigEntry(kind, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeConfigEntry will decode the result of using json.Unmarshal of a config
|
|
||||||
// entry into a map[string]interface{}.
|
|
||||||
//
|
|
||||||
// Important caveats:
|
|
||||||
//
|
|
||||||
// - This will NOT work if the map[string]interface{} was produced using HCL
|
|
||||||
// decoding as that requires more extensive parsing to work around the issues
|
|
||||||
// with map[string][]interface{} that arise.
|
|
||||||
//
|
|
||||||
// - This will only decode fields using their camel case json field
|
|
||||||
// representations.
|
|
||||||
func DecodeConfigEntry(raw map[string]interface{}) (ConfigEntry, error) {
|
|
||||||
var entry ConfigEntry
|
|
||||||
|
|
||||||
kindVal, ok := raw["Kind"]
|
|
||||||
if !ok {
|
|
||||||
kindVal, ok = raw["kind"]
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("Payload does not contain a kind/Kind key at the top level")
|
|
||||||
}
|
|
||||||
|
|
||||||
if kindStr, ok := kindVal.(string); ok {
|
|
||||||
newEntry, err := makeConfigEntry(kindStr, "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
entry = newEntry
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("Kind value in payload is not a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeConf := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
||||||
Result: &entry,
|
|
||||||
WeaklyTypedInput: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder, err := mapstructure.NewDecoder(decodeConf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry, decoder.Decode(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecodeConfigEntryFromJSON(data []byte) (ConfigEntry, error) {
|
|
||||||
var raw map[string]interface{}
|
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return DecodeConfigEntry(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeConfigEntrySlice(raw []map[string]interface{}) ([]ConfigEntry, error) {
|
|
||||||
var entries []ConfigEntry
|
|
||||||
for _, rawEntry := range raw {
|
|
||||||
entry, err := DecodeConfigEntry(rawEntry)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
entries = append(entries, entry)
|
|
||||||
}
|
|
||||||
return entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigEntries can be used to query the Config endpoints
|
|
||||||
type ConfigEntries struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config returns a handle to the Config endpoints
|
|
||||||
func (c *Client) ConfigEntries() *ConfigEntries {
|
|
||||||
return &ConfigEntries{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) Get(kind string, name string, q *QueryOptions) (ConfigEntry, *QueryMeta, error) {
|
|
||||||
if kind == "" || name == "" {
|
|
||||||
return nil, nil, fmt.Errorf("Both kind and name parameters must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
entry, err := makeConfigEntry(kind, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := conf.c.newRequest("GET", fmt.Sprintf("/v1/config/%s/%s", kind, name))
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(conf.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if err := decodeBody(resp, entry); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) List(kind string, q *QueryOptions) ([]ConfigEntry, *QueryMeta, error) {
|
|
||||||
if kind == "" {
|
|
||||||
return nil, nil, fmt.Errorf("The kind parameter must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
r := conf.c.newRequest("GET", fmt.Sprintf("/v1/config/%s", kind))
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(conf.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var raw []map[string]interface{}
|
|
||||||
if err := decodeBody(resp, &raw); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
entries, err := decodeConfigEntrySlice(raw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) Set(entry ConfigEntry, w *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
return conf.set(entry, nil, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) CAS(entry ConfigEntry, index uint64, w *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
return conf.set(entry, map[string]string{"cas": strconv.FormatUint(index, 10)}, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) set(entry ConfigEntry, params map[string]string, w *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
r := conf.c.newRequest("PUT", "/v1/config")
|
|
||||||
r.setWriteOptions(w)
|
|
||||||
for param, value := range params {
|
|
||||||
r.params.Set(param, value)
|
|
||||||
}
|
|
||||||
r.obj = entry
|
|
||||||
rtt, resp, err := requireOK(conf.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
|
||||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
res := strings.Contains(buf.String(), "true")
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
return res, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) Delete(kind string, name string, w *WriteOptions) (*WriteMeta, error) {
|
|
||||||
if kind == "" || name == "" {
|
|
||||||
return nil, fmt.Errorf("Both kind and name parameters must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
r := conf.c.newRequest("DELETE", fmt.Sprintf("/v1/config/%s/%s", kind, name))
|
|
||||||
r.setWriteOptions(w)
|
|
||||||
rtt, resp, err := requireOK(conf.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
|
@ -1,203 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ServiceRouterConfigEntry struct {
|
|
||||||
Kind string
|
|
||||||
Name string
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
|
|
||||||
Routes []ServiceRoute `json:",omitempty"`
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ServiceRouterConfigEntry) GetKind() string { return e.Kind }
|
|
||||||
func (e *ServiceRouterConfigEntry) GetName() string { return e.Name }
|
|
||||||
func (e *ServiceRouterConfigEntry) GetCreateIndex() uint64 { return e.CreateIndex }
|
|
||||||
func (e *ServiceRouterConfigEntry) GetModifyIndex() uint64 { return e.ModifyIndex }
|
|
||||||
|
|
||||||
type ServiceRoute struct {
|
|
||||||
Match *ServiceRouteMatch `json:",omitempty"`
|
|
||||||
Destination *ServiceRouteDestination `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceRouteMatch struct {
|
|
||||||
HTTP *ServiceRouteHTTPMatch `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceRouteHTTPMatch struct {
|
|
||||||
PathExact string `json:",omitempty" alias:"path_exact"`
|
|
||||||
PathPrefix string `json:",omitempty" alias:"path_prefix"`
|
|
||||||
PathRegex string `json:",omitempty" alias:"path_regex"`
|
|
||||||
|
|
||||||
Header []ServiceRouteHTTPMatchHeader `json:",omitempty"`
|
|
||||||
QueryParam []ServiceRouteHTTPMatchQueryParam `json:",omitempty" alias:"query_param"`
|
|
||||||
Methods []string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceRouteHTTPMatchHeader struct {
|
|
||||||
Name string
|
|
||||||
Present bool `json:",omitempty"`
|
|
||||||
Exact string `json:",omitempty"`
|
|
||||||
Prefix string `json:",omitempty"`
|
|
||||||
Suffix string `json:",omitempty"`
|
|
||||||
Regex string `json:",omitempty"`
|
|
||||||
Invert bool `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceRouteHTTPMatchQueryParam struct {
|
|
||||||
Name string
|
|
||||||
Present bool `json:",omitempty"`
|
|
||||||
Exact string `json:",omitempty"`
|
|
||||||
Regex string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceRouteDestination struct {
|
|
||||||
Service string `json:",omitempty"`
|
|
||||||
ServiceSubset string `json:",omitempty" alias:"service_subset"`
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
PrefixRewrite string `json:",omitempty" alias:"prefix_rewrite"`
|
|
||||||
RequestTimeout time.Duration `json:",omitempty" alias:"request_timeout"`
|
|
||||||
NumRetries uint32 `json:",omitempty" alias:"num_retries"`
|
|
||||||
RetryOnConnectFailure bool `json:",omitempty" alias:"retry_on_connect_failure"`
|
|
||||||
RetryOnStatusCodes []uint32 `json:",omitempty" alias:"retry_on_status_codes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ServiceRouteDestination) MarshalJSON() ([]byte, error) {
|
|
||||||
type Alias ServiceRouteDestination
|
|
||||||
exported := &struct {
|
|
||||||
RequestTimeout string `json:",omitempty"`
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
RequestTimeout: e.RequestTimeout.String(),
|
|
||||||
Alias: (*Alias)(e),
|
|
||||||
}
|
|
||||||
if e.RequestTimeout == 0 {
|
|
||||||
exported.RequestTimeout = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(exported)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ServiceRouteDestination) UnmarshalJSON(data []byte) error {
|
|
||||||
type Alias ServiceRouteDestination
|
|
||||||
aux := &struct {
|
|
||||||
RequestTimeout string
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
Alias: (*Alias)(e),
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if aux.RequestTimeout != "" {
|
|
||||||
if e.RequestTimeout, err = time.ParseDuration(aux.RequestTimeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceSplitterConfigEntry struct {
|
|
||||||
Kind string
|
|
||||||
Name string
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
|
|
||||||
Splits []ServiceSplit `json:",omitempty"`
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ServiceSplitterConfigEntry) GetKind() string { return e.Kind }
|
|
||||||
func (e *ServiceSplitterConfigEntry) GetName() string { return e.Name }
|
|
||||||
func (e *ServiceSplitterConfigEntry) GetCreateIndex() uint64 { return e.CreateIndex }
|
|
||||||
func (e *ServiceSplitterConfigEntry) GetModifyIndex() uint64 { return e.ModifyIndex }
|
|
||||||
|
|
||||||
type ServiceSplit struct {
|
|
||||||
Weight float32
|
|
||||||
Service string `json:",omitempty"`
|
|
||||||
ServiceSubset string `json:",omitempty" alias:"service_subset"`
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceResolverConfigEntry struct {
|
|
||||||
Kind string
|
|
||||||
Name string
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
|
|
||||||
DefaultSubset string `json:",omitempty" alias:"default_subset"`
|
|
||||||
Subsets map[string]ServiceResolverSubset `json:",omitempty"`
|
|
||||||
Redirect *ServiceResolverRedirect `json:",omitempty"`
|
|
||||||
Failover map[string]ServiceResolverFailover `json:",omitempty"`
|
|
||||||
ConnectTimeout time.Duration `json:",omitempty" alias:"connect_timeout"`
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ServiceResolverConfigEntry) MarshalJSON() ([]byte, error) {
|
|
||||||
type Alias ServiceResolverConfigEntry
|
|
||||||
exported := &struct {
|
|
||||||
ConnectTimeout string `json:",omitempty"`
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
ConnectTimeout: e.ConnectTimeout.String(),
|
|
||||||
Alias: (*Alias)(e),
|
|
||||||
}
|
|
||||||
if e.ConnectTimeout == 0 {
|
|
||||||
exported.ConnectTimeout = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(exported)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ServiceResolverConfigEntry) UnmarshalJSON(data []byte) error {
|
|
||||||
type Alias ServiceResolverConfigEntry
|
|
||||||
aux := &struct {
|
|
||||||
ConnectTimeout string
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
Alias: (*Alias)(e),
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if aux.ConnectTimeout != "" {
|
|
||||||
if e.ConnectTimeout, err = time.ParseDuration(aux.ConnectTimeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ServiceResolverConfigEntry) GetKind() string { return e.Kind }
|
|
||||||
func (e *ServiceResolverConfigEntry) GetName() string { return e.Name }
|
|
||||||
func (e *ServiceResolverConfigEntry) GetCreateIndex() uint64 { return e.CreateIndex }
|
|
||||||
func (e *ServiceResolverConfigEntry) GetModifyIndex() uint64 { return e.ModifyIndex }
|
|
||||||
|
|
||||||
type ServiceResolverSubset struct {
|
|
||||||
Filter string `json:",omitempty"`
|
|
||||||
OnlyPassing bool `json:",omitempty" alias:"only_passing"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceResolverRedirect struct {
|
|
||||||
Service string `json:",omitempty"`
|
|
||||||
ServiceSubset string `json:",omitempty" alias:"service_subset"`
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
Datacenter string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceResolverFailover struct {
|
|
||||||
Service string `json:",omitempty"`
|
|
||||||
ServiceSubset string `json:",omitempty" alias:"service_subset"`
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
Datacenters []string `json:",omitempty"`
|
|
||||||
}
|
|
|
@ -1,170 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
// IngressGatewayConfigEntry manages the configuration for an ingress service
|
|
||||||
// with the given name.
|
|
||||||
type IngressGatewayConfigEntry struct {
|
|
||||||
// Kind of the config entry. This should be set to api.IngressGateway.
|
|
||||||
Kind string
|
|
||||||
|
|
||||||
// Name is used to match the config entry with its associated ingress gateway
|
|
||||||
// service. This should match the name provided in the service definition.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Namespace is the namespace the IngressGateway is associated with
|
|
||||||
// Namespacing is a Consul Enterprise feature.
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
|
|
||||||
// TLS holds the TLS configuration for this gateway.
|
|
||||||
TLS GatewayTLSConfig
|
|
||||||
|
|
||||||
// Listeners declares what ports the ingress gateway should listen on, and
|
|
||||||
// what services to associated to those ports.
|
|
||||||
Listeners []IngressListener
|
|
||||||
|
|
||||||
// CreateIndex is the Raft index this entry was created at. This is a
|
|
||||||
// read-only field.
|
|
||||||
CreateIndex uint64
|
|
||||||
|
|
||||||
// ModifyIndex is used for the Check-And-Set operations and can also be fed
|
|
||||||
// back into the WaitIndex of the QueryOptions in order to perform blocking
|
|
||||||
// queries.
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type GatewayTLSConfig struct {
|
|
||||||
// Indicates that TLS should be enabled for this gateway service
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IngressListener manages the configuration for a listener on a specific port.
|
|
||||||
type IngressListener struct {
|
|
||||||
// Port declares the port on which the ingress gateway should listen for traffic.
|
|
||||||
Port int
|
|
||||||
|
|
||||||
// Protocol declares what type of traffic this listener is expected to
|
|
||||||
// receive. Depending on the protocol, a listener might support multiplexing
|
|
||||||
// services over a single port, or additional discovery chain features. The
|
|
||||||
// current supported values are: (tcp | http).
|
|
||||||
Protocol string
|
|
||||||
|
|
||||||
// Services declares the set of services to which the listener forwards
|
|
||||||
// traffic.
|
|
||||||
//
|
|
||||||
// For "tcp" protocol listeners, only a single service is allowed.
|
|
||||||
// For "http" listeners, multiple services can be declared.
|
|
||||||
Services []IngressService
|
|
||||||
}
|
|
||||||
|
|
||||||
// IngressService manages configuration for services that are exposed to
|
|
||||||
// ingress traffic.
|
|
||||||
type IngressService struct {
|
|
||||||
// Name declares the service to which traffic should be forwarded.
|
|
||||||
//
|
|
||||||
// This can either be a specific service, or the wildcard specifier,
|
|
||||||
// "*". If the wildcard specifier is provided, the listener must be of "http"
|
|
||||||
// protocol and means that the listener will forward traffic to all services.
|
|
||||||
//
|
|
||||||
// A name can be specified on multiple listeners, and will be exposed on both
|
|
||||||
// of the listeners
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Hosts is a list of hostnames which should be associated to this service on
|
|
||||||
// the defined listener. Only allowed on layer 7 protocols, this will be used
|
|
||||||
// to route traffic to the service by matching the Host header of the HTTP
|
|
||||||
// request.
|
|
||||||
//
|
|
||||||
// If a host is provided for a service that also has a wildcard specifier
|
|
||||||
// defined, the host will override the wildcard-specifier-provided
|
|
||||||
// "<service-name>.*" domain for that listener.
|
|
||||||
//
|
|
||||||
// This cannot be specified when using the wildcard specifier, "*", or when
|
|
||||||
// using a "tcp" listener.
|
|
||||||
Hosts []string
|
|
||||||
|
|
||||||
// Namespace is the namespace where the service is located.
|
|
||||||
// Namespacing is a Consul Enterprise feature.
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IngressGatewayConfigEntry) GetKind() string {
|
|
||||||
return i.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IngressGatewayConfigEntry) GetName() string {
|
|
||||||
return i.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IngressGatewayConfigEntry) GetCreateIndex() uint64 {
|
|
||||||
return i.CreateIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IngressGatewayConfigEntry) GetModifyIndex() uint64 {
|
|
||||||
return i.ModifyIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// TerminatingGatewayConfigEntry manages the configuration for a terminating gateway
|
|
||||||
// with the given name.
|
|
||||||
type TerminatingGatewayConfigEntry struct {
|
|
||||||
// Kind of the config entry. This should be set to api.TerminatingGateway.
|
|
||||||
Kind string
|
|
||||||
|
|
||||||
// Name is used to match the config entry with its associated terminating gateway
|
|
||||||
// service. This should match the name provided in the service definition.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Services is a list of service names represented by the terminating gateway.
|
|
||||||
Services []LinkedService `json:",omitempty"`
|
|
||||||
|
|
||||||
// CreateIndex is the Raft index this entry was created at. This is a
|
|
||||||
// read-only field.
|
|
||||||
CreateIndex uint64
|
|
||||||
|
|
||||||
// ModifyIndex is used for the Check-And-Set operations and can also be fed
|
|
||||||
// back into the WaitIndex of the QueryOptions in order to perform blocking
|
|
||||||
// queries.
|
|
||||||
ModifyIndex uint64
|
|
||||||
|
|
||||||
// Namespace is the namespace the config entry is associated with
|
|
||||||
// Namespacing is a Consul Enterprise feature.
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A LinkedService is a service represented by a terminating gateway
|
|
||||||
type LinkedService struct {
|
|
||||||
// The namespace the service is registered in
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
|
|
||||||
// Name is the name of the service, as defined in Consul's catalog
|
|
||||||
Name string `json:",omitempty"`
|
|
||||||
|
|
||||||
// CAFile is the optional path to a CA certificate to use for TLS connections
|
|
||||||
// from the gateway to the linked service
|
|
||||||
CAFile string `json:",omitempty" alias:"ca_file"`
|
|
||||||
|
|
||||||
// CertFile is the optional path to a client certificate to use for TLS connections
|
|
||||||
// from the gateway to the linked service
|
|
||||||
CertFile string `json:",omitempty" alias:"cert_file"`
|
|
||||||
|
|
||||||
// KeyFile is the optional path to a private key to use for TLS connections
|
|
||||||
// from the gateway to the linked service
|
|
||||||
KeyFile string `json:",omitempty" alias:"key_file"`
|
|
||||||
|
|
||||||
// SNI is the optional name to specify during the TLS handshake with a linked service
|
|
||||||
SNI string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *TerminatingGatewayConfigEntry) GetKind() string {
|
|
||||||
return g.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *TerminatingGatewayConfigEntry) GetName() string {
|
|
||||||
return g.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *TerminatingGatewayConfigEntry) GetCreateIndex() uint64 {
|
|
||||||
return g.CreateIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *TerminatingGatewayConfigEntry) GetModifyIndex() uint64 {
|
|
||||||
return g.ModifyIndex
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
// Connect can be used to work with endpoints related to Connect, the
|
|
||||||
// feature for securely connecting services within Consul.
|
|
||||||
type Connect struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect returns a handle to the connect-related endpoints
|
|
||||||
func (c *Client) Connect() *Connect {
|
|
||||||
return &Connect{c}
|
|
||||||
}
|
|
|
@ -1,181 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CAConfig is the structure for the Connect CA configuration.
|
|
||||||
type CAConfig struct {
|
|
||||||
// Provider is the CA provider implementation to use.
|
|
||||||
Provider string
|
|
||||||
|
|
||||||
// Configuration is arbitrary configuration for the provider. This
|
|
||||||
// should only contain primitive values and containers (such as lists
|
|
||||||
// and maps).
|
|
||||||
Config map[string]interface{}
|
|
||||||
|
|
||||||
// State is read-only data that the provider might have persisted for use
|
|
||||||
// after restart or leadership transition. For example this might include
|
|
||||||
// UUIDs of resources it has created. Setting this when writing a
|
|
||||||
// configuration is an error.
|
|
||||||
State map[string]string
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommonCAProviderConfig is the common options available to all CA providers.
|
|
||||||
type CommonCAProviderConfig struct {
|
|
||||||
LeafCertTTL time.Duration
|
|
||||||
SkipValidate bool
|
|
||||||
CSRMaxPerSecond float32
|
|
||||||
CSRMaxConcurrent int
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConsulCAProviderConfig is the config for the built-in Consul CA provider.
|
|
||||||
type ConsulCAProviderConfig struct {
|
|
||||||
CommonCAProviderConfig `mapstructure:",squash"`
|
|
||||||
|
|
||||||
PrivateKey string
|
|
||||||
RootCert string
|
|
||||||
RotationPeriod time.Duration
|
|
||||||
IntermediateCertTTL time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseConsulCAConfig takes a raw config map and returns a parsed
|
|
||||||
// ConsulCAProviderConfig.
|
|
||||||
func ParseConsulCAConfig(raw map[string]interface{}) (*ConsulCAProviderConfig, error) {
|
|
||||||
var config ConsulCAProviderConfig
|
|
||||||
decodeConf := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
||||||
Result: &config,
|
|
||||||
WeaklyTypedInput: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder, err := mapstructure.NewDecoder(decodeConf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := decoder.Decode(raw); err != nil {
|
|
||||||
return nil, fmt.Errorf("error decoding config: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CARootList is the structure for the results of listing roots.
|
|
||||||
type CARootList struct {
|
|
||||||
ActiveRootID string
|
|
||||||
TrustDomain string
|
|
||||||
Roots []*CARoot
|
|
||||||
}
|
|
||||||
|
|
||||||
// CARoot represents a root CA certificate that is trusted.
|
|
||||||
type CARoot struct {
|
|
||||||
// ID is a globally unique ID (UUID) representing this CA root.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Name is a human-friendly name for this CA root. This value is
|
|
||||||
// opaque to Consul and is not used for anything internally.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// RootCertPEM is the PEM-encoded public certificate.
|
|
||||||
RootCertPEM string `json:"RootCert"`
|
|
||||||
|
|
||||||
// Active is true if this is the current active CA. This must only
|
|
||||||
// be true for exactly one CA. For any method that modifies roots in the
|
|
||||||
// state store, tests should be written to verify that multiple roots
|
|
||||||
// cannot be active.
|
|
||||||
Active bool
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// LeafCert is a certificate that has been issued by a Connect CA.
|
|
||||||
type LeafCert struct {
|
|
||||||
// SerialNumber is the unique serial number for this certificate.
|
|
||||||
// This is encoded in standard hex separated by :.
|
|
||||||
SerialNumber string
|
|
||||||
|
|
||||||
// CertPEM and PrivateKeyPEM are the PEM-encoded certificate and private
|
|
||||||
// key for that cert, respectively. This should not be stored in the
|
|
||||||
// state store, but is present in the sign API response.
|
|
||||||
CertPEM string `json:",omitempty"`
|
|
||||||
PrivateKeyPEM string `json:",omitempty"`
|
|
||||||
|
|
||||||
// Service is the name of the service for which the cert was issued.
|
|
||||||
// ServiceURI is the cert URI value.
|
|
||||||
Service string
|
|
||||||
ServiceURI string
|
|
||||||
|
|
||||||
// ValidAfter and ValidBefore are the validity periods for the
|
|
||||||
// certificate.
|
|
||||||
ValidAfter time.Time
|
|
||||||
ValidBefore time.Time
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// CARoots queries the list of available roots.
|
|
||||||
func (h *Connect) CARoots(q *QueryOptions) (*CARootList, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/ca/roots")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out CARootList
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return &out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CAGetConfig returns the current CA configuration.
|
|
||||||
func (h *Connect) CAGetConfig(q *QueryOptions) (*CAConfig, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/ca/configuration")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out CAConfig
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return &out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CASetConfig sets the current CA configuration.
|
|
||||||
func (h *Connect) CASetConfig(conf *CAConfig, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := h.c.newRequest("PUT", "/v1/connect/ca/configuration")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = conf
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
|
@ -1,344 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Intention defines an intention for the Connect Service Graph. This defines
|
|
||||||
// the allowed or denied behavior of a connection between two services using
|
|
||||||
// Connect.
|
|
||||||
type Intention struct {
|
|
||||||
// ID is the UUID-based ID for the intention, always generated by Consul.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Description is a human-friendly description of this intention.
|
|
||||||
// It is opaque to Consul and is only stored and transferred in API
|
|
||||||
// requests.
|
|
||||||
Description string
|
|
||||||
|
|
||||||
// SourceNS, SourceName are the namespace and name, respectively, of
|
|
||||||
// the source service. Either of these may be the wildcard "*", but only
|
|
||||||
// the full value can be a wildcard. Partial wildcards are not allowed.
|
|
||||||
// The source may also be a non-Consul service, as specified by SourceType.
|
|
||||||
//
|
|
||||||
// DestinationNS, DestinationName is the same, but for the destination
|
|
||||||
// service. The same rules apply. The destination is always a Consul
|
|
||||||
// service.
|
|
||||||
SourceNS, SourceName string
|
|
||||||
DestinationNS, DestinationName string
|
|
||||||
|
|
||||||
// SourceType is the type of the value for the source.
|
|
||||||
SourceType IntentionSourceType
|
|
||||||
|
|
||||||
// Action is whether this is an allowlist or denylist intention.
|
|
||||||
Action IntentionAction
|
|
||||||
|
|
||||||
// DefaultAddr is not used.
|
|
||||||
// Deprecated: DefaultAddr is not used and may be removed in a future version.
|
|
||||||
DefaultAddr string `json:",omitempty"`
|
|
||||||
// DefaultPort is not used.
|
|
||||||
// Deprecated: DefaultPort is not used and may be removed in a future version.
|
|
||||||
DefaultPort int `json:",omitempty"`
|
|
||||||
|
|
||||||
// Meta is arbitrary metadata associated with the intention. This is
|
|
||||||
// opaque to Consul but is served in API responses.
|
|
||||||
Meta map[string]string
|
|
||||||
|
|
||||||
// Precedence is the order that the intention will be applied, with
|
|
||||||
// larger numbers being applied first. This is a read-only field, on
|
|
||||||
// any intention update it is updated.
|
|
||||||
Precedence int
|
|
||||||
|
|
||||||
// CreatedAt and UpdatedAt keep track of when this record was created
|
|
||||||
// or modified.
|
|
||||||
CreatedAt, UpdatedAt time.Time
|
|
||||||
|
|
||||||
// Hash of the contents of the intention
|
|
||||||
//
|
|
||||||
// This is needed mainly for replication purposes. When replicating from
|
|
||||||
// one DC to another keeping the content Hash will allow us to detect
|
|
||||||
// content changes more efficiently than checking every single field
|
|
||||||
Hash []byte
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns human-friendly output describing ths intention.
|
|
||||||
func (i *Intention) String() string {
|
|
||||||
return fmt.Sprintf("%s => %s (%s)",
|
|
||||||
i.SourceString(),
|
|
||||||
i.DestinationString(),
|
|
||||||
i.Action)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SourceString returns the namespace/name format for the source, or
|
|
||||||
// just "name" if the namespace is the default namespace.
|
|
||||||
func (i *Intention) SourceString() string {
|
|
||||||
return i.partString(i.SourceNS, i.SourceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DestinationString returns the namespace/name format for the source, or
|
|
||||||
// just "name" if the namespace is the default namespace.
|
|
||||||
func (i *Intention) DestinationString() string {
|
|
||||||
return i.partString(i.DestinationNS, i.DestinationName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Intention) partString(ns, n string) string {
|
|
||||||
// For now we omit the default namespace from the output. In the future
|
|
||||||
// we might want to look at this and show this in a multi-namespace world.
|
|
||||||
if ns != "" && ns != IntentionDefaultNamespace {
|
|
||||||
n = ns + "/" + n
|
|
||||||
}
|
|
||||||
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionDefaultNamespace is the default namespace value.
|
|
||||||
const IntentionDefaultNamespace = "default"
|
|
||||||
|
|
||||||
// IntentionAction is the action that the intention represents. This
|
|
||||||
// can be "allow" or "deny" to allowlist or denylist intentions.
|
|
||||||
type IntentionAction string
|
|
||||||
|
|
||||||
const (
|
|
||||||
IntentionActionAllow IntentionAction = "allow"
|
|
||||||
IntentionActionDeny IntentionAction = "deny"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IntentionSourceType is the type of the source within an intention.
|
|
||||||
type IntentionSourceType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// IntentionSourceConsul is a service within the Consul catalog.
|
|
||||||
IntentionSourceConsul IntentionSourceType = "consul"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IntentionMatch are the arguments for the intention match API.
|
|
||||||
type IntentionMatch struct {
|
|
||||||
By IntentionMatchType
|
|
||||||
Names []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionMatchType is the target for a match request. For example,
|
|
||||||
// matching by source will look for all intentions that match the given
|
|
||||||
// source value.
|
|
||||||
type IntentionMatchType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
IntentionMatchSource IntentionMatchType = "source"
|
|
||||||
IntentionMatchDestination IntentionMatchType = "destination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IntentionCheck are the arguments for the intention check API. For
|
|
||||||
// more documentation see the IntentionCheck function.
|
|
||||||
type IntentionCheck struct {
|
|
||||||
// Source and Destination are the source and destination values to
|
|
||||||
// check. The destination is always a Consul service, but the source
|
|
||||||
// may be other values as defined by the SourceType.
|
|
||||||
Source, Destination string
|
|
||||||
|
|
||||||
// SourceType is the type of the value for the source.
|
|
||||||
SourceType IntentionSourceType
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intentions returns the list of intentions.
|
|
||||||
func (h *Connect) Intentions(q *QueryOptions) ([]*Intention, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/intentions")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*Intention
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionGet retrieves a single intention.
|
|
||||||
func (h *Connect) IntentionGet(id string, q *QueryOptions) (*Intention, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/intentions/"+id)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := h.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if resp.StatusCode == 404 {
|
|
||||||
return nil, qm, nil
|
|
||||||
} else if resp.StatusCode != 200 {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
io.Copy(&buf, resp.Body)
|
|
||||||
return nil, nil, fmt.Errorf(
|
|
||||||
"Unexpected response %d: %s", resp.StatusCode, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var out Intention
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return &out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionDelete deletes a single intention.
|
|
||||||
func (h *Connect) IntentionDelete(id string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := h.c.newRequest("DELETE", "/v1/connect/intentions/"+id)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &WriteMeta{}
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
return qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionMatch returns the list of intentions that match a given source
|
|
||||||
// or destination. The returned intentions are ordered by precedence where
|
|
||||||
// result[0] is the highest precedence (if that matches, then that rule overrides
|
|
||||||
// all other rules).
|
|
||||||
//
|
|
||||||
// Matching can be done for multiple names at the same time. The resulting
|
|
||||||
// map is keyed by the given names. Casing is preserved.
|
|
||||||
func (h *Connect) IntentionMatch(args *IntentionMatch, q *QueryOptions) (map[string][]*Intention, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/intentions/match")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
r.params.Set("by", string(args.By))
|
|
||||||
for _, name := range args.Names {
|
|
||||||
r.params.Add("name", name)
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out map[string][]*Intention
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionCheck returns whether a given source/destination would be allowed
|
|
||||||
// or not given the current set of intentions and the configuration of Consul.
|
|
||||||
func (h *Connect) IntentionCheck(args *IntentionCheck, q *QueryOptions) (bool, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/intentions/check")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
r.params.Set("source", args.Source)
|
|
||||||
r.params.Set("destination", args.Destination)
|
|
||||||
if args.SourceType != "" {
|
|
||||||
r.params.Set("source-type", string(args.SourceType))
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out struct{ Allowed bool }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
return out.Allowed, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionGetExact retrieves a single intention by its unique name instead of
|
|
||||||
// its ID.
|
|
||||||
func (h *Connect) IntentionGetExact(source, destination string, q *QueryOptions) (*Intention, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/intentions/exact")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
r.params.Set("source", source)
|
|
||||||
r.params.Set("destination", destination)
|
|
||||||
rtt, resp, err := h.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if resp.StatusCode == 404 {
|
|
||||||
return nil, qm, nil
|
|
||||||
} else if resp.StatusCode != 200 {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
io.Copy(&buf, resp.Body)
|
|
||||||
return nil, nil, fmt.Errorf(
|
|
||||||
"Unexpected response %d: %s", resp.StatusCode, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var out Intention
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return &out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionCreate will create a new intention. The ID in the given
|
|
||||||
// structure must be empty and a generate ID will be returned on
|
|
||||||
// success.
|
|
||||||
func (c *Connect) IntentionCreate(ixn *Intention, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("POST", "/v1/connect/intentions")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = ixn
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionUpdate will update an existing intention. The ID in the given
|
|
||||||
// structure must be non-empty.
|
|
||||||
func (c *Connect) IntentionUpdate(ixn *Intention, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("PUT", "/v1/connect/intentions/"+ixn.ID)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = ixn
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/serf/coordinate"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CoordinateEntry represents a node and its associated network coordinate.
|
|
||||||
type CoordinateEntry struct {
|
|
||||||
Node string
|
|
||||||
Segment string
|
|
||||||
Coord *coordinate.Coordinate
|
|
||||||
}
|
|
||||||
|
|
||||||
// CoordinateDatacenterMap has the coordinates for servers in a given datacenter
|
|
||||||
// and area. Network coordinates are only compatible within the same area.
|
|
||||||
type CoordinateDatacenterMap struct {
|
|
||||||
Datacenter string
|
|
||||||
AreaID string
|
|
||||||
Coordinates []CoordinateEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
// Coordinate can be used to query the coordinate endpoints
|
|
||||||
type Coordinate struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Coordinate returns a handle to the coordinate endpoints
|
|
||||||
func (c *Client) Coordinate() *Coordinate {
|
|
||||||
return &Coordinate{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Datacenters is used to return the coordinates of all the servers in the WAN
|
|
||||||
// pool.
|
|
||||||
func (c *Coordinate) Datacenters() ([]*CoordinateDatacenterMap, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/coordinate/datacenters")
|
|
||||||
_, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out []*CoordinateDatacenterMap
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes is used to return the coordinates of all the nodes in the LAN pool.
|
|
||||||
func (c *Coordinate) Nodes(q *QueryOptions) ([]*CoordinateEntry, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/coordinate/nodes")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*CoordinateEntry
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update inserts or updates the LAN coordinate of a node.
|
|
||||||
func (c *Coordinate) Update(coord *CoordinateEntry, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("PUT", "/v1/coordinate/update")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = coord
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is used to return the coordinates of a single node in the LAN pool.
|
|
||||||
func (c *Coordinate) Node(node string, q *QueryOptions) ([]*CoordinateEntry, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/coordinate/node/"+node)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*CoordinateEntry
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Debug can be used to query the /debug/pprof endpoints to gather
|
|
||||||
// profiling information about the target agent.Debug
|
|
||||||
//
|
|
||||||
// The agent must have enable_debug set to true for profiling to be enabled
|
|
||||||
// and for these endpoints to function.
|
|
||||||
type Debug struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug returns a handle that exposes the internal debug endpoints.
|
|
||||||
func (c *Client) Debug() *Debug {
|
|
||||||
return &Debug{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Heap returns a pprof heap dump
|
|
||||||
func (d *Debug) Heap() ([]byte, error) {
|
|
||||||
r := d.c.newRequest("GET", "/debug/pprof/heap")
|
|
||||||
_, resp, err := d.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error making request: %s", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// We return a raw response because we're just passing through a response
|
|
||||||
// from the pprof handlers
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error decoding body: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Profile returns a pprof CPU profile for the specified number of seconds
|
|
||||||
func (d *Debug) Profile(seconds int) ([]byte, error) {
|
|
||||||
r := d.c.newRequest("GET", "/debug/pprof/profile")
|
|
||||||
|
|
||||||
// Capture a profile for the specified number of seconds
|
|
||||||
r.params.Set("seconds", strconv.Itoa(seconds))
|
|
||||||
|
|
||||||
_, resp, err := d.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error making request: %s", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// We return a raw response because we're just passing through a response
|
|
||||||
// from the pprof handlers
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error decoding body: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace returns an execution trace
|
|
||||||
func (d *Debug) Trace(seconds int) ([]byte, error) {
|
|
||||||
r := d.c.newRequest("GET", "/debug/pprof/trace")
|
|
||||||
|
|
||||||
// Capture a trace for the specified number of seconds
|
|
||||||
r.params.Set("seconds", strconv.Itoa(seconds))
|
|
||||||
|
|
||||||
_, resp, err := d.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error making request: %s", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// We return a raw response because we're just passing through a response
|
|
||||||
// from the pprof handlers
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error decoding body: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Goroutine returns a pprof goroutine profile
|
|
||||||
func (d *Debug) Goroutine() ([]byte, error) {
|
|
||||||
r := d.c.newRequest("GET", "/debug/pprof/goroutine")
|
|
||||||
|
|
||||||
_, resp, err := d.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error making request: %s", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// We return a raw response because we're just passing through a response
|
|
||||||
// from the pprof handlers
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error decoding body: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, nil
|
|
||||||
}
|
|
|
@ -1,229 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DiscoveryChain can be used to query the discovery-chain endpoints
|
|
||||||
type DiscoveryChain struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscoveryChain returns a handle to the discovery-chain endpoints
|
|
||||||
func (c *Client) DiscoveryChain() *DiscoveryChain {
|
|
||||||
return &DiscoveryChain{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DiscoveryChain) Get(name string, opts *DiscoveryChainOptions, q *QueryOptions) (*DiscoveryChainResponse, *QueryMeta, error) {
|
|
||||||
if name == "" {
|
|
||||||
return nil, nil, fmt.Errorf("Name parameter must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
method := "GET"
|
|
||||||
if opts != nil && opts.requiresPOST() {
|
|
||||||
method = "POST"
|
|
||||||
}
|
|
||||||
|
|
||||||
r := d.c.newRequest(method, fmt.Sprintf("/v1/discovery-chain/%s", name))
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
|
|
||||||
if opts != nil {
|
|
||||||
if opts.EvaluateInDatacenter != "" {
|
|
||||||
r.params.Set("compile-dc", opts.EvaluateInDatacenter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if method == "POST" {
|
|
||||||
r.obj = opts
|
|
||||||
}
|
|
||||||
|
|
||||||
rtt, resp, err := requireOK(d.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out DiscoveryChainResponse
|
|
||||||
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DiscoveryChainOptions struct {
|
|
||||||
EvaluateInDatacenter string `json:"-"`
|
|
||||||
|
|
||||||
// OverrideMeshGateway allows for the mesh gateway setting to be overridden
|
|
||||||
// for any resolver in the compiled chain.
|
|
||||||
OverrideMeshGateway MeshGatewayConfig `json:",omitempty"`
|
|
||||||
|
|
||||||
// OverrideProtocol allows for the final protocol for the chain to be
|
|
||||||
// altered.
|
|
||||||
//
|
|
||||||
// - If the chain ordinarily would be TCP and an L7 protocol is passed here
|
|
||||||
// the chain will not include Routers or Splitters.
|
|
||||||
//
|
|
||||||
// - If the chain ordinarily would be L7 and TCP is passed here the chain
|
|
||||||
// will not include Routers or Splitters.
|
|
||||||
OverrideProtocol string `json:",omitempty"`
|
|
||||||
|
|
||||||
// OverrideConnectTimeout allows for the ConnectTimeout setting to be
|
|
||||||
// overridden for any resolver in the compiled chain.
|
|
||||||
OverrideConnectTimeout time.Duration `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *DiscoveryChainOptions) requiresPOST() bool {
|
|
||||||
if o == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return o.OverrideMeshGateway.Mode != "" ||
|
|
||||||
o.OverrideProtocol != "" ||
|
|
||||||
o.OverrideConnectTimeout != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type DiscoveryChainResponse struct {
|
|
||||||
Chain *CompiledDiscoveryChain
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompiledDiscoveryChain struct {
|
|
||||||
ServiceName string
|
|
||||||
Namespace string
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// CustomizationHash is a unique hash of any data that affects the
|
|
||||||
// compilation of the discovery chain other than config entries or the
|
|
||||||
// name/namespace/datacenter evaluation criteria.
|
|
||||||
//
|
|
||||||
// If set, this value should be used to prefix/suffix any generated load
|
|
||||||
// balancer data plane objects to avoid sharing customized and
|
|
||||||
// non-customized versions.
|
|
||||||
CustomizationHash string
|
|
||||||
|
|
||||||
// Protocol is the overall protocol shared by everything in the chain.
|
|
||||||
Protocol string
|
|
||||||
|
|
||||||
// StartNode is the first key into the Nodes map that should be followed
|
|
||||||
// when walking the discovery chain.
|
|
||||||
StartNode string
|
|
||||||
|
|
||||||
// Nodes contains all nodes available for traversal in the chain keyed by a
|
|
||||||
// unique name. You can walk this by starting with StartNode.
|
|
||||||
//
|
|
||||||
// NOTE: The names should be treated as opaque values and are only
|
|
||||||
// guaranteed to be consistent within a single compilation.
|
|
||||||
Nodes map[string]*DiscoveryGraphNode
|
|
||||||
|
|
||||||
// Targets is a list of all targets used in this chain.
|
|
||||||
//
|
|
||||||
// NOTE: The names should be treated as opaque values and are only
|
|
||||||
// guaranteed to be consistent within a single compilation.
|
|
||||||
Targets map[string]*DiscoveryTarget
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
DiscoveryGraphNodeTypeRouter = "router"
|
|
||||||
DiscoveryGraphNodeTypeSplitter = "splitter"
|
|
||||||
DiscoveryGraphNodeTypeResolver = "resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DiscoveryGraphNode is a single node in the compiled discovery chain.
|
|
||||||
type DiscoveryGraphNode struct {
|
|
||||||
Type string
|
|
||||||
Name string // this is NOT necessarily a service
|
|
||||||
|
|
||||||
// fields for Type==router
|
|
||||||
Routes []*DiscoveryRoute
|
|
||||||
|
|
||||||
// fields for Type==splitter
|
|
||||||
Splits []*DiscoverySplit
|
|
||||||
|
|
||||||
// fields for Type==resolver
|
|
||||||
Resolver *DiscoveryResolver
|
|
||||||
}
|
|
||||||
|
|
||||||
// compiled form of ServiceRoute
|
|
||||||
type DiscoveryRoute struct {
|
|
||||||
Definition *ServiceRoute
|
|
||||||
NextNode string
|
|
||||||
}
|
|
||||||
|
|
||||||
// compiled form of ServiceSplit
|
|
||||||
type DiscoverySplit struct {
|
|
||||||
Weight float32
|
|
||||||
NextNode string
|
|
||||||
}
|
|
||||||
|
|
||||||
// compiled form of ServiceResolverConfigEntry
|
|
||||||
type DiscoveryResolver struct {
|
|
||||||
Default bool
|
|
||||||
ConnectTimeout time.Duration
|
|
||||||
Target string
|
|
||||||
Failover *DiscoveryFailover
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DiscoveryResolver) MarshalJSON() ([]byte, error) {
|
|
||||||
type Alias DiscoveryResolver
|
|
||||||
exported := &struct {
|
|
||||||
ConnectTimeout string `json:",omitempty"`
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
ConnectTimeout: r.ConnectTimeout.String(),
|
|
||||||
Alias: (*Alias)(r),
|
|
||||||
}
|
|
||||||
if r.ConnectTimeout == 0 {
|
|
||||||
exported.ConnectTimeout = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(exported)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DiscoveryResolver) UnmarshalJSON(data []byte) error {
|
|
||||||
type Alias DiscoveryResolver
|
|
||||||
aux := &struct {
|
|
||||||
ConnectTimeout string
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
Alias: (*Alias)(r),
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if aux.ConnectTimeout != "" {
|
|
||||||
if r.ConnectTimeout, err = time.ParseDuration(aux.ConnectTimeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// compiled form of ServiceResolverFailover
|
|
||||||
type DiscoveryFailover struct {
|
|
||||||
Targets []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscoveryTarget represents all of the inputs necessary to use a resolver
|
|
||||||
// config entry to execute a catalog query to generate a list of service
|
|
||||||
// instances during discovery.
|
|
||||||
type DiscoveryTarget struct {
|
|
||||||
ID string
|
|
||||||
|
|
||||||
Service string
|
|
||||||
ServiceSubset string
|
|
||||||
Namespace string
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
MeshGateway MeshGatewayConfig
|
|
||||||
Subset ServiceResolverSubset
|
|
||||||
External bool
|
|
||||||
SNI string
|
|
||||||
Name string
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Event can be used to query the Event endpoints
|
|
||||||
type Event struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserEvent represents an event that was fired by the user
|
|
||||||
type UserEvent struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Payload []byte
|
|
||||||
NodeFilter string
|
|
||||||
ServiceFilter string
|
|
||||||
TagFilter string
|
|
||||||
Version int
|
|
||||||
LTime uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event returns a handle to the event endpoints
|
|
||||||
func (c *Client) Event() *Event {
|
|
||||||
return &Event{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire is used to fire a new user event. Only the Name, Payload and Filters
|
|
||||||
// are respected. This returns the ID or an associated error. Cross DC requests
|
|
||||||
// are supported.
|
|
||||||
func (e *Event) Fire(params *UserEvent, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := e.c.newRequest("PUT", "/v1/event/fire/"+params.Name)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
if params.NodeFilter != "" {
|
|
||||||
r.params.Set("node", params.NodeFilter)
|
|
||||||
}
|
|
||||||
if params.ServiceFilter != "" {
|
|
||||||
r.params.Set("service", params.ServiceFilter)
|
|
||||||
}
|
|
||||||
if params.TagFilter != "" {
|
|
||||||
r.params.Set("tag", params.TagFilter)
|
|
||||||
}
|
|
||||||
if params.Payload != nil {
|
|
||||||
r.body = bytes.NewReader(params.Payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
var out UserEvent
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List is used to get the most recent events an agent has received.
|
|
||||||
// This list can be optionally filtered by the name. This endpoint supports
|
|
||||||
// quasi-blocking queries. The index is not monotonic, nor does it provide provide
|
|
||||||
// LastContact or KnownLeader.
|
|
||||||
func (e *Event) List(name string, q *QueryOptions) ([]*UserEvent, *QueryMeta, error) {
|
|
||||||
r := e.c.newRequest("GET", "/v1/event/list")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
if name != "" {
|
|
||||||
r.params.Set("name", name)
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var entries []*UserEvent
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDToIndex is a bit of a hack. This simulates the index generation to
|
|
||||||
// convert an event ID into a WaitIndex.
|
|
||||||
func (e *Event) IDToIndex(uuid string) uint64 {
|
|
||||||
lower := uuid[0:8] + uuid[9:13] + uuid[14:18]
|
|
||||||
upper := uuid[19:23] + uuid[24:36]
|
|
||||||
lowVal, err := strconv.ParseUint(lower, 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to convert " + lower)
|
|
||||||
}
|
|
||||||
highVal, err := strconv.ParseUint(upper, 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to convert " + upper)
|
|
||||||
}
|
|
||||||
return lowVal ^ highVal
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
module github.com/hashicorp/consul/api
|
|
||||||
|
|
||||||
go 1.12
|
|
||||||
|
|
||||||
replace github.com/hashicorp/consul/sdk => ../sdk
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/hashicorp/consul/sdk v0.6.0
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1
|
|
||||||
github.com/hashicorp/go-hclog v0.12.0
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1
|
|
||||||
github.com/hashicorp/serf v0.9.3
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2
|
|
||||||
github.com/stretchr/testify v1.4.0
|
|
||||||
)
|
|
|
@ -1,131 +0,0 @@
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
|
||||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
|
|
||||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
|
||||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
|
||||||
github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g=
|
|
||||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
|
||||||
github.com/hashicorp/serf v0.9.3 h1:AVF6JDQQens6nMHT9OGERBvK0f8rPrAGILnsKLr6lzM=
|
|
||||||
github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
|
||||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
|
||||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
|
||||||
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
|
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
|
||||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
|
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5 h1:x6r4Jo0KNzOOzYd8lbcRsqjuqEASK6ob3auvWYM4/8U=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
|
|
||||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
|
@ -1,375 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// HealthAny is special, and is used as a wild card,
|
|
||||||
// not as a specific state.
|
|
||||||
HealthAny = "any"
|
|
||||||
HealthPassing = "passing"
|
|
||||||
HealthWarning = "warning"
|
|
||||||
HealthCritical = "critical"
|
|
||||||
HealthMaint = "maintenance"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
serviceHealth = "service"
|
|
||||||
connectHealth = "connect"
|
|
||||||
ingressHealth = "ingress"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NodeMaint is the special key set by a node in maintenance mode.
|
|
||||||
NodeMaint = "_node_maintenance"
|
|
||||||
|
|
||||||
// ServiceMaintPrefix is the prefix for a service in maintenance mode.
|
|
||||||
ServiceMaintPrefix = "_service_maintenance:"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HealthCheck is used to represent a single check
|
|
||||||
type HealthCheck struct {
|
|
||||||
Node string
|
|
||||||
CheckID string
|
|
||||||
Name string
|
|
||||||
Status string
|
|
||||||
Notes string
|
|
||||||
Output string
|
|
||||||
ServiceID string
|
|
||||||
ServiceName string
|
|
||||||
ServiceTags []string
|
|
||||||
Type string
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
|
|
||||||
Definition HealthCheckDefinition
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealthCheckDefinition is used to store the details about
|
|
||||||
// a health check's execution.
|
|
||||||
type HealthCheckDefinition struct {
|
|
||||||
HTTP string
|
|
||||||
Header map[string][]string
|
|
||||||
Method string
|
|
||||||
Body string
|
|
||||||
TLSSkipVerify bool
|
|
||||||
TCP string
|
|
||||||
IntervalDuration time.Duration `json:"-"`
|
|
||||||
TimeoutDuration time.Duration `json:"-"`
|
|
||||||
DeregisterCriticalServiceAfterDuration time.Duration `json:"-"`
|
|
||||||
|
|
||||||
// DEPRECATED in Consul 1.4.1. Use the above time.Duration fields instead.
|
|
||||||
Interval ReadableDuration
|
|
||||||
Timeout ReadableDuration
|
|
||||||
DeregisterCriticalServiceAfter ReadableDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) {
|
|
||||||
type Alias HealthCheckDefinition
|
|
||||||
out := &struct {
|
|
||||||
Interval string
|
|
||||||
Timeout string
|
|
||||||
DeregisterCriticalServiceAfter string
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
Interval: d.Interval.String(),
|
|
||||||
Timeout: d.Timeout.String(),
|
|
||||||
DeregisterCriticalServiceAfter: d.DeregisterCriticalServiceAfter.String(),
|
|
||||||
Alias: (*Alias)(d),
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.IntervalDuration != 0 {
|
|
||||||
out.Interval = d.IntervalDuration.String()
|
|
||||||
} else if d.Interval != 0 {
|
|
||||||
out.Interval = d.Interval.String()
|
|
||||||
}
|
|
||||||
if d.TimeoutDuration != 0 {
|
|
||||||
out.Timeout = d.TimeoutDuration.String()
|
|
||||||
} else if d.Timeout != 0 {
|
|
||||||
out.Timeout = d.Timeout.String()
|
|
||||||
}
|
|
||||||
if d.DeregisterCriticalServiceAfterDuration != 0 {
|
|
||||||
out.DeregisterCriticalServiceAfter = d.DeregisterCriticalServiceAfterDuration.String()
|
|
||||||
} else if d.DeregisterCriticalServiceAfter != 0 {
|
|
||||||
out.DeregisterCriticalServiceAfter = d.DeregisterCriticalServiceAfter.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *HealthCheckDefinition) UnmarshalJSON(data []byte) (err error) {
|
|
||||||
type Alias HealthCheckDefinition
|
|
||||||
aux := &struct {
|
|
||||||
IntervalDuration interface{}
|
|
||||||
TimeoutDuration interface{}
|
|
||||||
DeregisterCriticalServiceAfterDuration interface{}
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
Alias: (*Alias)(t),
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the values into both the time.Duration and old ReadableDuration fields.
|
|
||||||
|
|
||||||
if aux.IntervalDuration == nil {
|
|
||||||
t.IntervalDuration = time.Duration(t.Interval)
|
|
||||||
} else {
|
|
||||||
switch v := aux.IntervalDuration.(type) {
|
|
||||||
case string:
|
|
||||||
if t.IntervalDuration, err = time.ParseDuration(v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
t.IntervalDuration = time.Duration(v)
|
|
||||||
}
|
|
||||||
t.Interval = ReadableDuration(t.IntervalDuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
if aux.TimeoutDuration == nil {
|
|
||||||
t.TimeoutDuration = time.Duration(t.Timeout)
|
|
||||||
} else {
|
|
||||||
switch v := aux.TimeoutDuration.(type) {
|
|
||||||
case string:
|
|
||||||
if t.TimeoutDuration, err = time.ParseDuration(v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
t.TimeoutDuration = time.Duration(v)
|
|
||||||
}
|
|
||||||
t.Timeout = ReadableDuration(t.TimeoutDuration)
|
|
||||||
}
|
|
||||||
if aux.DeregisterCriticalServiceAfterDuration == nil {
|
|
||||||
t.DeregisterCriticalServiceAfterDuration = time.Duration(t.DeregisterCriticalServiceAfter)
|
|
||||||
} else {
|
|
||||||
switch v := aux.DeregisterCriticalServiceAfterDuration.(type) {
|
|
||||||
case string:
|
|
||||||
if t.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
t.DeregisterCriticalServiceAfterDuration = time.Duration(v)
|
|
||||||
}
|
|
||||||
t.DeregisterCriticalServiceAfter = ReadableDuration(t.DeregisterCriticalServiceAfterDuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealthChecks is a collection of HealthCheck structs.
|
|
||||||
type HealthChecks []*HealthCheck
|
|
||||||
|
|
||||||
// AggregatedStatus returns the "best" status for the list of health checks.
|
|
||||||
// Because a given entry may have many service and node-level health checks
|
|
||||||
// attached, this function determines the best representative of the status as
|
|
||||||
// as single string using the following heuristic:
|
|
||||||
//
|
|
||||||
// maintenance > critical > warning > passing
|
|
||||||
//
|
|
||||||
func (c HealthChecks) AggregatedStatus() string {
|
|
||||||
var passing, warning, critical, maintenance bool
|
|
||||||
for _, check := range c {
|
|
||||||
id := check.CheckID
|
|
||||||
if id == NodeMaint || strings.HasPrefix(id, ServiceMaintPrefix) {
|
|
||||||
maintenance = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch check.Status {
|
|
||||||
case HealthPassing:
|
|
||||||
passing = true
|
|
||||||
case HealthWarning:
|
|
||||||
warning = true
|
|
||||||
case HealthCritical:
|
|
||||||
critical = true
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case maintenance:
|
|
||||||
return HealthMaint
|
|
||||||
case critical:
|
|
||||||
return HealthCritical
|
|
||||||
case warning:
|
|
||||||
return HealthWarning
|
|
||||||
case passing:
|
|
||||||
return HealthPassing
|
|
||||||
default:
|
|
||||||
return HealthPassing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceEntry is used for the health service endpoint
|
|
||||||
type ServiceEntry struct {
|
|
||||||
Node *Node
|
|
||||||
Service *AgentService
|
|
||||||
Checks HealthChecks
|
|
||||||
}
|
|
||||||
|
|
||||||
// Health can be used to query the Health endpoints
|
|
||||||
type Health struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Health returns a handle to the health endpoints
|
|
||||||
func (c *Client) Health() *Health {
|
|
||||||
return &Health{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is used to query for checks belonging to a given node
|
|
||||||
func (h *Health) Node(node string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/health/node/"+node)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out HealthChecks
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks is used to return the checks associated with a service
|
|
||||||
func (h *Health) Checks(service string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/health/checks/"+service)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out HealthChecks
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service is used to query health information along with service info
|
|
||||||
// for a given service. It can optionally do server-side filtering on a tag
|
|
||||||
// or nodes with passing health checks only.
|
|
||||||
func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
var tags []string
|
|
||||||
if tag != "" {
|
|
||||||
tags = []string{tag}
|
|
||||||
}
|
|
||||||
return h.service(service, tags, passingOnly, q, serviceHealth)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Health) ServiceMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
return h.service(service, tags, passingOnly, q, serviceHealth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect is equivalent to Service except that it will only return services
|
|
||||||
// which are Connect-enabled and will returns the connection address for Connect
|
|
||||||
// client's to use which may be a proxy in front of the named service. If
|
|
||||||
// passingOnly is true only instances where both the service and any proxy are
|
|
||||||
// healthy will be returned.
|
|
||||||
func (h *Health) Connect(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
var tags []string
|
|
||||||
if tag != "" {
|
|
||||||
tags = []string{tag}
|
|
||||||
}
|
|
||||||
return h.service(service, tags, passingOnly, q, connectHealth)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Health) ConnectMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
return h.service(service, tags, passingOnly, q, connectHealth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ingress is equivalent to Connect except that it will only return associated
|
|
||||||
// ingress gateways for the requested service.
|
|
||||||
func (h *Health) Ingress(service string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
var tags []string
|
|
||||||
return h.service(service, tags, passingOnly, q, ingressHealth)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Health) service(service string, tags []string, passingOnly bool, q *QueryOptions, healthType string) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
var path string
|
|
||||||
switch healthType {
|
|
||||||
case connectHealth:
|
|
||||||
path = "/v1/health/connect/" + service
|
|
||||||
case ingressHealth:
|
|
||||||
path = "/v1/health/ingress/" + service
|
|
||||||
default:
|
|
||||||
path = "/v1/health/service/" + service
|
|
||||||
}
|
|
||||||
|
|
||||||
r := h.c.newRequest("GET", path)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
if len(tags) > 0 {
|
|
||||||
for _, tag := range tags {
|
|
||||||
r.params.Add("tag", tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if passingOnly {
|
|
||||||
r.params.Set(HealthPassing, "1")
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*ServiceEntry
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// State is used to retrieve all the checks in a given state.
|
|
||||||
// The wildcard "any" state can also be used for all checks.
|
|
||||||
func (h *Health) State(state string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
|
|
||||||
switch state {
|
|
||||||
case HealthAny:
|
|
||||||
case HealthWarning:
|
|
||||||
case HealthCritical:
|
|
||||||
case HealthPassing:
|
|
||||||
default:
|
|
||||||
return nil, nil, fmt.Errorf("Unsupported state: %v", state)
|
|
||||||
}
|
|
||||||
r := h.c.newRequest("GET", "/v1/health/state/"+state)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out HealthChecks
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
|
@ -1,290 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KVPair is used to represent a single K/V entry
|
|
||||||
type KVPair struct {
|
|
||||||
// Key is the name of the key. It is also part of the URL path when accessed
|
|
||||||
// via the API.
|
|
||||||
Key string
|
|
||||||
|
|
||||||
// CreateIndex holds the index corresponding the creation of this KVPair. This
|
|
||||||
// is a read-only field.
|
|
||||||
CreateIndex uint64
|
|
||||||
|
|
||||||
// ModifyIndex is used for the Check-And-Set operations and can also be fed
|
|
||||||
// back into the WaitIndex of the QueryOptions in order to perform blocking
|
|
||||||
// queries.
|
|
||||||
ModifyIndex uint64
|
|
||||||
|
|
||||||
// LockIndex holds the index corresponding to a lock on this key, if any. This
|
|
||||||
// is a read-only field.
|
|
||||||
LockIndex uint64
|
|
||||||
|
|
||||||
// Flags are any user-defined flags on the key. It is up to the implementer
|
|
||||||
// to check these values, since Consul does not treat them specially.
|
|
||||||
Flags uint64
|
|
||||||
|
|
||||||
// Value is the value for the key. This can be any value, but it will be
|
|
||||||
// base64 encoded upon transport.
|
|
||||||
Value []byte
|
|
||||||
|
|
||||||
// Session is a string representing the ID of the session. Any other
|
|
||||||
// interactions with this key over the same session must specify the same
|
|
||||||
// session ID.
|
|
||||||
Session string
|
|
||||||
|
|
||||||
// Namespace is the namespace the KVPair is associated with
|
|
||||||
// Namespacing is a Consul Enterprise feature.
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// KVPairs is a list of KVPair objects
|
|
||||||
type KVPairs []*KVPair
|
|
||||||
|
|
||||||
// KV is used to manipulate the K/V API
|
|
||||||
type KV struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// KV is used to return a handle to the K/V apis
|
|
||||||
func (c *Client) KV() *KV {
|
|
||||||
return &KV{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get is used to lookup a single key. The returned pointer
|
|
||||||
// to the KVPair will be nil if the key does not exist.
|
|
||||||
func (k *KV) Get(key string, q *QueryOptions) (*KVPair, *QueryMeta, error) {
|
|
||||||
resp, qm, err := k.getInternal(key, nil, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var entries []*KVPair
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if len(entries) > 0 {
|
|
||||||
return entries[0], qm, nil
|
|
||||||
}
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List is used to lookup all keys under a prefix
|
|
||||||
func (k *KV) List(prefix string, q *QueryOptions) (KVPairs, *QueryMeta, error) {
|
|
||||||
resp, qm, err := k.getInternal(prefix, map[string]string{"recurse": ""}, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var entries []*KVPair
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys is used to list all the keys under a prefix. Optionally,
|
|
||||||
// a separator can be used to limit the responses.
|
|
||||||
func (k *KV) Keys(prefix, separator string, q *QueryOptions) ([]string, *QueryMeta, error) {
|
|
||||||
params := map[string]string{"keys": ""}
|
|
||||||
if separator != "" {
|
|
||||||
params["separator"] = separator
|
|
||||||
}
|
|
||||||
resp, qm, err := k.getInternal(prefix, params, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var entries []string
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KV) getInternal(key string, params map[string]string, q *QueryOptions) (*http.Response, *QueryMeta, error) {
|
|
||||||
r := k.c.newRequest("GET", "/v1/kv/"+strings.TrimPrefix(key, "/"))
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
for param, val := range params {
|
|
||||||
r.params.Set(param, val)
|
|
||||||
}
|
|
||||||
rtt, resp, err := k.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if resp.StatusCode == 404 {
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil, qm, nil
|
|
||||||
} else if resp.StatusCode != 200 {
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
return resp, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put is used to write a new value. Only the
|
|
||||||
// Key, Flags and Value is respected.
|
|
||||||
func (k *KV) Put(p *KVPair, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 1)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
_, wm, err := k.put(p.Key, params, p.Value, q)
|
|
||||||
return wm, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CAS is used for a Check-And-Set operation. The Key,
|
|
||||||
// ModifyIndex, Flags and Value are respected. Returns true
|
|
||||||
// on success or false on failures.
|
|
||||||
func (k *KV) CAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 2)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
params["cas"] = strconv.FormatUint(p.ModifyIndex, 10)
|
|
||||||
return k.put(p.Key, params, p.Value, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acquire is used for a lock acquisition operation. The Key,
|
|
||||||
// Flags, Value and Session are respected. Returns true
|
|
||||||
// on success or false on failures.
|
|
||||||
func (k *KV) Acquire(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 2)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
params["acquire"] = p.Session
|
|
||||||
return k.put(p.Key, params, p.Value, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release is used for a lock release operation. The Key,
|
|
||||||
// Flags, Value and Session are respected. Returns true
|
|
||||||
// on success or false on failures.
|
|
||||||
func (k *KV) Release(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 2)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
params["release"] = p.Session
|
|
||||||
return k.put(p.Key, params, p.Value, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KV) put(key string, params map[string]string, body []byte, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
if len(key) > 0 && key[0] == '/' {
|
|
||||||
return false, nil, fmt.Errorf("Invalid key. Key must not begin with a '/': %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := k.c.newRequest("PUT", "/v1/kv/"+key)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
for param, val := range params {
|
|
||||||
r.params.Set(param, val)
|
|
||||||
}
|
|
||||||
r.body = bytes.NewReader(body)
|
|
||||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &WriteMeta{}
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
|
||||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
res := strings.Contains(buf.String(), "true")
|
|
||||||
return res, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete is used to delete a single key
|
|
||||||
func (k *KV) Delete(key string, w *WriteOptions) (*WriteMeta, error) {
|
|
||||||
_, qm, err := k.deleteInternal(key, nil, w)
|
|
||||||
return qm, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteCAS is used for a Delete Check-And-Set operation. The Key
|
|
||||||
// and ModifyIndex are respected. Returns true on success or false on failures.
|
|
||||||
func (k *KV) DeleteCAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := map[string]string{
|
|
||||||
"cas": strconv.FormatUint(p.ModifyIndex, 10),
|
|
||||||
}
|
|
||||||
return k.deleteInternal(p.Key, params, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTree is used to delete all keys under a prefix
|
|
||||||
func (k *KV) DeleteTree(prefix string, w *WriteOptions) (*WriteMeta, error) {
|
|
||||||
_, qm, err := k.deleteInternal(prefix, map[string]string{"recurse": ""}, w)
|
|
||||||
return qm, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KV) deleteInternal(key string, params map[string]string, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
r := k.c.newRequest("DELETE", "/v1/kv/"+strings.TrimPrefix(key, "/"))
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
for param, val := range params {
|
|
||||||
r.params.Set(param, val)
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &WriteMeta{}
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
|
||||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
res := strings.Contains(buf.String(), "true")
|
|
||||||
return res, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Txn function has been deprecated from the KV object; please see the Txn
|
|
||||||
// object for more information about Transactions.
|
|
||||||
func (k *KV) Txn(txn KVTxnOps, q *QueryOptions) (bool, *KVTxnResponse, *QueryMeta, error) {
|
|
||||||
var ops TxnOps
|
|
||||||
for _, op := range txn {
|
|
||||||
ops = append(ops, &TxnOp{KV: op})
|
|
||||||
}
|
|
||||||
|
|
||||||
respOk, txnResp, qm, err := k.c.txn(ops, q)
|
|
||||||
if 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 respOk, &kvResp, qm, nil
|
|
||||||
}
|
|
|
@ -1,406 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultLockSessionName is the Session Name we assign if none is provided
|
|
||||||
DefaultLockSessionName = "Consul API Lock"
|
|
||||||
|
|
||||||
// DefaultLockSessionTTL is the default session TTL if no Session is provided
|
|
||||||
// when creating a new Lock. This is used because we do not have another
|
|
||||||
// other check to depend upon.
|
|
||||||
DefaultLockSessionTTL = "15s"
|
|
||||||
|
|
||||||
// DefaultLockWaitTime is how long we block for at a time to check if lock
|
|
||||||
// acquisition is possible. This affects the minimum time it takes to cancel
|
|
||||||
// a Lock acquisition.
|
|
||||||
DefaultLockWaitTime = 15 * time.Second
|
|
||||||
|
|
||||||
// DefaultLockRetryTime is how long we wait after a failed lock acquisition
|
|
||||||
// before attempting to do the lock again. This is so that once a lock-delay
|
|
||||||
// is in effect, we do not hot loop retrying the acquisition.
|
|
||||||
DefaultLockRetryTime = 5 * time.Second
|
|
||||||
|
|
||||||
// DefaultMonitorRetryTime is how long we wait after a failed monitor check
|
|
||||||
// of a lock (500 response code). This allows the monitor to ride out brief
|
|
||||||
// periods of unavailability, subject to the MonitorRetries setting in the
|
|
||||||
// lock options which is by default set to 0, disabling this feature. This
|
|
||||||
// affects locks and semaphores.
|
|
||||||
DefaultMonitorRetryTime = 2 * time.Second
|
|
||||||
|
|
||||||
// LockFlagValue is a magic flag we set to indicate a key
|
|
||||||
// is being used for a lock. It is used to detect a potential
|
|
||||||
// conflict with a semaphore.
|
|
||||||
LockFlagValue = 0x2ddccbc058a50c18
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrLockHeld is returned if we attempt to double lock
|
|
||||||
ErrLockHeld = fmt.Errorf("Lock already held")
|
|
||||||
|
|
||||||
// ErrLockNotHeld is returned if we attempt to unlock a lock
|
|
||||||
// that we do not hold.
|
|
||||||
ErrLockNotHeld = fmt.Errorf("Lock not held")
|
|
||||||
|
|
||||||
// ErrLockInUse is returned if we attempt to destroy a lock
|
|
||||||
// that is in use.
|
|
||||||
ErrLockInUse = fmt.Errorf("Lock in use")
|
|
||||||
|
|
||||||
// ErrLockConflict is returned if the flags on a key
|
|
||||||
// used for a lock do not match expectation
|
|
||||||
ErrLockConflict = fmt.Errorf("Existing key does not match lock use")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Lock is used to implement client-side leader election. It is follows the
|
|
||||||
// algorithm as described here: https://www.consul.io/docs/guides/leader-election.html.
|
|
||||||
type Lock struct {
|
|
||||||
c *Client
|
|
||||||
opts *LockOptions
|
|
||||||
|
|
||||||
isHeld bool
|
|
||||||
sessionRenew chan struct{}
|
|
||||||
lockSession string
|
|
||||||
l sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockOptions is used to parameterize the Lock behavior.
|
|
||||||
type LockOptions struct {
|
|
||||||
Key string // Must be set and have write permissions
|
|
||||||
Value []byte // Optional, value to associate with the lock
|
|
||||||
Session string // Optional, created if not specified
|
|
||||||
SessionOpts *SessionEntry // Optional, options to use when creating a session
|
|
||||||
SessionName string // Optional, defaults to DefaultLockSessionName (ignored if SessionOpts is given)
|
|
||||||
SessionTTL string // Optional, defaults to DefaultLockSessionTTL (ignored if SessionOpts is given)
|
|
||||||
MonitorRetries int // Optional, defaults to 0 which means no retries
|
|
||||||
MonitorRetryTime time.Duration // Optional, defaults to DefaultMonitorRetryTime
|
|
||||||
LockWaitTime time.Duration // Optional, defaults to DefaultLockWaitTime
|
|
||||||
LockTryOnce bool // Optional, defaults to false which means try forever
|
|
||||||
Namespace string `json:",omitempty"` // Optional, defaults to API client config, namespace of ACL token, or "default" namespace
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockKey returns a handle to a lock struct which can be used
|
|
||||||
// to acquire and release the mutex. The key used must have
|
|
||||||
// write permissions.
|
|
||||||
func (c *Client) LockKey(key string) (*Lock, error) {
|
|
||||||
opts := &LockOptions{
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
return c.LockOpts(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockOpts returns a handle to a lock struct which can be used
|
|
||||||
// to acquire and release the mutex. The key used must have
|
|
||||||
// write permissions.
|
|
||||||
func (c *Client) LockOpts(opts *LockOptions) (*Lock, error) {
|
|
||||||
if opts.Key == "" {
|
|
||||||
return nil, fmt.Errorf("missing key")
|
|
||||||
}
|
|
||||||
if opts.SessionName == "" {
|
|
||||||
opts.SessionName = DefaultLockSessionName
|
|
||||||
}
|
|
||||||
if opts.SessionTTL == "" {
|
|
||||||
opts.SessionTTL = DefaultLockSessionTTL
|
|
||||||
} else {
|
|
||||||
if _, err := time.ParseDuration(opts.SessionTTL); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid SessionTTL: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opts.MonitorRetryTime == 0 {
|
|
||||||
opts.MonitorRetryTime = DefaultMonitorRetryTime
|
|
||||||
}
|
|
||||||
if opts.LockWaitTime == 0 {
|
|
||||||
opts.LockWaitTime = DefaultLockWaitTime
|
|
||||||
}
|
|
||||||
l := &Lock{
|
|
||||||
c: c,
|
|
||||||
opts: opts,
|
|
||||||
}
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock attempts to acquire the lock and blocks while doing so.
|
|
||||||
// Providing a non-nil stopCh can be used to abort the lock attempt.
|
|
||||||
// Returns a channel that is closed if our lock is lost or an error.
|
|
||||||
// This channel could be closed at any time due to session invalidation,
|
|
||||||
// communication errors, operator intervention, etc. It is NOT safe to
|
|
||||||
// assume that the lock is held until Unlock() unless the Session is specifically
|
|
||||||
// created without any associated health checks. By default Consul sessions
|
|
||||||
// prefer liveness over safety and an application must be able to handle
|
|
||||||
// the lock being lost.
|
|
||||||
func (l *Lock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
|
|
||||||
// Hold the lock as we try to acquire
|
|
||||||
l.l.Lock()
|
|
||||||
defer l.l.Unlock()
|
|
||||||
|
|
||||||
// Check if we already hold the lock
|
|
||||||
if l.isHeld {
|
|
||||||
return nil, ErrLockHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
wOpts := WriteOptions{
|
|
||||||
Namespace: l.opts.Namespace,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we need to create a session first
|
|
||||||
l.lockSession = l.opts.Session
|
|
||||||
if l.lockSession == "" {
|
|
||||||
s, err := l.createSession()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create session: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.sessionRenew = make(chan struct{})
|
|
||||||
l.lockSession = s
|
|
||||||
|
|
||||||
session := l.c.Session()
|
|
||||||
go session.RenewPeriodic(l.opts.SessionTTL, s, &wOpts, l.sessionRenew)
|
|
||||||
|
|
||||||
// If we fail to acquire the lock, cleanup the session
|
|
||||||
defer func() {
|
|
||||||
if !l.isHeld {
|
|
||||||
close(l.sessionRenew)
|
|
||||||
l.sessionRenew = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the query options
|
|
||||||
kv := l.c.KV()
|
|
||||||
qOpts := QueryOptions{
|
|
||||||
WaitTime: l.opts.LockWaitTime,
|
|
||||||
Namespace: l.opts.Namespace,
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
attempts := 0
|
|
||||||
WAIT:
|
|
||||||
// Check if we should quit
|
|
||||||
select {
|
|
||||||
case <-stopCh:
|
|
||||||
return nil, nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the one-shot mode.
|
|
||||||
if l.opts.LockTryOnce && attempts > 0 {
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
if elapsed > l.opts.LockWaitTime {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query wait time should not exceed the lock wait time
|
|
||||||
qOpts.WaitTime = l.opts.LockWaitTime - elapsed
|
|
||||||
}
|
|
||||||
attempts++
|
|
||||||
|
|
||||||
// Look for an existing lock, blocking until not taken
|
|
||||||
pair, meta, err := kv.Get(l.opts.Key, &qOpts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read lock: %v", err)
|
|
||||||
}
|
|
||||||
if pair != nil && pair.Flags != LockFlagValue {
|
|
||||||
return nil, ErrLockConflict
|
|
||||||
}
|
|
||||||
locked := false
|
|
||||||
if pair != nil && pair.Session == l.lockSession {
|
|
||||||
goto HELD
|
|
||||||
}
|
|
||||||
if pair != nil && pair.Session != "" {
|
|
||||||
qOpts.WaitIndex = meta.LastIndex
|
|
||||||
goto WAIT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to acquire the lock
|
|
||||||
pair = l.lockEntry(l.lockSession)
|
|
||||||
|
|
||||||
locked, _, err = kv.Acquire(pair, &wOpts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to acquire lock: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the case of not getting the lock
|
|
||||||
if !locked {
|
|
||||||
// Determine why the lock failed
|
|
||||||
qOpts.WaitIndex = 0
|
|
||||||
pair, meta, err = kv.Get(l.opts.Key, &qOpts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if pair != nil && pair.Session != "" {
|
|
||||||
//If the session is not null, this means that a wait can safely happen
|
|
||||||
//using a long poll
|
|
||||||
qOpts.WaitIndex = meta.LastIndex
|
|
||||||
goto WAIT
|
|
||||||
} else {
|
|
||||||
// If the session is empty and the lock failed to acquire, then it means
|
|
||||||
// a lock-delay is in effect and a timed wait must be used
|
|
||||||
select {
|
|
||||||
case <-time.After(DefaultLockRetryTime):
|
|
||||||
goto WAIT
|
|
||||||
case <-stopCh:
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HELD:
|
|
||||||
// Watch to ensure we maintain leadership
|
|
||||||
leaderCh := make(chan struct{})
|
|
||||||
go l.monitorLock(l.lockSession, leaderCh)
|
|
||||||
|
|
||||||
// Set that we own the lock
|
|
||||||
l.isHeld = true
|
|
||||||
|
|
||||||
// Locked! All done
|
|
||||||
return leaderCh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock released the lock. It is an error to call this
|
|
||||||
// if the lock is not currently held.
|
|
||||||
func (l *Lock) Unlock() error {
|
|
||||||
// Hold the lock as we try to release
|
|
||||||
l.l.Lock()
|
|
||||||
defer l.l.Unlock()
|
|
||||||
|
|
||||||
// Ensure the lock is actually held
|
|
||||||
if !l.isHeld {
|
|
||||||
return ErrLockNotHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set that we no longer own the lock
|
|
||||||
l.isHeld = false
|
|
||||||
|
|
||||||
// Stop the session renew
|
|
||||||
if l.sessionRenew != nil {
|
|
||||||
defer func() {
|
|
||||||
close(l.sessionRenew)
|
|
||||||
l.sessionRenew = nil
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the lock entry, and clear the lock session
|
|
||||||
lockEnt := l.lockEntry(l.lockSession)
|
|
||||||
l.lockSession = ""
|
|
||||||
|
|
||||||
// Release the lock explicitly
|
|
||||||
kv := l.c.KV()
|
|
||||||
w := WriteOptions{Namespace: l.opts.Namespace}
|
|
||||||
|
|
||||||
_, _, err := kv.Release(lockEnt, &w)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to release lock: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy is used to cleanup the lock entry. It is not necessary
|
|
||||||
// to invoke. It will fail if the lock is in use.
|
|
||||||
func (l *Lock) Destroy() error {
|
|
||||||
// Hold the lock as we try to release
|
|
||||||
l.l.Lock()
|
|
||||||
defer l.l.Unlock()
|
|
||||||
|
|
||||||
// Check if we already hold the lock
|
|
||||||
if l.isHeld {
|
|
||||||
return ErrLockHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for an existing lock
|
|
||||||
kv := l.c.KV()
|
|
||||||
q := QueryOptions{Namespace: l.opts.Namespace}
|
|
||||||
|
|
||||||
pair, _, err := kv.Get(l.opts.Key, &q)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read lock: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing to do if the lock does not exist
|
|
||||||
if pair == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for possible flag conflict
|
|
||||||
if pair.Flags != LockFlagValue {
|
|
||||||
return ErrLockConflict
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it is in use
|
|
||||||
if pair.Session != "" {
|
|
||||||
return ErrLockInUse
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt the delete
|
|
||||||
w := WriteOptions{Namespace: l.opts.Namespace}
|
|
||||||
didRemove, _, err := kv.DeleteCAS(pair, &w)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to remove lock: %v", err)
|
|
||||||
}
|
|
||||||
if !didRemove {
|
|
||||||
return ErrLockInUse
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createSession is used to create a new managed session
|
|
||||||
func (l *Lock) createSession() (string, error) {
|
|
||||||
session := l.c.Session()
|
|
||||||
se := l.opts.SessionOpts
|
|
||||||
if se == nil {
|
|
||||||
se = &SessionEntry{
|
|
||||||
Name: l.opts.SessionName,
|
|
||||||
TTL: l.opts.SessionTTL,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w := WriteOptions{Namespace: l.opts.Namespace}
|
|
||||||
id, _, err := session.Create(se, &w)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lockEntry returns a formatted KVPair for the lock
|
|
||||||
func (l *Lock) lockEntry(session string) *KVPair {
|
|
||||||
return &KVPair{
|
|
||||||
Key: l.opts.Key,
|
|
||||||
Value: l.opts.Value,
|
|
||||||
Session: session,
|
|
||||||
Flags: LockFlagValue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// monitorLock is a long running routine to monitor a lock ownership
|
|
||||||
// It closes the stopCh if we lose our leadership.
|
|
||||||
func (l *Lock) monitorLock(session string, stopCh chan struct{}) {
|
|
||||||
defer close(stopCh)
|
|
||||||
kv := l.c.KV()
|
|
||||||
opts := QueryOptions{
|
|
||||||
RequireConsistent: true,
|
|
||||||
Namespace: l.opts.Namespace,
|
|
||||||
}
|
|
||||||
WAIT:
|
|
||||||
retries := l.opts.MonitorRetries
|
|
||||||
RETRY:
|
|
||||||
pair, meta, err := kv.Get(l.opts.Key, &opts)
|
|
||||||
if err != nil {
|
|
||||||
// If configured we can try to ride out a brief Consul unavailability
|
|
||||||
// by doing retries. Note that we have to attempt the retry in a non-
|
|
||||||
// blocking fashion so that we have a clean place to reset the retry
|
|
||||||
// counter if service is restored.
|
|
||||||
if retries > 0 && IsRetryableError(err) {
|
|
||||||
time.Sleep(l.opts.MonitorRetryTime)
|
|
||||||
retries--
|
|
||||||
opts.WaitIndex = 0
|
|
||||||
goto RETRY
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pair != nil && pair.Session == session {
|
|
||||||
opts.WaitIndex = meta.LastIndex
|
|
||||||
goto WAIT
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Namespace is the configuration of a single namespace. Namespacing is a Consul Enterprise feature.
|
|
||||||
type Namespace struct {
|
|
||||||
// Name is the name of the Namespace. It must be unique and
|
|
||||||
// must be a DNS hostname. There are also other reserved names
|
|
||||||
// that may not be used.
|
|
||||||
Name string `json:"Name"`
|
|
||||||
|
|
||||||
// Description is where the user puts any information they want
|
|
||||||
// about the namespace. It is not used internally.
|
|
||||||
Description string `json:"Description,omitempty"`
|
|
||||||
|
|
||||||
// ACLs is the configuration of ACLs for this namespace. It has its
|
|
||||||
// own struct so that we can add more to it in the future.
|
|
||||||
// This is nullable so that we can omit if empty when encoding in JSON
|
|
||||||
ACLs *NamespaceACLConfig `json:"ACLs,omitempty"`
|
|
||||||
|
|
||||||
// Meta is a map that can be used to add kv metadata to the namespace definition
|
|
||||||
Meta map[string]string `json:"Meta,omitempty"`
|
|
||||||
|
|
||||||
// DeletedAt is the time when the Namespace was marked for deletion
|
|
||||||
// This is nullable so that we can omit if empty when encoding in JSON
|
|
||||||
DeletedAt *time.Time `json:"DeletedAt,omitempty"`
|
|
||||||
|
|
||||||
// CreateIndex is the Raft index at which the Namespace was created
|
|
||||||
CreateIndex uint64 `json:"CreateIndex,omitempty"`
|
|
||||||
|
|
||||||
// ModifyIndex is the latest Raft index at which the Namespace was modified.
|
|
||||||
ModifyIndex uint64 `json:"ModifyIndex,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NamespaceACLConfig is the Namespace specific ACL configuration container
|
|
||||||
type NamespaceACLConfig struct {
|
|
||||||
// PolicyDefaults is the list of policies that should be used for the parent authorizer
|
|
||||||
// of all tokens in the associated namespace.
|
|
||||||
PolicyDefaults []ACLLink `json:"PolicyDefaults"`
|
|
||||||
// RoleDefaults is the list of roles that should be used for the parent authorizer
|
|
||||||
// of all tokens in the associated namespace.
|
|
||||||
RoleDefaults []ACLLink `json:"RoleDefaults"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Namespaces can be used to manage Namespaces in Consul Enterprise..
|
|
||||||
type Namespaces struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operator returns a handle to the operator endpoints.
|
|
||||||
func (c *Client) Namespaces() *Namespaces {
|
|
||||||
return &Namespaces{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespaces) Create(ns *Namespace, q *WriteOptions) (*Namespace, *WriteMeta, error) {
|
|
||||||
if ns.Name == "" {
|
|
||||||
return nil, nil, fmt.Errorf("Must specify a Name for Namespace creation")
|
|
||||||
}
|
|
||||||
|
|
||||||
r := n.c.newRequest("PUT", "/v1/namespace")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = ns
|
|
||||||
rtt, resp, err := requireOK(n.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
var out Namespace
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &out, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespaces) Update(ns *Namespace, q *WriteOptions) (*Namespace, *WriteMeta, error) {
|
|
||||||
if ns.Name == "" {
|
|
||||||
return nil, nil, fmt.Errorf("Must specify a Name for Namespace updating")
|
|
||||||
}
|
|
||||||
|
|
||||||
r := n.c.newRequest("PUT", "/v1/namespace/"+ns.Name)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = ns
|
|
||||||
rtt, resp, err := requireOK(n.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
var out Namespace
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &out, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespaces) Read(name string, q *QueryOptions) (*Namespace, *QueryMeta, error) {
|
|
||||||
var out Namespace
|
|
||||||
r := n.c.newRequest("GET", "/v1/namespace/"+name)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
found, rtt, resp, err := requireNotFoundOrOK(n.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return &out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespaces) Delete(name string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := n.c.newRequest("DELETE", "/v1/namespace/"+name)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(n.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespaces) List(q *QueryOptions) ([]*Namespace, *QueryMeta, error) {
|
|
||||||
var out []*Namespace
|
|
||||||
r := n.c.newRequest("GET", "/v1/namespaces")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(n.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
// Operator can be used to perform low-level operator tasks for Consul.
|
|
||||||
type Operator struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operator returns a handle to the operator endpoints.
|
|
||||||
func (c *Client) Operator() *Operator {
|
|
||||||
return &Operator{c}
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
// The /v1/operator/area endpoints are available only in Consul Enterprise and
|
|
||||||
// interact with its network area subsystem. Network areas are used to link
|
|
||||||
// together Consul servers in different Consul datacenters. With network areas,
|
|
||||||
// Consul datacenters can be linked together in ways other than a fully-connected
|
|
||||||
// mesh, as is required for Consul's WAN.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Area defines a network area.
|
|
||||||
type Area struct {
|
|
||||||
// ID is this identifier for an area (a UUID). This must be left empty
|
|
||||||
// when creating a new area.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// PeerDatacenter is the peer Consul datacenter that will make up the
|
|
||||||
// other side of this network area. Network areas always involve a pair
|
|
||||||
// of datacenters: the datacenter where the area was created, and the
|
|
||||||
// peer datacenter. This is required.
|
|
||||||
PeerDatacenter string
|
|
||||||
|
|
||||||
// RetryJoin specifies the address of Consul servers to join to, such as
|
|
||||||
// an IPs or hostnames with an optional port number. This is optional.
|
|
||||||
RetryJoin []string
|
|
||||||
|
|
||||||
// UseTLS specifies whether gossip over this area should be encrypted with TLS
|
|
||||||
// if possible.
|
|
||||||
UseTLS bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaJoinResponse is returned when a join occurs and gives the result for each
|
|
||||||
// address.
|
|
||||||
type AreaJoinResponse struct {
|
|
||||||
// The address that was joined.
|
|
||||||
Address string
|
|
||||||
|
|
||||||
// Whether or not the join was a success.
|
|
||||||
Joined bool
|
|
||||||
|
|
||||||
// If we couldn't join, this is the message with information.
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerfMember is a generic structure for reporting information about members in
|
|
||||||
// a Serf cluster. This is only used by the area endpoints right now, but this
|
|
||||||
// could be expanded to other endpoints in the future.
|
|
||||||
type SerfMember struct {
|
|
||||||
// ID is the node identifier (a UUID).
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Name is the node name.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Addr has the IP address.
|
|
||||||
Addr net.IP
|
|
||||||
|
|
||||||
// Port is the RPC port.
|
|
||||||
Port uint16
|
|
||||||
|
|
||||||
// Datacenter is the DC name.
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// Role is "client", "server", or "unknown".
|
|
||||||
Role string
|
|
||||||
|
|
||||||
// Build has the version of the Consul agent.
|
|
||||||
Build string
|
|
||||||
|
|
||||||
// Protocol is the protocol of the Consul agent.
|
|
||||||
Protocol int
|
|
||||||
|
|
||||||
// Status is the Serf health status "none", "alive", "leaving", "left",
|
|
||||||
// or "failed".
|
|
||||||
Status string
|
|
||||||
|
|
||||||
// RTT is the estimated round trip time from the server handling the
|
|
||||||
// request to the this member. This will be negative if no RTT estimate
|
|
||||||
// is available.
|
|
||||||
RTT time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaCreate will create a new network area. The ID in the given structure must
|
|
||||||
// be empty and a generated ID will be returned on success.
|
|
||||||
func (op *Operator) AreaCreate(area *Area, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := op.c.newRequest("POST", "/v1/operator/area")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = area
|
|
||||||
rtt, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaUpdate will update the configuration of the network area with the given ID.
|
|
||||||
func (op *Operator) AreaUpdate(areaID string, area *Area, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/area/"+areaID)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = area
|
|
||||||
rtt, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaGet returns a single network area.
|
|
||||||
func (op *Operator) AreaGet(areaID string, q *QueryOptions) ([]*Area, *QueryMeta, error) {
|
|
||||||
var out []*Area
|
|
||||||
qm, err := op.c.query("/v1/operator/area/"+areaID, &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaList returns all the available network areas.
|
|
||||||
func (op *Operator) AreaList(q *QueryOptions) ([]*Area, *QueryMeta, error) {
|
|
||||||
var out []*Area
|
|
||||||
qm, err := op.c.query("/v1/operator/area", &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaDelete deletes the given network area.
|
|
||||||
func (op *Operator) AreaDelete(areaID string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := op.c.newRequest("DELETE", "/v1/operator/area/"+areaID)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaJoin attempts to join the given set of join addresses to the given
|
|
||||||
// network area. See the Area structure for details about join addresses.
|
|
||||||
func (op *Operator) AreaJoin(areaID string, addresses []string, q *WriteOptions) ([]*AreaJoinResponse, *WriteMeta, error) {
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/area/"+areaID+"/join")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = addresses
|
|
||||||
rtt, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*AreaJoinResponse
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaMembers lists the Serf information about the members in the given area.
|
|
||||||
func (op *Operator) AreaMembers(areaID string, q *QueryOptions) ([]*SerfMember, *QueryMeta, error) {
|
|
||||||
var out []*SerfMember
|
|
||||||
qm, err := op.c.query("/v1/operator/area/"+areaID+"/members", &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
|
@ -1,232 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AutopilotConfiguration is used for querying/setting the Autopilot configuration.
|
|
||||||
// Autopilot helps manage operator tasks related to Consul servers like removing
|
|
||||||
// failed servers from the Raft quorum.
|
|
||||||
type AutopilotConfiguration struct {
|
|
||||||
// CleanupDeadServers controls whether to remove dead servers from the Raft
|
|
||||||
// peer list when a new server joins
|
|
||||||
CleanupDeadServers bool
|
|
||||||
|
|
||||||
// LastContactThreshold is the limit on the amount of time a server can go
|
|
||||||
// without leader contact before being considered unhealthy.
|
|
||||||
LastContactThreshold *ReadableDuration
|
|
||||||
|
|
||||||
// MaxTrailingLogs is the amount of entries in the Raft Log that a server can
|
|
||||||
// be behind before being considered unhealthy.
|
|
||||||
MaxTrailingLogs uint64
|
|
||||||
|
|
||||||
// MinQuorum sets the minimum number of servers allowed in a cluster before
|
|
||||||
// autopilot can prune dead servers.
|
|
||||||
MinQuorum uint
|
|
||||||
|
|
||||||
// ServerStabilizationTime is the minimum amount of time a server must be
|
|
||||||
// in a stable, healthy state before it can be added to the cluster. Only
|
|
||||||
// applicable with Raft protocol version 3 or higher.
|
|
||||||
ServerStabilizationTime *ReadableDuration
|
|
||||||
|
|
||||||
// (Enterprise-only) RedundancyZoneTag is the node tag to use for separating
|
|
||||||
// servers into zones for redundancy. If left blank, this feature will be disabled.
|
|
||||||
RedundancyZoneTag string
|
|
||||||
|
|
||||||
// (Enterprise-only) DisableUpgradeMigration will disable Autopilot's upgrade migration
|
|
||||||
// strategy of waiting until enough newer-versioned servers have been added to the
|
|
||||||
// cluster before promoting them to voters.
|
|
||||||
DisableUpgradeMigration bool
|
|
||||||
|
|
||||||
// (Enterprise-only) UpgradeVersionTag is the node tag to use for version info when
|
|
||||||
// performing upgrade migrations. If left blank, the Consul version will be used.
|
|
||||||
UpgradeVersionTag string
|
|
||||||
|
|
||||||
// CreateIndex holds the index corresponding the creation of this configuration.
|
|
||||||
// This is a read-only field.
|
|
||||||
CreateIndex uint64
|
|
||||||
|
|
||||||
// ModifyIndex will be set to the index of the last update when retrieving the
|
|
||||||
// Autopilot configuration. Resubmitting a configuration with
|
|
||||||
// AutopilotCASConfiguration will perform a check-and-set operation which ensures
|
|
||||||
// there hasn't been a subsequent update since the configuration was retrieved.
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerHealth is the health (from the leader's point of view) of a server.
|
|
||||||
type ServerHealth struct {
|
|
||||||
// ID is the raft ID of the server.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Name is the node name of the server.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Address is the address of the server.
|
|
||||||
Address string
|
|
||||||
|
|
||||||
// The status of the SerfHealth check for the server.
|
|
||||||
SerfStatus string
|
|
||||||
|
|
||||||
// Version is the Consul version of the server.
|
|
||||||
Version string
|
|
||||||
|
|
||||||
// Leader is whether this server is currently the leader.
|
|
||||||
Leader bool
|
|
||||||
|
|
||||||
// LastContact is the time since this node's last contact with the leader.
|
|
||||||
LastContact *ReadableDuration
|
|
||||||
|
|
||||||
// LastTerm is the highest leader term this server has a record of in its Raft log.
|
|
||||||
LastTerm uint64
|
|
||||||
|
|
||||||
// LastIndex is the last log index this server has a record of in its Raft log.
|
|
||||||
LastIndex uint64
|
|
||||||
|
|
||||||
// Healthy is whether or not the server is healthy according to the current
|
|
||||||
// Autopilot config.
|
|
||||||
Healthy bool
|
|
||||||
|
|
||||||
// Voter is whether this is a voting server.
|
|
||||||
Voter bool
|
|
||||||
|
|
||||||
// StableSince is the last time this server's Healthy value changed.
|
|
||||||
StableSince time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// OperatorHealthReply is a representation of the overall health of the cluster
|
|
||||||
type OperatorHealthReply struct {
|
|
||||||
// Healthy is true if all the servers in the cluster are healthy.
|
|
||||||
Healthy bool
|
|
||||||
|
|
||||||
// FailureTolerance is the number of healthy servers that could be lost without
|
|
||||||
// an outage occurring.
|
|
||||||
FailureTolerance int
|
|
||||||
|
|
||||||
// Servers holds the health of each server.
|
|
||||||
Servers []ServerHealth
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadableDuration is a duration type that is serialized to JSON in human readable format.
|
|
||||||
type ReadableDuration time.Duration
|
|
||||||
|
|
||||||
func NewReadableDuration(dur time.Duration) *ReadableDuration {
|
|
||||||
d := ReadableDuration(dur)
|
|
||||||
return &d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ReadableDuration) String() string {
|
|
||||||
return d.Duration().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ReadableDuration) Duration() time.Duration {
|
|
||||||
if d == nil {
|
|
||||||
return time.Duration(0)
|
|
||||||
}
|
|
||||||
return time.Duration(*d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ReadableDuration) MarshalJSON() ([]byte, error) {
|
|
||||||
return []byte(fmt.Sprintf(`"%s"`, d.Duration().String())), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ReadableDuration) UnmarshalJSON(raw []byte) (err error) {
|
|
||||||
if d == nil {
|
|
||||||
return fmt.Errorf("cannot unmarshal to nil pointer")
|
|
||||||
}
|
|
||||||
|
|
||||||
var dur time.Duration
|
|
||||||
str := string(raw)
|
|
||||||
if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
|
|
||||||
// quoted string
|
|
||||||
dur, err = time.ParseDuration(str[1 : len(str)-1])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// no quotes, not a string
|
|
||||||
v, err := strconv.ParseFloat(str, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dur = time.Duration(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
*d = ReadableDuration(dur)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutopilotGetConfiguration is used to query the current Autopilot configuration.
|
|
||||||
func (op *Operator) AutopilotGetConfiguration(q *QueryOptions) (*AutopilotConfiguration, error) {
|
|
||||||
r := op.c.newRequest("GET", "/v1/operator/autopilot/configuration")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out AutopilotConfiguration
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutopilotSetConfiguration is used to set the current Autopilot configuration.
|
|
||||||
func (op *Operator) AutopilotSetConfiguration(conf *AutopilotConfiguration, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/autopilot/configuration")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = conf
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutopilotCASConfiguration is used to perform a Check-And-Set update on the
|
|
||||||
// Autopilot configuration. The ModifyIndex value will be respected. Returns
|
|
||||||
// true on success or false on failures.
|
|
||||||
func (op *Operator) AutopilotCASConfiguration(conf *AutopilotConfiguration, q *WriteOptions) (bool, error) {
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/autopilot/configuration")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.params.Set("cas", strconv.FormatUint(conf.ModifyIndex, 10))
|
|
||||||
r.obj = conf
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
|
||||||
return false, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
res := strings.Contains(buf.String(), "true")
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutopilotServerHealth
|
|
||||||
func (op *Operator) AutopilotServerHealth(q *QueryOptions) (*OperatorHealthReply, error) {
|
|
||||||
r := op.c.newRequest("GET", "/v1/operator/autopilot/health")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out OperatorHealthReply
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &out, nil
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
// keyringRequest is used for performing Keyring operations
|
|
||||||
type keyringRequest struct {
|
|
||||||
Key string
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringResponse is returned when listing the gossip encryption keys
|
|
||||||
type KeyringResponse struct {
|
|
||||||
// Whether this response is for a WAN ring
|
|
||||||
WAN bool
|
|
||||||
|
|
||||||
// The datacenter name this request corresponds to
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// Segment has the network segment this request corresponds to.
|
|
||||||
Segment string
|
|
||||||
|
|
||||||
// Messages has information or errors from serf
|
|
||||||
Messages map[string]string `json:",omitempty"`
|
|
||||||
|
|
||||||
// A map of the encryption keys to the number of nodes they're installed on
|
|
||||||
Keys map[string]int
|
|
||||||
|
|
||||||
// A map of the encryption primary keys to the number of nodes they're installed on
|
|
||||||
PrimaryKeys map[string]int
|
|
||||||
|
|
||||||
// The total number of nodes in this ring
|
|
||||||
NumNodes int
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringInstall is used to install a new gossip encryption key into the cluster
|
|
||||||
func (op *Operator) KeyringInstall(key string, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("POST", "/v1/operator/keyring")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = keyringRequest{
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringList is used to list the gossip keys installed in the cluster
|
|
||||||
func (op *Operator) KeyringList(q *QueryOptions) ([]*KeyringResponse, error) {
|
|
||||||
r := op.c.newRequest("GET", "/v1/operator/keyring")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out []*KeyringResponse
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringRemove is used to remove a gossip encryption key from the cluster
|
|
||||||
func (op *Operator) KeyringRemove(key string, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("DELETE", "/v1/operator/keyring")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = keyringRequest{
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringUse is used to change the active gossip encryption key
|
|
||||||
func (op *Operator) KeyringUse(key string, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/keyring")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = keyringRequest{
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type License struct {
|
|
||||||
// The unique identifier of the license
|
|
||||||
LicenseID string `json:"license_id"`
|
|
||||||
|
|
||||||
// The customer ID associated with the license
|
|
||||||
CustomerID string `json:"customer_id"`
|
|
||||||
|
|
||||||
// If set, an identifier that should be used to lock the license to a
|
|
||||||
// particular site, cluster, etc.
|
|
||||||
InstallationID string `json:"installation_id"`
|
|
||||||
|
|
||||||
// The time at which the license was issued
|
|
||||||
IssueTime time.Time `json:"issue_time"`
|
|
||||||
|
|
||||||
// The time at which the license starts being valid
|
|
||||||
StartTime time.Time `json:"start_time"`
|
|
||||||
|
|
||||||
// The time after which the license expires
|
|
||||||
ExpirationTime time.Time `json:"expiration_time"`
|
|
||||||
|
|
||||||
// The time at which the license ceases to function and can
|
|
||||||
// no longer be used in any capacity
|
|
||||||
TerminationTime time.Time `json:"termination_time"`
|
|
||||||
|
|
||||||
// The product the license is valid for
|
|
||||||
Product string `json:"product"`
|
|
||||||
|
|
||||||
// License Specific Flags
|
|
||||||
Flags map[string]interface{} `json:"flags"`
|
|
||||||
|
|
||||||
// Modules is a list of the licensed enterprise modules
|
|
||||||
Modules []string `json:"modules"`
|
|
||||||
|
|
||||||
// List of features enabled by the license
|
|
||||||
Features []string `json:"features"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LicenseReply struct {
|
|
||||||
Valid bool
|
|
||||||
License *License
|
|
||||||
Warnings []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *Operator) LicenseGet(q *QueryOptions) (*LicenseReply, error) {
|
|
||||||
var reply LicenseReply
|
|
||||||
if _, err := op.c.query("/v1/operator/license", &reply, q); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return &reply, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *Operator) LicenseGetSigned(q *QueryOptions) (string, error) {
|
|
||||||
r := op.c.newRequest("GET", "/v1/operator/license")
|
|
||||||
r.params.Set("signed", "1")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LicenseReset will reset the license to the builtin one if it is still valid.
|
|
||||||
// If the builtin license is invalid, the current license stays active.
|
|
||||||
func (op *Operator) LicenseReset(opts *WriteOptions) (*LicenseReply, error) {
|
|
||||||
var reply LicenseReply
|
|
||||||
r := op.c.newRequest("DELETE", "/v1/operator/license")
|
|
||||||
r.setWriteOptions(opts)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if err := decodeBody(resp, &reply); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &reply, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (op *Operator) LicensePut(license string, opts *WriteOptions) (*LicenseReply, error) {
|
|
||||||
var reply LicenseReply
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/license")
|
|
||||||
r.setWriteOptions(opts)
|
|
||||||
r.body = strings.NewReader(license)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if err := decodeBody(resp, &reply); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &reply, nil
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
// RaftServer has information about a server in the Raft configuration.
|
|
||||||
type RaftServer struct {
|
|
||||||
// ID is the unique ID for the server. These are currently the same
|
|
||||||
// as the address, but they will be changed to a real GUID in a future
|
|
||||||
// release of Consul.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Node is the node name of the server, as known by Consul, or this
|
|
||||||
// will be set to "(unknown)" otherwise.
|
|
||||||
Node string
|
|
||||||
|
|
||||||
// Address is the IP:port of the server, used for Raft communications.
|
|
||||||
Address string
|
|
||||||
|
|
||||||
// Leader is true if this server is the current cluster leader.
|
|
||||||
Leader bool
|
|
||||||
|
|
||||||
// Protocol version is the raft protocol version used by the server
|
|
||||||
ProtocolVersion string
|
|
||||||
|
|
||||||
// Voter is true if this server has a vote in the cluster. This might
|
|
||||||
// be false if the server is staging and still coming online, or if
|
|
||||||
// it's a non-voting server, which will be added in a future release of
|
|
||||||
// Consul.
|
|
||||||
Voter bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// RaftConfiguration is returned when querying for the current Raft configuration.
|
|
||||||
type RaftConfiguration struct {
|
|
||||||
// Servers has the list of servers in the Raft configuration.
|
|
||||||
Servers []*RaftServer
|
|
||||||
|
|
||||||
// Index has the Raft index of this configuration.
|
|
||||||
Index uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// RaftGetConfiguration is used to query the current Raft peer set.
|
|
||||||
func (op *Operator) RaftGetConfiguration(q *QueryOptions) (*RaftConfiguration, error) {
|
|
||||||
r := op.c.newRequest("GET", "/v1/operator/raft/configuration")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out RaftConfiguration
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RaftRemovePeerByAddress is used to kick a stale peer (one that it in the Raft
|
|
||||||
// quorum but no longer known to Serf or the catalog) by address in the form of
|
|
||||||
// "IP:port".
|
|
||||||
func (op *Operator) RaftRemovePeerByAddress(address string, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("DELETE", "/v1/operator/raft/peer")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
|
|
||||||
r.params.Set("address", address)
|
|
||||||
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RaftRemovePeerByID is used to kick a stale peer (one that it in the Raft
|
|
||||||
// quorum but no longer known to Serf or the catalog) by ID.
|
|
||||||
func (op *Operator) RaftRemovePeerByID(id string, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("DELETE", "/v1/operator/raft/peer")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
|
|
||||||
r.params.Set("id", id)
|
|
||||||
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
// SegmentList returns all the available LAN segments.
|
|
||||||
func (op *Operator) SegmentList(q *QueryOptions) ([]string, *QueryMeta, error) {
|
|
||||||
var out []string
|
|
||||||
qm, err := op.c.query("/v1/operator/segment", &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
|
@ -1,223 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
// QueryDatacenterOptions sets options about how we fail over if there are no
|
|
||||||
// healthy nodes in the local datacenter.
|
|
||||||
type QueryDatacenterOptions struct {
|
|
||||||
// NearestN is set to the number of remote datacenters to try, based on
|
|
||||||
// network coordinates.
|
|
||||||
NearestN int
|
|
||||||
|
|
||||||
// Datacenters is a fixed list of datacenters to try after NearestN. We
|
|
||||||
// never try a datacenter multiple times, so those are subtracted from
|
|
||||||
// this list before proceeding.
|
|
||||||
Datacenters []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryDNSOptions controls settings when query results are served over DNS.
|
|
||||||
type QueryDNSOptions struct {
|
|
||||||
// TTL is the time to live for the served DNS results.
|
|
||||||
TTL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceQuery is used to query for a set of healthy nodes offering a specific
|
|
||||||
// service.
|
|
||||||
type ServiceQuery struct {
|
|
||||||
// Service is the service to query.
|
|
||||||
Service string
|
|
||||||
|
|
||||||
// Namespace of the service to query
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
|
|
||||||
// Near allows baking in the name of a node to automatically distance-
|
|
||||||
// sort from. The magic "_agent" value is supported, which sorts near
|
|
||||||
// the agent which initiated the request by default.
|
|
||||||
Near string
|
|
||||||
|
|
||||||
// Failover controls what we do if there are no healthy nodes in the
|
|
||||||
// local datacenter.
|
|
||||||
Failover QueryDatacenterOptions
|
|
||||||
|
|
||||||
// IgnoreCheckIDs is an optional list of health check IDs to ignore when
|
|
||||||
// considering which nodes are healthy. It is useful as an emergency measure
|
|
||||||
// to temporarily override some health check that is producing false negatives
|
|
||||||
// for example.
|
|
||||||
IgnoreCheckIDs []string
|
|
||||||
|
|
||||||
// If OnlyPassing is true then we will only include nodes with passing
|
|
||||||
// health checks (critical AND warning checks will cause a node to be
|
|
||||||
// discarded)
|
|
||||||
OnlyPassing bool
|
|
||||||
|
|
||||||
// Tags are a set of required and/or disallowed tags. If a tag is in
|
|
||||||
// this list it must be present. If the tag is preceded with "!" then
|
|
||||||
// it is disallowed.
|
|
||||||
Tags []string
|
|
||||||
|
|
||||||
// NodeMeta is a map of required node metadata fields. If a key/value
|
|
||||||
// pair is in this map it must be present on the node in order for the
|
|
||||||
// service entry to be returned.
|
|
||||||
NodeMeta map[string]string
|
|
||||||
|
|
||||||
// ServiceMeta is a map of required service metadata fields. If a key/value
|
|
||||||
// pair is in this map it must be present on the node in order for the
|
|
||||||
// service entry to be returned.
|
|
||||||
ServiceMeta map[string]string
|
|
||||||
|
|
||||||
// Connect if true will filter the prepared query results to only
|
|
||||||
// include Connect-capable services. These include both native services
|
|
||||||
// and proxies for matching services. Note that if a proxy matches,
|
|
||||||
// the constraints in the query above (Near, OnlyPassing, etc.) apply
|
|
||||||
// to the _proxy_ and not the service being proxied. In practice, proxies
|
|
||||||
// should be directly next to their services so this isn't an issue.
|
|
||||||
Connect bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryTemplate carries the arguments for creating a templated query.
|
|
||||||
type QueryTemplate struct {
|
|
||||||
// Type specifies the type of the query template. Currently only
|
|
||||||
// "name_prefix_match" is supported. This field is required.
|
|
||||||
Type string
|
|
||||||
|
|
||||||
// Regexp allows specifying a regex pattern to match against the name
|
|
||||||
// of the query being executed.
|
|
||||||
Regexp string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreparedQueryDefinition defines a complete prepared query.
|
|
||||||
type PreparedQueryDefinition struct {
|
|
||||||
// ID is this UUID-based ID for the query, always generated by Consul.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Name is an optional friendly name for the query supplied by the
|
|
||||||
// user. NOTE - if this feature is used then it will reduce the security
|
|
||||||
// of any read ACL associated with this query/service since this name
|
|
||||||
// can be used to locate nodes with supplying any ACL.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Session is an optional session to tie this query's lifetime to. If
|
|
||||||
// this is omitted then the query will not expire.
|
|
||||||
Session string
|
|
||||||
|
|
||||||
// Token is the ACL token used when the query was created, and it is
|
|
||||||
// used when a query is subsequently executed. This token, or a token
|
|
||||||
// with management privileges, must be used to change the query later.
|
|
||||||
Token string
|
|
||||||
|
|
||||||
// Service defines a service query (leaving things open for other types
|
|
||||||
// later).
|
|
||||||
Service ServiceQuery
|
|
||||||
|
|
||||||
// DNS has options that control how the results of this query are
|
|
||||||
// served over DNS.
|
|
||||||
DNS QueryDNSOptions
|
|
||||||
|
|
||||||
// Template is used to pass through the arguments for creating a
|
|
||||||
// prepared query with an attached template. If a template is given,
|
|
||||||
// interpolations are possible in other struct fields.
|
|
||||||
Template QueryTemplate
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreparedQueryExecuteResponse has the results of executing a query.
|
|
||||||
type PreparedQueryExecuteResponse struct {
|
|
||||||
// Service is the service that was queried.
|
|
||||||
Service string
|
|
||||||
|
|
||||||
// Namespace of the service that was queried
|
|
||||||
Namespace string `json:",omitempty"`
|
|
||||||
|
|
||||||
// Nodes has the nodes that were output by the query.
|
|
||||||
Nodes []ServiceEntry
|
|
||||||
|
|
||||||
// DNS has the options for serving these results over DNS.
|
|
||||||
DNS QueryDNSOptions
|
|
||||||
|
|
||||||
// Datacenter is the datacenter that these results came from.
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// Failovers is a count of how many times we had to query a remote
|
|
||||||
// datacenter.
|
|
||||||
Failovers int
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreparedQuery can be used to query the prepared query endpoints.
|
|
||||||
type PreparedQuery struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreparedQuery returns a handle to the prepared query endpoints.
|
|
||||||
func (c *Client) PreparedQuery() *PreparedQuery {
|
|
||||||
return &PreparedQuery{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create makes a new prepared query. The ID of the new query is returned.
|
|
||||||
func (c *PreparedQuery) Create(query *PreparedQueryDefinition, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("POST", "/v1/query")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = query
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update makes updates to an existing prepared query.
|
|
||||||
func (c *PreparedQuery) Update(query *PreparedQueryDefinition, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
return c.c.write("/v1/query/"+query.ID, query, nil, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List is used to fetch all the prepared queries (always requires a management
|
|
||||||
// token).
|
|
||||||
func (c *PreparedQuery) List(q *QueryOptions) ([]*PreparedQueryDefinition, *QueryMeta, error) {
|
|
||||||
var out []*PreparedQueryDefinition
|
|
||||||
qm, err := c.c.query("/v1/query", &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get is used to fetch a specific prepared query.
|
|
||||||
func (c *PreparedQuery) Get(queryID string, q *QueryOptions) ([]*PreparedQueryDefinition, *QueryMeta, error) {
|
|
||||||
var out []*PreparedQueryDefinition
|
|
||||||
qm, err := c.c.query("/v1/query/"+queryID, &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete is used to delete a specific prepared query.
|
|
||||||
func (c *PreparedQuery) Delete(queryID string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("DELETE", "/v1/query/"+queryID)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute is used to execute a specific prepared query. You can execute using
|
|
||||||
// a query ID or name.
|
|
||||||
func (c *PreparedQuery) Execute(queryIDOrName string, q *QueryOptions) (*PreparedQueryExecuteResponse, *QueryMeta, error) {
|
|
||||||
var out *PreparedQueryExecuteResponse
|
|
||||||
qm, err := c.c.query("/v1/query/"+queryIDOrName+"/execute", &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
// Raw can be used to do raw queries against custom endpoints
|
|
||||||
type Raw struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raw returns a handle to query endpoints
|
|
||||||
func (c *Client) Raw() *Raw {
|
|
||||||
return &Raw{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query is used to do a GET request against an endpoint
|
|
||||||
// and deserialize the response into an interface using
|
|
||||||
// standard Consul conventions.
|
|
||||||
func (raw *Raw) Query(endpoint string, out interface{}, q *QueryOptions) (*QueryMeta, error) {
|
|
||||||
return raw.c.query(endpoint, out, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write is used to do a PUT request against an endpoint
|
|
||||||
// and serialize/deserialized using the standard Consul conventions.
|
|
||||||
func (raw *Raw) Write(endpoint string, in, out interface{}, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
return raw.c.write(endpoint, in, out, q)
|
|
||||||
}
|
|
|
@ -1,530 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"path"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultSemaphoreSessionName is the Session Name we assign if none is provided
|
|
||||||
DefaultSemaphoreSessionName = "Consul API Semaphore"
|
|
||||||
|
|
||||||
// DefaultSemaphoreSessionTTL is the default session TTL if no Session is provided
|
|
||||||
// when creating a new Semaphore. This is used because we do not have another
|
|
||||||
// other check to depend upon.
|
|
||||||
DefaultSemaphoreSessionTTL = "15s"
|
|
||||||
|
|
||||||
// DefaultSemaphoreWaitTime is how long we block for at a time to check if semaphore
|
|
||||||
// acquisition is possible. This affects the minimum time it takes to cancel
|
|
||||||
// a Semaphore acquisition.
|
|
||||||
DefaultSemaphoreWaitTime = 15 * time.Second
|
|
||||||
|
|
||||||
// DefaultSemaphoreKey is the key used within the prefix to
|
|
||||||
// use for coordination between all the contenders.
|
|
||||||
DefaultSemaphoreKey = ".lock"
|
|
||||||
|
|
||||||
// SemaphoreFlagValue is a magic flag we set to indicate a key
|
|
||||||
// is being used for a semaphore. It is used to detect a potential
|
|
||||||
// conflict with a lock.
|
|
||||||
SemaphoreFlagValue = 0xe0f69a2baa414de0
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrSemaphoreHeld is returned if we attempt to double lock
|
|
||||||
ErrSemaphoreHeld = fmt.Errorf("Semaphore already held")
|
|
||||||
|
|
||||||
// ErrSemaphoreNotHeld is returned if we attempt to unlock a semaphore
|
|
||||||
// that we do not hold.
|
|
||||||
ErrSemaphoreNotHeld = fmt.Errorf("Semaphore not held")
|
|
||||||
|
|
||||||
// ErrSemaphoreInUse is returned if we attempt to destroy a semaphore
|
|
||||||
// that is in use.
|
|
||||||
ErrSemaphoreInUse = fmt.Errorf("Semaphore in use")
|
|
||||||
|
|
||||||
// ErrSemaphoreConflict is returned if the flags on a key
|
|
||||||
// used for a semaphore do not match expectation
|
|
||||||
ErrSemaphoreConflict = fmt.Errorf("Existing key does not match semaphore use")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Semaphore is used to implement a distributed semaphore
|
|
||||||
// using the Consul KV primitives.
|
|
||||||
type Semaphore struct {
|
|
||||||
c *Client
|
|
||||||
opts *SemaphoreOptions
|
|
||||||
|
|
||||||
isHeld bool
|
|
||||||
sessionRenew chan struct{}
|
|
||||||
lockSession string
|
|
||||||
l sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// SemaphoreOptions is used to parameterize the Semaphore
|
|
||||||
type SemaphoreOptions struct {
|
|
||||||
Prefix string // Must be set and have write permissions
|
|
||||||
Limit int // Must be set, and be positive
|
|
||||||
Value []byte // Optional, value to associate with the contender entry
|
|
||||||
Session string // Optional, created if not specified
|
|
||||||
SessionName string // Optional, defaults to DefaultLockSessionName
|
|
||||||
SessionTTL string // Optional, defaults to DefaultLockSessionTTL
|
|
||||||
MonitorRetries int // Optional, defaults to 0 which means no retries
|
|
||||||
MonitorRetryTime time.Duration // Optional, defaults to DefaultMonitorRetryTime
|
|
||||||
SemaphoreWaitTime time.Duration // Optional, defaults to DefaultSemaphoreWaitTime
|
|
||||||
SemaphoreTryOnce bool // Optional, defaults to false which means try forever
|
|
||||||
Namespace string `json:",omitempty"` // Optional, defaults to API client config, namespace of ACL token, or "default" namespace
|
|
||||||
}
|
|
||||||
|
|
||||||
// semaphoreLock is written under the DefaultSemaphoreKey and
|
|
||||||
// is used to coordinate between all the contenders.
|
|
||||||
type semaphoreLock struct {
|
|
||||||
// Limit is the integer limit of holders. This is used to
|
|
||||||
// verify that all the holders agree on the value.
|
|
||||||
Limit int
|
|
||||||
|
|
||||||
// Holders is a list of all the semaphore holders.
|
|
||||||
// It maps the session ID to true. It is used as a set effectively.
|
|
||||||
Holders map[string]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SemaphorePrefix is used to created a Semaphore which will operate
|
|
||||||
// at the given KV prefix and uses the given limit for the semaphore.
|
|
||||||
// The prefix must have write privileges, and the limit must be agreed
|
|
||||||
// upon by all contenders.
|
|
||||||
func (c *Client) SemaphorePrefix(prefix string, limit int) (*Semaphore, error) {
|
|
||||||
opts := &SemaphoreOptions{
|
|
||||||
Prefix: prefix,
|
|
||||||
Limit: limit,
|
|
||||||
}
|
|
||||||
return c.SemaphoreOpts(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SemaphoreOpts is used to create a Semaphore with the given options.
|
|
||||||
// The prefix must have write privileges, and the limit must be agreed
|
|
||||||
// upon by all contenders. If a Session is not provided, one will be created.
|
|
||||||
func (c *Client) SemaphoreOpts(opts *SemaphoreOptions) (*Semaphore, error) {
|
|
||||||
if opts.Prefix == "" {
|
|
||||||
return nil, fmt.Errorf("missing prefix")
|
|
||||||
}
|
|
||||||
if opts.Limit <= 0 {
|
|
||||||
return nil, fmt.Errorf("semaphore limit must be positive")
|
|
||||||
}
|
|
||||||
if opts.SessionName == "" {
|
|
||||||
opts.SessionName = DefaultSemaphoreSessionName
|
|
||||||
}
|
|
||||||
if opts.SessionTTL == "" {
|
|
||||||
opts.SessionTTL = DefaultSemaphoreSessionTTL
|
|
||||||
} else {
|
|
||||||
if _, err := time.ParseDuration(opts.SessionTTL); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid SessionTTL: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opts.MonitorRetryTime == 0 {
|
|
||||||
opts.MonitorRetryTime = DefaultMonitorRetryTime
|
|
||||||
}
|
|
||||||
if opts.SemaphoreWaitTime == 0 {
|
|
||||||
opts.SemaphoreWaitTime = DefaultSemaphoreWaitTime
|
|
||||||
}
|
|
||||||
s := &Semaphore{
|
|
||||||
c: c,
|
|
||||||
opts: opts,
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acquire attempts to reserve a slot in the semaphore, blocking until
|
|
||||||
// success, interrupted via the stopCh or an error is encountered.
|
|
||||||
// Providing a non-nil stopCh can be used to abort the attempt.
|
|
||||||
// On success, a channel is returned that represents our slot.
|
|
||||||
// This channel could be closed at any time due to session invalidation,
|
|
||||||
// communication errors, operator intervention, etc. It is NOT safe to
|
|
||||||
// assume that the slot is held until Release() unless the Session is specifically
|
|
||||||
// created without any associated health checks. By default Consul sessions
|
|
||||||
// prefer liveness over safety and an application must be able to handle
|
|
||||||
// the session being lost.
|
|
||||||
func (s *Semaphore) Acquire(stopCh <-chan struct{}) (<-chan struct{}, error) {
|
|
||||||
// Hold the lock as we try to acquire
|
|
||||||
s.l.Lock()
|
|
||||||
defer s.l.Unlock()
|
|
||||||
|
|
||||||
// Check if we already hold the semaphore
|
|
||||||
if s.isHeld {
|
|
||||||
return nil, ErrSemaphoreHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we need to create a session first
|
|
||||||
s.lockSession = s.opts.Session
|
|
||||||
if s.lockSession == "" {
|
|
||||||
sess, err := s.createSession()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create session: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.sessionRenew = make(chan struct{})
|
|
||||||
s.lockSession = sess
|
|
||||||
session := s.c.Session()
|
|
||||||
go session.RenewPeriodic(s.opts.SessionTTL, sess, nil, s.sessionRenew)
|
|
||||||
|
|
||||||
// If we fail to acquire the lock, cleanup the session
|
|
||||||
defer func() {
|
|
||||||
if !s.isHeld {
|
|
||||||
close(s.sessionRenew)
|
|
||||||
s.sessionRenew = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the contender entry
|
|
||||||
kv := s.c.KV()
|
|
||||||
wOpts := WriteOptions{Namespace: s.opts.Namespace}
|
|
||||||
|
|
||||||
made, _, err := kv.Acquire(s.contenderEntry(s.lockSession), &wOpts)
|
|
||||||
if err != nil || !made {
|
|
||||||
return nil, fmt.Errorf("failed to make contender entry: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the query options
|
|
||||||
qOpts := QueryOptions{
|
|
||||||
WaitTime: s.opts.SemaphoreWaitTime,
|
|
||||||
Namespace: s.opts.Namespace,
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
attempts := 0
|
|
||||||
WAIT:
|
|
||||||
// Check if we should quit
|
|
||||||
select {
|
|
||||||
case <-stopCh:
|
|
||||||
return nil, nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the one-shot mode.
|
|
||||||
if s.opts.SemaphoreTryOnce && attempts > 0 {
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
if elapsed > s.opts.SemaphoreWaitTime {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query wait time should not exceed the semaphore wait time
|
|
||||||
qOpts.WaitTime = s.opts.SemaphoreWaitTime - elapsed
|
|
||||||
}
|
|
||||||
attempts++
|
|
||||||
|
|
||||||
// Read the prefix
|
|
||||||
pairs, meta, err := kv.List(s.opts.Prefix, &qOpts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read prefix: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the lock
|
|
||||||
lockPair := s.findLock(pairs)
|
|
||||||
if lockPair.Flags != SemaphoreFlagValue {
|
|
||||||
return nil, ErrSemaphoreConflict
|
|
||||||
}
|
|
||||||
lock, err := s.decodeLock(lockPair)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify we agree with the limit
|
|
||||||
if lock.Limit != s.opts.Limit {
|
|
||||||
return nil, fmt.Errorf("semaphore limit conflict (lock: %d, local: %d)",
|
|
||||||
lock.Limit, s.opts.Limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prune the dead holders
|
|
||||||
s.pruneDeadHolders(lock, pairs)
|
|
||||||
|
|
||||||
// Check if the lock is held
|
|
||||||
if len(lock.Holders) >= lock.Limit {
|
|
||||||
qOpts.WaitIndex = meta.LastIndex
|
|
||||||
goto WAIT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new lock with us as a holder
|
|
||||||
lock.Holders[s.lockSession] = true
|
|
||||||
newLock, err := s.encodeLock(lock, lockPair.ModifyIndex)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt the acquisition
|
|
||||||
didSet, _, err := kv.CAS(newLock, &wOpts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to update lock: %v", err)
|
|
||||||
}
|
|
||||||
if !didSet {
|
|
||||||
// Update failed, could have been a race with another contender,
|
|
||||||
// retry the operation
|
|
||||||
goto WAIT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch to ensure we maintain ownership of the slot
|
|
||||||
lockCh := make(chan struct{})
|
|
||||||
go s.monitorLock(s.lockSession, lockCh)
|
|
||||||
|
|
||||||
// Set that we own the lock
|
|
||||||
s.isHeld = true
|
|
||||||
|
|
||||||
// Acquired! All done
|
|
||||||
return lockCh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release is used to voluntarily give up our semaphore slot. It is
|
|
||||||
// an error to call this if the semaphore has not been acquired.
|
|
||||||
func (s *Semaphore) Release() error {
|
|
||||||
// Hold the lock as we try to release
|
|
||||||
s.l.Lock()
|
|
||||||
defer s.l.Unlock()
|
|
||||||
|
|
||||||
// Ensure the lock is actually held
|
|
||||||
if !s.isHeld {
|
|
||||||
return ErrSemaphoreNotHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set that we no longer own the lock
|
|