VOL-1558 Implementation of openolt adapter with dep for dependency management
Also updated the build system to take this into account.
Currently dep ensure fails due to missing libraries in voltha-go, but the vendor folder has been updated otherwise.
This can be worked around in development using the LOCAL_VOLTHAGO variable described in the readme
This does not build currrently, but that is due to missing code in voltha-go master.
This pattern is consistent with how voltha-go does things, but does not leave you dependent on it to build.
See the readme for how to use dep.
The resourcemanager file is no longer hidden.
Change-Id: I25b8472dbc517b193970597c9f43ddff18c2d89f
diff --git a/vendor/github.com/hashicorp/consul/LICENSE b/vendor/github.com/hashicorp/consul/LICENSE
new file mode 100644
index 0000000..c33dcc7
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/LICENSE
@@ -0,0 +1,354 @@
+Mozilla Public License, version 2.0
+
+1. Definitions
+
+1.1. “Contributor”
+
+ means each individual or legal entity that creates, contributes to the
+ creation of, or owns Covered Software.
+
+1.2. “Contributor Version”
+
+ means the combination of the Contributions of others (if any) used by a
+ Contributor and that particular Contributor’s Contribution.
+
+1.3. “Contribution”
+
+ means Covered Software of a particular Contributor.
+
+1.4. “Covered Software”
+
+ means Source Code Form to which the initial Contributor has attached the
+ notice in Exhibit A, the Executable Form of such Source Code Form, and
+ Modifications of such Source Code Form, in each case including portions
+ thereof.
+
+1.5. “Incompatible With Secondary Licenses”
+ means
+
+ a. that the initial Contributor has attached the notice described in
+ Exhibit B to the Covered Software; or
+
+ b. that the Covered Software was made available under the terms of version
+ 1.1 or earlier of the License, but not also under the terms of a
+ Secondary License.
+
+1.6. “Executable Form”
+
+ means any form of the work other than Source Code Form.
+
+1.7. “Larger Work”
+
+ means a work that combines Covered Software with other material, in a separate
+ file or files, that is not Covered Software.
+
+1.8. “License”
+
+ means this document.
+
+1.9. “Licensable”
+
+ means having the right to grant, to the maximum extent possible, whether at the
+ time of the initial grant or subsequently, any and all of the rights conveyed by
+ this License.
+
+1.10. “Modifications”
+
+ means any of the following:
+
+ a. any file in Source Code Form that results from an addition to, deletion
+ from, or modification of the contents of Covered Software; or
+
+ b. any new file in Source Code Form that contains any Covered Software.
+
+1.11. “Patent Claims” of a Contributor
+
+ means any patent claim(s), including without limitation, method, process,
+ and apparatus claims, in any patent Licensable by such Contributor that
+ would be infringed, but for the grant of the License, by the making,
+ using, selling, offering for sale, having made, import, or transfer of
+ either its Contributions or its Contributor Version.
+
+1.12. “Secondary License”
+
+ means either the GNU General Public License, Version 2.0, the GNU Lesser
+ General Public License, Version 2.1, the GNU Affero General Public
+ License, Version 3.0, or any later versions of those licenses.
+
+1.13. “Source Code Form”
+
+ means the form of the work preferred for making modifications.
+
+1.14. “You” (or “Your”)
+
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, “You” includes any entity that controls, is
+ controlled by, or is under common control with You. For purposes of this
+ definition, “control” means (a) the power, direct or indirect, to cause
+ the direction or management of such entity, whether by contract or
+ otherwise, or (b) ownership of more than fifty percent (50%) of the
+ outstanding shares or beneficial ownership of such entity.
+
+
+2. License Grants and Conditions
+
+2.1. Grants
+
+ Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ a. under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or as
+ part of a Larger Work; and
+
+ b. under Patent Claims of such Contributor to make, use, sell, offer for
+ sale, have made, import, and otherwise transfer either its Contributions
+ or its Contributor Version.
+
+2.2. Effective Date
+
+ The licenses granted in Section 2.1 with respect to any Contribution become
+ effective for each Contribution on the date the Contributor first distributes
+ such Contribution.
+
+2.3. Limitations on Grant Scope
+
+ The licenses granted in this Section 2 are the only rights granted under this
+ License. No additional rights or licenses will be implied from the distribution
+ or licensing of Covered Software under this License. Notwithstanding Section
+ 2.1(b) above, no patent license is granted by a Contributor:
+
+ a. for any code that a Contributor has removed from Covered Software; or
+
+ b. for infringements caused by: (i) Your and any other third party’s
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+ c. under Patent Claims infringed by Covered Software in the absence of its
+ Contributions.
+
+ This License does not grant any rights in the trademarks, service marks, or
+ logos of any Contributor (except as may be necessary to comply with the
+ notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+ No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this License
+ (see Section 10.2) or under the terms of a Secondary License (if permitted
+ under the terms of Section 3.3).
+
+2.5. Representation
+
+ Each Contributor represents that the Contributor believes its Contributions
+ are its original creation(s) or it has sufficient rights to grant the
+ rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+ This License is not intended to limit any rights You have under applicable
+ copyright doctrines of fair use, fair dealing, or other equivalents.
+
+2.7. Conditions
+
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
+ Section 2.1.
+
+
+3. Responsibilities
+
+3.1. Distribution of Source Form
+
+ All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under the
+ terms of this License. You must inform recipients that the Source Code Form
+ of the Covered Software is governed by the terms of this License, and how
+ they can obtain a copy of this License. You may not attempt to alter or
+ restrict the recipients’ rights in the Source Code Form.
+
+3.2. Distribution of Executable Form
+
+ If You distribute Covered Software in Executable Form then:
+
+ a. such Covered Software must also be made available in Source Code Form,
+ as described in Section 3.1, and You must inform recipients of the
+ Executable Form how they can obtain a copy of such Source Code Form by
+ reasonable means in a timely manner, at a charge no more than the cost
+ of distribution to the recipient; and
+
+ b. You may distribute such Executable Form under the terms of this License,
+ or sublicense it under different terms, provided that the license for
+ the Executable Form does not attempt to limit or alter the recipients’
+ rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+ You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for the
+ Covered Software. If the Larger Work is a combination of Covered Software
+ with a work governed by one or more Secondary Licenses, and the Covered
+ Software is not Incompatible With Secondary Licenses, this License permits
+ You to additionally distribute such Covered Software under the terms of
+ such Secondary License(s), so that the recipient of the Larger Work may, at
+ their option, further distribute the Covered Software under the terms of
+ either this License or such Secondary License(s).
+
+3.4. Notices
+
+ You may not remove or alter the substance of any license notices (including
+ copyright notices, patent notices, disclaimers of warranty, or limitations
+ of liability) contained within the Source Code Form of the Covered
+ Software, except that You may alter any license notices to the extent
+ required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+ You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on behalf
+ of any Contributor. You must make it absolutely clear that any such
+ warranty, support, indemnity, or liability obligation is offered by You
+ alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+
+ If it is impossible for You to comply with any of the terms of this License
+ with respect to some or all of the Covered Software due to statute, judicial
+ order, or regulation then You must: (a) comply with the terms of this License
+ to the maximum extent possible; and (b) describe the limitations and the code
+ they affect. Such description must be placed in a text file included with all
+ distributions of the Covered Software under this License. Except to the
+ extent prohibited by statute or regulation, such description must be
+ sufficiently detailed for a recipient of ordinary skill to be able to
+ understand it.
+
+5. Termination
+
+5.1. The rights granted under this License will terminate automatically if You
+ fail to comply with any of its terms. However, if You become compliant,
+ then the rights granted under this License from a particular Contributor
+ are reinstated (a) provisionally, unless and until such Contributor
+ explicitly and finally terminates Your grants, and (b) on an ongoing basis,
+ if such Contributor fails to notify You of the non-compliance by some
+ reasonable means prior to 60 days after You have come back into compliance.
+ Moreover, Your grants from a particular Contributor are reinstated on an
+ ongoing basis if such Contributor notifies You of the non-compliance by
+ some reasonable means, this is the first time You have received notice of
+ non-compliance with this License from such Contributor, and You become
+ compliant prior to 30 days after Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions, counter-claims,
+ and cross-claims) alleging that a Contributor Version directly or
+ indirectly infringes any patent, then the rights granted to You by any and
+ all Contributors for the Covered Software under Section 2.1 of this License
+ shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
+ license agreements (excluding distributors and resellers) which have been
+ validly granted by You or Your distributors under this License prior to
+ termination shall survive termination.
+
+6. Disclaimer of Warranty
+
+ Covered Software is provided under this License on an “as is” basis, without
+ warranty of any kind, either expressed, implied, or statutory, including,
+ without limitation, warranties that the Covered Software is free of defects,
+ merchantable, fit for a particular purpose or non-infringing. The entire
+ risk as to the quality and performance of the Covered Software is with You.
+ Should any Covered Software prove defective in any respect, You (not any
+ Contributor) assume the cost of any necessary servicing, repair, or
+ correction. This disclaimer of warranty constitutes an essential part of this
+ License. No use of any Covered Software is authorized under this License
+ except under this disclaimer.
+
+7. Limitation of Liability
+
+ Under no circumstances and under no legal theory, whether tort (including
+ negligence), contract, or otherwise, shall any Contributor, or anyone who
+ distributes Covered Software as permitted above, be liable to You for any
+ direct, indirect, special, incidental, or consequential damages of any
+ character including, without limitation, damages for lost profits, loss of
+ goodwill, work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses, even if such party shall have been
+ informed of the possibility of such damages. This limitation of liability
+ shall not apply to liability for death or personal injury resulting from such
+ party’s negligence to the extent applicable law prohibits such limitation.
+ Some jurisdictions do not allow the exclusion or limitation of incidental or
+ consequential damages, so this exclusion and limitation may not apply to You.
+
+8. Litigation
+
+ Any litigation relating to this License may be brought only in the courts of
+ a jurisdiction where the defendant maintains its principal place of business
+ and such litigation shall be governed by laws of that jurisdiction, without
+ reference to its conflict-of-law provisions. Nothing in this Section shall
+ prevent a party’s ability to bring cross-claims or counter-claims.
+
+9. Miscellaneous
+
+ This License represents the complete agreement concerning the subject matter
+ hereof. If any provision of this License is held to be unenforceable, such
+ provision shall be reformed only to the extent necessary to make it
+ enforceable. Any law or regulation which provides that the language of a
+ contract shall be construed against the drafter shall not be used to construe
+ this License against a Contributor.
+
+
+10. Versions of the License
+
+10.1. New Versions
+
+ Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.
+
+10.2. Effect of New Versions
+
+ You may distribute the Covered Software under the terms of the version of
+ the License under which You originally received the Covered Software, or
+ under the terms of any subsequent version published by the license
+ steward.
+
+10.3. Modified Versions
+
+ If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a modified
+ version of this License if you rename the license and remove any
+ references to the name of the license steward (except to note that such
+ modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
+ If You choose to distribute Source Code Form that is Incompatible With
+ Secondary Licenses under the terms of this version of the License, the
+ notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+
+ This Source Code Form is subject to the
+ terms of the Mozilla Public License, v.
+ 2.0. If a copy of the MPL was not
+ distributed with this file, You can
+ obtain one at
+ http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular file, then
+You may include the notice in a location (such as a LICENSE file in a relevant
+directory) where a recipient would be likely to look for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - “Incompatible With Secondary Licenses” Notice
+
+ This Source Code Form is “Incompatible
+ With Secondary Licenses”, as defined by
+ the Mozilla Public License, v. 2.0.
+
diff --git a/vendor/github.com/hashicorp/consul/NOTICE.md b/vendor/github.com/hashicorp/consul/NOTICE.md
new file mode 100644
index 0000000..fe34b5e
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/NOTICE.md
@@ -0,0 +1,3 @@
+Copyright © 2014-2018 HashiCorp, Inc.
+
+This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this project, you can obtain one at http://mozilla.org/MPL/2.0/.
diff --git a/vendor/github.com/hashicorp/consul/api/README.md b/vendor/github.com/hashicorp/consul/api/README.md
new file mode 100644
index 0000000..3255cbb
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/README.md
@@ -0,0 +1,67 @@
+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
diff --git a/vendor/github.com/hashicorp/consul/api/acl.go b/vendor/github.com/hashicorp/consul/api/acl.go
new file mode 100644
index 0000000..53a0523
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/acl.go
@@ -0,0 +1,588 @@
+package api
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "time"
+)
+
+const (
+ // ACLClientType is the client type token
+ ACLClientType = "client"
+
+ // ACLManagementType is the management type token
+ ACLManagementType = "management"
+)
+
+type ACLTokenPolicyLink struct {
+ ID string
+ Name string
+}
+
+// ACLToken represents an ACL Token
+type ACLToken struct {
+ CreateIndex uint64
+ ModifyIndex uint64
+ AccessorID string
+ SecretID string
+ Description string
+ Policies []*ACLTokenPolicyLink
+ Local bool
+ CreateTime time.Time `json:",omitempty"`
+ Hash []byte `json:",omitempty"`
+
+ // DEPRECATED (ACL-Legacy-Compat)
+ // Rules will only be present for legacy tokens returned via the new APIs
+ Rules string `json:",omitempty"`
+}
+
+type ACLTokenListEntry struct {
+ CreateIndex uint64
+ ModifyIndex uint64
+ AccessorID string
+ Description string
+ Policies []*ACLTokenPolicyLink
+ Local bool
+ CreateTime time.Time
+ Hash []byte
+ Legacy bool
+}
+
+// ACLEntry is used to represent a legacy ACL token
+// The legacy tokens are deprecated.
+type ACLEntry struct {
+ CreateIndex uint64
+ ModifyIndex uint64
+ ID string
+ Name string
+ Type string
+ Rules string
+}
+
+// ACLReplicationStatus is used to represent the status of ACL replication.
+type ACLReplicationStatus struct {
+ Enabled bool
+ Running bool
+ SourceDatacenter string
+ ReplicationType string
+ ReplicatedIndex uint64
+ ReplicatedTokenIndex uint64
+ LastSuccess time.Time
+ LastError time.Time
+}
+
+// ACLPolicy represents an ACL Policy.
+type ACLPolicy struct {
+ ID string
+ Name string
+ Description string
+ Rules string
+ Datacenters []string
+ Hash []byte
+ CreateIndex uint64
+ ModifyIndex uint64
+}
+
+type ACLPolicyListEntry struct {
+ ID string
+ Name string
+ Description string
+ Datacenters []string
+ Hash []byte
+ CreateIndex uint64
+ ModifyIndex uint64
+}
+
+// ACL can be used to query the ACL endpoints
+type ACL struct {
+ c *Client
+}
+
+// ACL returns a handle to the ACL endpoints
+func (c *Client) ACL() *ACL {
+ return &ACL{c}
+}
+
+// Bootstrap is used to perform a one-time ACL bootstrap operation on a cluster
+// to get the first management token.
+func (a *ACL) Bootstrap() (*ACLToken, *WriteMeta, error) {
+ r := a.c.newRequest("PUT", "/v1/acl/bootstrap")
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ var out ACLToken
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+ return &out, wm, nil
+}
+
+// Create is used to generate a new token with the given parameters
+//
+// Deprecated: Use TokenCreate instead.
+func (a *ACL) Create(acl *ACLEntry, q *WriteOptions) (string, *WriteMeta, error) {
+ r := a.c.newRequest("PUT", "/v1/acl/create")
+ r.setWriteOptions(q)
+ r.obj = acl
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return "", nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ var out struct{ ID string }
+ if err := decodeBody(resp, &out); err != nil {
+ return "", nil, err
+ }
+ return out.ID, wm, nil
+}
+
+// Update is used to update the rules of an existing token
+//
+// Deprecated: Use TokenUpdate instead.
+func (a *ACL) Update(acl *ACLEntry, q *WriteOptions) (*WriteMeta, error) {
+ r := a.c.newRequest("PUT", "/v1/acl/update")
+ r.setWriteOptions(q)
+ r.obj = acl
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ return wm, nil
+}
+
+// Destroy is used to destroy a given ACL token ID
+//
+// Deprecated: Use TokenDelete instead.
+func (a *ACL) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
+ r := a.c.newRequest("PUT", "/v1/acl/destroy/"+id)
+ r.setWriteOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ return wm, nil
+}
+
+// Clone is used to return a new token cloned from an existing one
+//
+// Deprecated: Use TokenClone instead.
+func (a *ACL) Clone(id string, q *WriteOptions) (string, *WriteMeta, error) {
+ r := a.c.newRequest("PUT", "/v1/acl/clone/"+id)
+ r.setWriteOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return "", nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ var out struct{ ID string }
+ if err := decodeBody(resp, &out); err != nil {
+ return "", nil, err
+ }
+ return out.ID, wm, nil
+}
+
+// Info is used to query for information about an ACL token
+//
+// Deprecated: Use TokenRead instead.
+func (a *ACL) Info(id string, q *QueryOptions) (*ACLEntry, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/acl/info/"+id)
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var entries []*ACLEntry
+ 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 get all the ACL tokens
+//
+// Deprecated: Use TokenList instead.
+func (a *ACL) List(q *QueryOptions) ([]*ACLEntry, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/acl/list")
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var entries []*ACLEntry
+ if err := decodeBody(resp, &entries); err != nil {
+ return nil, nil, err
+ }
+ return entries, qm, nil
+}
+
+// Replication returns the status of the ACL replication process in the datacenter
+func (a *ACL) Replication(q *QueryOptions) (*ACLReplicationStatus, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/acl/replication")
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var entries *ACLReplicationStatus
+ if err := decodeBody(resp, &entries); err != nil {
+ return nil, nil, err
+ }
+ return entries, qm, nil
+}
+
+// TokenCreate creates a new ACL token. It requires that the AccessorID and SecretID fields
+// of the ACLToken structure to be empty as these will be filled in by Consul.
+func (a *ACL) TokenCreate(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
+ if token.AccessorID != "" {
+ return nil, nil, fmt.Errorf("Cannot specify an AccessorID in Token Creation")
+ }
+
+ if token.SecretID != "" {
+ return nil, nil, fmt.Errorf("Cannot specify a SecretID in Token Creation")
+ }
+
+ r := a.c.newRequest("PUT", "/v1/acl/token")
+ r.setWriteOptions(q)
+ r.obj = token
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ var out ACLToken
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+
+ return &out, wm, nil
+}
+
+// TokenUpdate updates a token in place without modifying its AccessorID or SecretID. A valid
+// AccessorID must be set in the ACLToken structure passed to this function but the SecretID may
+// be omitted and will be filled in by Consul with its existing value.
+func (a *ACL) TokenUpdate(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
+ if token.AccessorID == "" {
+ return nil, nil, fmt.Errorf("Must specify an AccessorID for Token Updating")
+ }
+ r := a.c.newRequest("PUT", "/v1/acl/token/"+token.AccessorID)
+ r.setWriteOptions(q)
+ r.obj = token
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ var out ACLToken
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+
+ return &out, wm, nil
+}
+
+// TokenClone will create a new token with the same policies and locality as the original
+// token but will have its own auto-generated AccessorID and SecretID as well having the
+// description passed to this function. The tokenID parameter must be a valid Accessor ID
+// of an existing token.
+func (a *ACL) TokenClone(tokenID string, description string, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
+ if tokenID == "" {
+ return nil, nil, fmt.Errorf("Must specify a tokenID for Token Cloning")
+ }
+
+ r := a.c.newRequest("PUT", "/v1/acl/token/"+tokenID+"/clone")
+ r.setWriteOptions(q)
+ r.obj = struct{ Description string }{description}
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ var out ACLToken
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+
+ return &out, wm, nil
+}
+
+// TokenDelete removes a single ACL token. The tokenID parameter must be a valid
+// Accessor ID of an existing token.
+func (a *ACL) TokenDelete(tokenID string, q *WriteOptions) (*WriteMeta, error) {
+ r := a.c.newRequest("DELETE", "/v1/acl/token/"+tokenID)
+ r.setWriteOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ return wm, nil
+}
+
+// TokenRead retrieves the full token details. The tokenID parameter must be a valid
+// Accessor ID of an existing token.
+func (a *ACL) TokenRead(tokenID string, q *QueryOptions) (*ACLToken, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/acl/token/"+tokenID)
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var out ACLToken
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+
+ return &out, qm, nil
+}
+
+// TokenReadSelf retrieves the full token details of the token currently
+// assigned to the API Client. In this manner its possible to read a token
+// by its Secret ID.
+func (a *ACL) TokenReadSelf(q *QueryOptions) (*ACLToken, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/acl/token/self")
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var out ACLToken
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+
+ return &out, qm, nil
+}
+
+// TokenList lists all tokens. The listing does not contain any SecretIDs as those
+// may only be retrieved by a call to TokenRead.
+func (a *ACL) TokenList(q *QueryOptions) ([]*ACLTokenListEntry, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/acl/tokens")
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var entries []*ACLTokenListEntry
+ if err := decodeBody(resp, &entries); err != nil {
+ return nil, nil, err
+ }
+ return entries, qm, nil
+}
+
+// PolicyCreate will create a new policy. It is not allowed for the policy parameters
+// ID field to be set as this will be generated by Consul while processing the request.
+func (a *ACL) PolicyCreate(policy *ACLPolicy, q *WriteOptions) (*ACLPolicy, *WriteMeta, error) {
+ if policy.ID != "" {
+ return nil, nil, fmt.Errorf("Cannot specify an ID in Policy Creation")
+ }
+
+ r := a.c.newRequest("PUT", "/v1/acl/policy")
+ r.setWriteOptions(q)
+ r.obj = policy
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ var out ACLPolicy
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+
+ return &out, wm, nil
+}
+
+// PolicyUpdate updates a policy. The ID field of the policy parameter must be set to an
+// existing policy ID
+func (a *ACL) PolicyUpdate(policy *ACLPolicy, q *WriteOptions) (*ACLPolicy, *WriteMeta, error) {
+ if policy.ID == "" {
+ return nil, nil, fmt.Errorf("Must specify an ID in Policy Creation")
+ }
+
+ r := a.c.newRequest("PUT", "/v1/acl/policy/"+policy.ID)
+ r.setWriteOptions(q)
+ r.obj = policy
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ var out ACLPolicy
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+
+ return &out, wm, nil
+}
+
+// PolicyDelete deletes a policy given its ID.
+func (a *ACL) PolicyDelete(policyID string, q *WriteOptions) (*WriteMeta, error) {
+ r := a.c.newRequest("DELETE", "/v1/acl/policy/"+policyID)
+ r.setWriteOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ return wm, nil
+}
+
+// PolicyRead retrieves the policy details including the rule set.
+func (a *ACL) PolicyRead(policyID string, q *QueryOptions) (*ACLPolicy, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/acl/policy/"+policyID)
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var out ACLPolicy
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+
+ return &out, qm, nil
+}
+
+// PolicyList retrieves a listing of all policies. The listing does not include the
+// rules for any policy as those should be retrieved by subsequent calls to PolicyRead.
+func (a *ACL) PolicyList(q *QueryOptions) ([]*ACLPolicyListEntry, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/acl/policies")
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var entries []*ACLPolicyListEntry
+ if err := decodeBody(resp, &entries); err != nil {
+ return nil, nil, err
+ }
+ return entries, qm, nil
+}
+
+// RulesTranslate translates the legacy rule syntax into the current syntax.
+//
+// Deprecated: Support for the legacy syntax translation will be removed
+// when legacy ACL support is removed.
+func (a *ACL) RulesTranslate(rules io.Reader) (string, error) {
+ r := a.c.newRequest("POST", "/v1/acl/rules/translate")
+ r.body = rules
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ ruleBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", fmt.Errorf("Failed to read translated rule body: %v", err)
+ }
+
+ return string(ruleBytes), nil
+}
+
+// RulesTranslateToken translates the rules associated with the legacy syntax
+// into the current syntax and returns the results.
+//
+// Deprecated: Support for the legacy syntax translation will be removed
+// when legacy ACL support is removed.
+func (a *ACL) RulesTranslateToken(tokenID string) (string, error) {
+ r := a.c.newRequest("GET", "/v1/acl/rules/translate/"+tokenID)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ ruleBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", fmt.Errorf("Failed to read translated rule body: %v", err)
+ }
+
+ return string(ruleBytes), nil
+}
diff --git a/vendor/github.com/hashicorp/consul/api/agent.go b/vendor/github.com/hashicorp/consul/api/agent.go
new file mode 100644
index 0000000..6acf8ad
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/agent.go
@@ -0,0 +1,1011 @@
+package api
+
+import (
+ "bufio"
+ "fmt"
+ "net/http"
+ "net/url"
+)
+
+// ServiceKind is the kind of service being registered.
+type ServiceKind string
+
+const (
+ // ServiceKindTypical is a typical, classic Consul service. This is
+ // represented by the absence of a value. This was chosen for ease of
+ // backwards compatibility: existing services in the catalog would
+ // default to the typical service.
+ ServiceKindTypical ServiceKind = ""
+
+ // ServiceKindConnectProxy is a proxy for the Connect feature. This
+ // service proxies another service within Consul and speaks the connect
+ // protocol.
+ ServiceKindConnectProxy ServiceKind = "connect-proxy"
+)
+
+// ProxyExecMode is the execution mode for a managed Connect proxy.
+type ProxyExecMode string
+
+const (
+ // ProxyExecModeDaemon indicates that the proxy command should be long-running
+ // and should be started and supervised by the agent until it's target service
+ // is deregistered.
+ ProxyExecModeDaemon ProxyExecMode = "daemon"
+
+ // ProxyExecModeScript indicates that the proxy command should be invoke to
+ // completion on each change to the configuration of lifecycle event. The
+ // script typically fetches the config and certificates from the agent API and
+ // then configures an externally managed daemon, perhaps starting and stopping
+ // it if necessary.
+ ProxyExecModeScript ProxyExecMode = "script"
+)
+
+// UpstreamDestType is the type of upstream discovery mechanism.
+type UpstreamDestType string
+
+const (
+ // UpstreamDestTypeService discovers instances via healthy service lookup.
+ UpstreamDestTypeService UpstreamDestType = "service"
+
+ // UpstreamDestTypePreparedQuery discovers instances via prepared query
+ // execution.
+ UpstreamDestTypePreparedQuery UpstreamDestType = "prepared_query"
+)
+
+// AgentCheck represents a check known to the agent
+type AgentCheck struct {
+ Node string
+ CheckID string
+ Name string
+ Status string
+ Notes string
+ Output string
+ ServiceID string
+ ServiceName string
+ Definition HealthCheckDefinition
+}
+
+// AgentWeights represent optional weights for a service
+type AgentWeights struct {
+ Passing int
+ Warning int
+}
+
+// AgentService represents a service known to the agent
+type AgentService struct {
+ Kind ServiceKind `json:",omitempty"`
+ ID string
+ Service string
+ Tags []string
+ Meta map[string]string
+ Port int
+ Address string
+ Weights AgentWeights
+ EnableTagOverride bool
+ CreateIndex uint64 `json:",omitempty"`
+ ModifyIndex uint64 `json:",omitempty"`
+ ContentHash string `json:",omitempty"`
+ // DEPRECATED (ProxyDestination) - remove this field
+ ProxyDestination string `json:",omitempty"`
+ Proxy *AgentServiceConnectProxyConfig `json:",omitempty"`
+ Connect *AgentServiceConnect `json:",omitempty"`
+}
+
+// AgentServiceChecksInfo returns information about a Service and its checks
+type AgentServiceChecksInfo struct {
+ AggregatedStatus string
+ Service *AgentService
+ Checks HealthChecks
+}
+
+// AgentServiceConnect represents the Connect configuration of a service.
+type AgentServiceConnect struct {
+ Native bool `json:",omitempty"`
+ Proxy *AgentServiceConnectProxy `json:",omitempty"`
+ SidecarService *AgentServiceRegistration `json:",omitempty"`
+}
+
+// AgentServiceConnectProxy represents the Connect Proxy configuration of a
+// service.
+type AgentServiceConnectProxy struct {
+ ExecMode ProxyExecMode `json:",omitempty"`
+ Command []string `json:",omitempty"`
+ Config map[string]interface{} `json:",omitempty"`
+ Upstreams []Upstream `json:",omitempty"`
+}
+
+// AgentServiceConnectProxyConfig is the proxy configuration in a connect-proxy
+// ServiceDefinition or response.
+type AgentServiceConnectProxyConfig struct {
+ DestinationServiceName string
+ DestinationServiceID string `json:",omitempty"`
+ LocalServiceAddress string `json:",omitempty"`
+ LocalServicePort int `json:",omitempty"`
+ Config map[string]interface{} `json:",omitempty"`
+ Upstreams []Upstream
+}
+
+// AgentMember represents a cluster member known to the agent
+type AgentMember struct {
+ Name string
+ Addr string
+ Port uint16
+ Tags map[string]string
+ Status int
+ ProtocolMin uint8
+ ProtocolMax uint8
+ ProtocolCur uint8
+ DelegateMin uint8
+ DelegateMax uint8
+ DelegateCur uint8
+}
+
+// AllSegments is used to select for all segments in MembersOpts.
+const AllSegments = "_all"
+
+// MembersOpts is used for querying member information.
+type MembersOpts struct {
+ // WAN is whether to show members from the WAN.
+ WAN bool
+
+ // Segment is the LAN segment to show members for. Setting this to the
+ // AllSegments value above will show members in all segments.
+ Segment string
+}
+
+// AgentServiceRegistration is used to register a new service
+type AgentServiceRegistration struct {
+ Kind ServiceKind `json:",omitempty"`
+ ID string `json:",omitempty"`
+ Name string `json:",omitempty"`
+ Tags []string `json:",omitempty"`
+ Port int `json:",omitempty"`
+ Address string `json:",omitempty"`
+ EnableTagOverride bool `json:",omitempty"`
+ Meta map[string]string `json:",omitempty"`
+ Weights *AgentWeights `json:",omitempty"`
+ Check *AgentServiceCheck
+ Checks AgentServiceChecks
+ // DEPRECATED (ProxyDestination) - remove this field
+ ProxyDestination string `json:",omitempty"`
+ Proxy *AgentServiceConnectProxyConfig `json:",omitempty"`
+ Connect *AgentServiceConnect `json:",omitempty"`
+}
+
+// AgentCheckRegistration is used to register a new check
+type AgentCheckRegistration struct {
+ ID string `json:",omitempty"`
+ Name string `json:",omitempty"`
+ Notes string `json:",omitempty"`
+ ServiceID string `json:",omitempty"`
+ AgentServiceCheck
+}
+
+// AgentServiceCheck is used to define a node or service level check
+type AgentServiceCheck struct {
+ CheckID string `json:",omitempty"`
+ Name string `json:",omitempty"`
+ Args []string `json:"ScriptArgs,omitempty"`
+ DockerContainerID string `json:",omitempty"`
+ Shell string `json:",omitempty"` // Only supported for Docker.
+ Interval string `json:",omitempty"`
+ Timeout string `json:",omitempty"`
+ TTL string `json:",omitempty"`
+ HTTP string `json:",omitempty"`
+ Header map[string][]string `json:",omitempty"`
+ Method string `json:",omitempty"`
+ TCP string `json:",omitempty"`
+ Status string `json:",omitempty"`
+ Notes string `json:",omitempty"`
+ TLSSkipVerify bool `json:",omitempty"`
+ GRPC string `json:",omitempty"`
+ GRPCUseTLS bool `json:",omitempty"`
+ AliasNode string `json:",omitempty"`
+ AliasService string `json:",omitempty"`
+
+ // In Consul 0.7 and later, checks that are associated with a service
+ // may also contain this optional DeregisterCriticalServiceAfter field,
+ // which is a timeout in the same Go time format as Interval and TTL. If
+ // a check is in the critical state for more than this configured value,
+ // then its associated service (and all of its associated checks) will
+ // automatically be deregistered.
+ DeregisterCriticalServiceAfter string `json:",omitempty"`
+}
+type AgentServiceChecks []*AgentServiceCheck
+
+// AgentToken is used when updating ACL tokens for an agent.
+type AgentToken struct {
+ Token string
+}
+
+// Metrics info is used to store different types of metric values from the agent.
+type MetricsInfo struct {
+ Timestamp string
+ Gauges []GaugeValue
+ Points []PointValue
+ Counters []SampledValue
+ Samples []SampledValue
+}
+
+// GaugeValue stores one value that is updated as time goes on, such as
+// the amount of memory allocated.
+type GaugeValue struct {
+ Name string
+ Value float32
+ Labels map[string]string
+}
+
+// PointValue holds a series of points for a metric.
+type PointValue struct {
+ Name string
+ Points []float32
+}
+
+// SampledValue stores info about a metric that is incremented over time,
+// such as the number of requests to an HTTP endpoint.
+type SampledValue struct {
+ Name string
+ Count int
+ Sum float64
+ Min float64
+ Max float64
+ Mean float64
+ Stddev float64
+ Labels map[string]string
+}
+
+// AgentAuthorizeParams are the request parameters for authorizing a request.
+type AgentAuthorizeParams struct {
+ Target string
+ ClientCertURI string
+ ClientCertSerial string
+}
+
+// AgentAuthorize is the response structure for Connect authorization.
+type AgentAuthorize struct {
+ Authorized bool
+ Reason string
+}
+
+// ConnectProxyConfig is the response structure for agent-local proxy
+// configuration.
+type ConnectProxyConfig struct {
+ ProxyServiceID string
+ TargetServiceID string
+ TargetServiceName string
+ ContentHash string
+ // DEPRECATED(managed-proxies) - this struct is re-used for sidecar configs
+ // but they don't need ExecMode or Command
+ ExecMode ProxyExecMode `json:",omitempty"`
+ Command []string `json:",omitempty"`
+ Config map[string]interface{}
+ Upstreams []Upstream
+}
+
+// Upstream is the response structure for a proxy upstream configuration.
+type Upstream struct {
+ DestinationType UpstreamDestType `json:",omitempty"`
+ DestinationNamespace string `json:",omitempty"`
+ DestinationName string
+ Datacenter string `json:",omitempty"`
+ LocalBindAddress string `json:",omitempty"`
+ LocalBindPort int `json:",omitempty"`
+ Config map[string]interface{} `json:",omitempty"`
+}
+
+// Agent can be used to query the Agent endpoints
+type Agent struct {
+ c *Client
+
+ // cache the node name
+ nodeName string
+}
+
+// Agent returns a handle to the agent endpoints
+func (c *Client) Agent() *Agent {
+ return &Agent{c: c}
+}
+
+// Self is used to query the agent we are speaking to for
+// information about itself
+func (a *Agent) Self() (map[string]map[string]interface{}, error) {
+ r := a.c.newRequest("GET", "/v1/agent/self")
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var out map[string]map[string]interface{}
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// Host is used to retrieve information about the host the
+// agent is running on such as CPU, memory, and disk. Requires
+// a operator:read ACL token.
+func (a *Agent) Host() (map[string]interface{}, error) {
+ r := a.c.newRequest("GET", "/v1/agent/host")
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var out map[string]interface{}
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// Metrics is used to query the agent we are speaking to for
+// its current internal metric data
+func (a *Agent) Metrics() (*MetricsInfo, error) {
+ r := a.c.newRequest("GET", "/v1/agent/metrics")
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var out *MetricsInfo
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// Reload triggers a configuration reload for the agent we are connected to.
+func (a *Agent) Reload() error {
+ r := a.c.newRequest("PUT", "/v1/agent/reload")
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// NodeName is used to get the node name of the agent
+func (a *Agent) NodeName() (string, error) {
+ if a.nodeName != "" {
+ return a.nodeName, nil
+ }
+ info, err := a.Self()
+ if err != nil {
+ return "", err
+ }
+ name := info["Config"]["NodeName"].(string)
+ a.nodeName = name
+ return name, nil
+}
+
+// Checks returns the locally registered checks
+func (a *Agent) Checks() (map[string]*AgentCheck, error) {
+ r := a.c.newRequest("GET", "/v1/agent/checks")
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var out map[string]*AgentCheck
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// Services returns the locally registered services
+func (a *Agent) Services() (map[string]*AgentService, error) {
+ r := a.c.newRequest("GET", "/v1/agent/services")
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var out map[string]*AgentService
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, err
+ }
+
+ return out, nil
+}
+
+// AgentHealthServiceByID returns for a given serviceID: the aggregated health status, the service definition or an error if any
+// - If the service is not found, will return status (critical, nil, nil)
+// - If the service is found, will return (critical|passing|warning), AgentServiceChecksInfo, nil)
+// - In all other cases, will return an error
+func (a *Agent) AgentHealthServiceByID(serviceID string) (string, *AgentServiceChecksInfo, error) {
+ path := fmt.Sprintf("/v1/agent/health/service/id/%v", url.PathEscape(serviceID))
+ r := a.c.newRequest("GET", path)
+ r.params.Add("format", "json")
+ r.header.Set("Accept", "application/json")
+ _, resp, err := a.c.doRequest(r)
+ if err != nil {
+ return "", nil, err
+ }
+ defer resp.Body.Close()
+ // Service not Found
+ if resp.StatusCode == http.StatusNotFound {
+ return HealthCritical, nil, nil
+ }
+ var out *AgentServiceChecksInfo
+ if err := decodeBody(resp, &out); err != nil {
+ return HealthCritical, out, err
+ }
+ switch resp.StatusCode {
+ case http.StatusOK:
+ return HealthPassing, out, nil
+ case http.StatusTooManyRequests:
+ return HealthWarning, out, nil
+ case http.StatusServiceUnavailable:
+ return HealthCritical, out, nil
+ }
+ return HealthCritical, out, fmt.Errorf("Unexpected Error Code %v for %s", resp.StatusCode, path)
+}
+
+// AgentHealthServiceByName returns for a given service name: the aggregated health status for all services
+// having the specified name.
+// - If no service is not found, will return status (critical, [], nil)
+// - If the service is found, will return (critical|passing|warning), []api.AgentServiceChecksInfo, nil)
+// - In all other cases, will return an error
+func (a *Agent) AgentHealthServiceByName(service string) (string, []AgentServiceChecksInfo, error) {
+ path := fmt.Sprintf("/v1/agent/health/service/name/%v", url.PathEscape(service))
+ r := a.c.newRequest("GET", path)
+ r.params.Add("format", "json")
+ r.header.Set("Accept", "application/json")
+ _, resp, err := a.c.doRequest(r)
+ if err != nil {
+ return "", nil, err
+ }
+ defer resp.Body.Close()
+ // Service not Found
+ if resp.StatusCode == http.StatusNotFound {
+ return HealthCritical, nil, nil
+ }
+ var out []AgentServiceChecksInfo
+ if err := decodeBody(resp, &out); err != nil {
+ return HealthCritical, out, err
+ }
+ switch resp.StatusCode {
+ case http.StatusOK:
+ return HealthPassing, out, nil
+ case http.StatusTooManyRequests:
+ return HealthWarning, out, nil
+ case http.StatusServiceUnavailable:
+ return HealthCritical, out, nil
+ }
+ return HealthCritical, out, fmt.Errorf("Unexpected Error Code %v for %s", resp.StatusCode, path)
+}
+
+// Service returns a locally registered service instance and allows for
+// hash-based blocking.
+//
+// Note that this uses an unconventional blocking mechanism since it's
+// agent-local state. That means there is no persistent raft index so we block
+// based on object hash instead.
+func (a *Agent) Service(serviceID string, q *QueryOptions) (*AgentService, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/agent/service/"+serviceID)
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var out *AgentService
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+
+ return out, qm, nil
+}
+
+// Members returns the known gossip members. The WAN
+// flag can be used to query a server for WAN members.
+func (a *Agent) Members(wan bool) ([]*AgentMember, error) {
+ r := a.c.newRequest("GET", "/v1/agent/members")
+ if wan {
+ r.params.Set("wan", "1")
+ }
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var out []*AgentMember
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// MembersOpts returns the known gossip members and can be passed
+// additional options for WAN/segment filtering.
+func (a *Agent) MembersOpts(opts MembersOpts) ([]*AgentMember, error) {
+ r := a.c.newRequest("GET", "/v1/agent/members")
+ r.params.Set("segment", opts.Segment)
+ if opts.WAN {
+ r.params.Set("wan", "1")
+ }
+
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var out []*AgentMember
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// ServiceRegister is used to register a new service with
+// the local agent
+func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
+ r := a.c.newRequest("PUT", "/v1/agent/service/register")
+ r.obj = service
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// ServiceDeregister is used to deregister a service with
+// the local agent
+func (a *Agent) ServiceDeregister(serviceID string) error {
+ r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID)
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// PassTTL is used to set a TTL check to the passing state.
+//
+// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
+// The client interface will be removed in 0.8 or changed to use
+// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
+func (a *Agent) PassTTL(checkID, note string) error {
+ return a.updateTTL(checkID, note, "pass")
+}
+
+// WarnTTL is used to set a TTL check to the warning state.
+//
+// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
+// The client interface will be removed in 0.8 or changed to use
+// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
+func (a *Agent) WarnTTL(checkID, note string) error {
+ return a.updateTTL(checkID, note, "warn")
+}
+
+// FailTTL is used to set a TTL check to the failing state.
+//
+// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
+// The client interface will be removed in 0.8 or changed to use
+// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
+func (a *Agent) FailTTL(checkID, note string) error {
+ return a.updateTTL(checkID, note, "fail")
+}
+
+// updateTTL is used to update the TTL of a check. This is the internal
+// method that uses the old API that's present in Consul versions prior to
+// 0.6.4. Since Consul didn't have an analogous "update" API before it seemed
+// ok to break this (former) UpdateTTL in favor of the new UpdateTTL below,
+// but keep the old Pass/Warn/Fail methods using the old API under the hood.
+//
+// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
+// The client interface will be removed in 0.8 and the server endpoints will
+// be removed in 0.9.
+func (a *Agent) updateTTL(checkID, note, status string) error {
+ switch status {
+ case "pass":
+ case "warn":
+ case "fail":
+ default:
+ return fmt.Errorf("Invalid status: %s", status)
+ }
+ endpoint := fmt.Sprintf("/v1/agent/check/%s/%s", status, checkID)
+ r := a.c.newRequest("PUT", endpoint)
+ r.params.Set("note", note)
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// checkUpdate is the payload for a PUT for a check update.
+type checkUpdate struct {
+ // Status is one of the api.Health* states: HealthPassing
+ // ("passing"), HealthWarning ("warning"), or HealthCritical
+ // ("critical").
+ Status string
+
+ // Output is the information to post to the UI for operators as the
+ // output of the process that decided to hit the TTL check. This is
+ // different from the note field that's associated with the check
+ // itself.
+ Output string
+}
+
+// UpdateTTL is used to update the TTL of a check. This uses the newer API
+// that was introduced in Consul 0.6.4 and later. We translate the old status
+// strings for compatibility (though a newer version of Consul will still be
+// required to use this API).
+func (a *Agent) UpdateTTL(checkID, output, status string) error {
+ switch status {
+ case "pass", HealthPassing:
+ status = HealthPassing
+ case "warn", HealthWarning:
+ status = HealthWarning
+ case "fail", HealthCritical:
+ status = HealthCritical
+ default:
+ return fmt.Errorf("Invalid status: %s", status)
+ }
+
+ endpoint := fmt.Sprintf("/v1/agent/check/update/%s", checkID)
+ r := a.c.newRequest("PUT", endpoint)
+ r.obj = &checkUpdate{
+ Status: status,
+ Output: output,
+ }
+
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// CheckRegister is used to register a new check with
+// the local agent
+func (a *Agent) CheckRegister(check *AgentCheckRegistration) error {
+ r := a.c.newRequest("PUT", "/v1/agent/check/register")
+ r.obj = check
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// CheckDeregister is used to deregister a check with
+// the local agent
+func (a *Agent) CheckDeregister(checkID string) error {
+ r := a.c.newRequest("PUT", "/v1/agent/check/deregister/"+checkID)
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// Join is used to instruct the agent to attempt a join to
+// another cluster member
+func (a *Agent) Join(addr string, wan bool) error {
+ r := a.c.newRequest("PUT", "/v1/agent/join/"+addr)
+ if wan {
+ r.params.Set("wan", "1")
+ }
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// Leave is used to have the agent gracefully leave the cluster and shutdown
+func (a *Agent) Leave() error {
+ r := a.c.newRequest("PUT", "/v1/agent/leave")
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// ForceLeave is used to have the agent eject a failed node
+func (a *Agent) ForceLeave(node string) error {
+ r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// ConnectAuthorize is used to authorize an incoming connection
+// to a natively integrated Connect service.
+func (a *Agent) ConnectAuthorize(auth *AgentAuthorizeParams) (*AgentAuthorize, error) {
+ r := a.c.newRequest("POST", "/v1/agent/connect/authorize")
+ r.obj = auth
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var out AgentAuthorize
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, err
+ }
+ return &out, nil
+}
+
+// ConnectCARoots returns the list of roots.
+func (a *Agent) ConnectCARoots(q *QueryOptions) (*CARootList, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/agent/connect/ca/roots")
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.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
+}
+
+// ConnectCALeaf gets the leaf certificate for the given service ID.
+func (a *Agent) ConnectCALeaf(serviceID string, q *QueryOptions) (*LeafCert, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/agent/connect/ca/leaf/"+serviceID)
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var out LeafCert
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+ return &out, qm, nil
+}
+
+// ConnectProxyConfig gets the configuration for a local managed proxy instance.
+//
+// Note that this uses an unconventional blocking mechanism since it's
+// agent-local state. That means there is no persistent raft index so we block
+// based on object hash instead.
+func (a *Agent) ConnectProxyConfig(proxyServiceID string, q *QueryOptions) (*ConnectProxyConfig, *QueryMeta, error) {
+ r := a.c.newRequest("GET", "/v1/agent/connect/proxy/"+proxyServiceID)
+ r.setQueryOptions(q)
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ var out ConnectProxyConfig
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, nil, err
+ }
+ return &out, qm, nil
+}
+
+// EnableServiceMaintenance toggles service maintenance mode on
+// for the given service ID.
+func (a *Agent) EnableServiceMaintenance(serviceID, reason string) error {
+ r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
+ r.params.Set("enable", "true")
+ r.params.Set("reason", reason)
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// DisableServiceMaintenance toggles service maintenance mode off
+// for the given service ID.
+func (a *Agent) DisableServiceMaintenance(serviceID string) error {
+ r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
+ r.params.Set("enable", "false")
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// EnableNodeMaintenance toggles node maintenance mode on for the
+// agent we are connected to.
+func (a *Agent) EnableNodeMaintenance(reason string) error {
+ r := a.c.newRequest("PUT", "/v1/agent/maintenance")
+ r.params.Set("enable", "true")
+ r.params.Set("reason", reason)
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// DisableNodeMaintenance toggles node maintenance mode off for the
+// agent we are connected to.
+func (a *Agent) DisableNodeMaintenance() error {
+ r := a.c.newRequest("PUT", "/v1/agent/maintenance")
+ r.params.Set("enable", "false")
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// Monitor returns a channel which will receive streaming logs from the agent
+// Providing a non-nil stopCh can be used to close the connection and stop the
+// log stream. An empty string will be sent down the given channel when there's
+// nothing left to stream, after which the caller should close the stopCh.
+func (a *Agent) Monitor(loglevel string, stopCh <-chan struct{}, q *QueryOptions) (chan string, error) {
+ r := a.c.newRequest("GET", "/v1/agent/monitor")
+ r.setQueryOptions(q)
+ if loglevel != "" {
+ r.params.Add("loglevel", loglevel)
+ }
+ _, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+
+ logCh := make(chan string, 64)
+ go func() {
+ defer resp.Body.Close()
+
+ scanner := bufio.NewScanner(resp.Body)
+ for {
+ select {
+ case <-stopCh:
+ close(logCh)
+ return
+ default:
+ }
+ if scanner.Scan() {
+ // An empty string signals to the caller that
+ // the scan is done, so make sure we only emit
+ // that when the scanner says it's done, not if
+ // we happen to ingest an empty line.
+ if text := scanner.Text(); text != "" {
+ logCh <- text
+ } else {
+ logCh <- " "
+ }
+ } else {
+ logCh <- ""
+ }
+ }
+ }()
+
+ return logCh, nil
+}
+
+// UpdateACLToken updates the agent's "acl_token". See updateToken for more
+// details.
+//
+// DEPRECATED (ACL-Legacy-Compat) - Prefer UpdateDefaultACLToken for v1.4.3 and above
+func (a *Agent) UpdateACLToken(token string, q *WriteOptions) (*WriteMeta, error) {
+ return a.updateToken("acl_token", token, q)
+}
+
+// UpdateACLAgentToken updates the agent's "acl_agent_token". See updateToken
+// for more details.
+//
+// DEPRECATED (ACL-Legacy-Compat) - Prefer UpdateAgentACLToken for v1.4.3 and above
+func (a *Agent) UpdateACLAgentToken(token string, q *WriteOptions) (*WriteMeta, error) {
+ return a.updateToken("acl_agent_token", token, q)
+}
+
+// UpdateACLAgentMasterToken updates the agent's "acl_agent_master_token". See
+// updateToken for more details.
+//
+// DEPRECATED (ACL-Legacy-Compat) - Prefer UpdateAgentMasterACLToken for v1.4.3 and above
+func (a *Agent) UpdateACLAgentMasterToken(token string, q *WriteOptions) (*WriteMeta, error) {
+ return a.updateToken("acl_agent_master_token", token, q)
+}
+
+// UpdateACLReplicationToken updates the agent's "acl_replication_token". See
+// updateToken for more details.
+//
+// DEPRECATED (ACL-Legacy-Compat) - Prefer UpdateReplicationACLToken for v1.4.3 and above
+func (a *Agent) UpdateACLReplicationToken(token string, q *WriteOptions) (*WriteMeta, error) {
+ return a.updateToken("acl_replication_token", token, q)
+}
+
+// UpdateDefaultACLToken updates the agent's "default" token. See updateToken
+// for more details
+func (a *Agent) UpdateDefaultACLToken(token string, q *WriteOptions) (*WriteMeta, error) {
+ return a.updateTokenFallback("default", "acl_token", token, q)
+}
+
+// UpdateAgentACLToken updates the agent's "agent" token. See updateToken
+// for more details
+func (a *Agent) UpdateAgentACLToken(token string, q *WriteOptions) (*WriteMeta, error) {
+ return a.updateTokenFallback("agent", "acl_agent_token", token, q)
+}
+
+// UpdateAgentMasterACLToken updates the agent's "agent_master" token. See updateToken
+// for more details
+func (a *Agent) UpdateAgentMasterACLToken(token string, q *WriteOptions) (*WriteMeta, error) {
+ return a.updateTokenFallback("agent_master", "acl_agent_master_token", token, q)
+}
+
+// UpdateReplicationACLToken updates the agent's "replication" token. See updateToken
+// for more details
+func (a *Agent) UpdateReplicationACLToken(token string, q *WriteOptions) (*WriteMeta, error) {
+ return a.updateTokenFallback("replication", "acl_replication_token", token, q)
+}
+
+// updateToken can be used to update one of an agent's ACL tokens after the agent has
+// started. The tokens are may not be persisted, so will need to be updated again if
+// the agent is restarted unless the agent is configured to persist them.
+func (a *Agent) updateToken(target, token string, q *WriteOptions) (*WriteMeta, error) {
+ meta, _, err := a.updateTokenOnce(target, token, q)
+ return meta, err
+}
+
+func (a *Agent) updateTokenFallback(target, fallback, token string, q *WriteOptions) (*WriteMeta, error) {
+ meta, status, err := a.updateTokenOnce(target, token, q)
+ if err != nil && status == 404 {
+ meta, _, err = a.updateTokenOnce(fallback, token, q)
+ }
+ return meta, err
+}
+
+func (a *Agent) updateTokenOnce(target, token string, q *WriteOptions) (*WriteMeta, int, error) {
+ r := a.c.newRequest("PUT", fmt.Sprintf("/v1/agent/token/%s", target))
+ r.setWriteOptions(q)
+ r.obj = &AgentToken{Token: token}
+ rtt, resp, err := requireOK(a.c.doRequest(r))
+ if err != nil {
+ return nil, resp.StatusCode, err
+ }
+ resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ return wm, resp.StatusCode, nil
+}
diff --git a/vendor/github.com/hashicorp/consul/api/api.go b/vendor/github.com/hashicorp/consul/api/api.go
new file mode 100644
index 0000000..39a0ad3
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/api.go
@@ -0,0 +1,899 @@
+package api
+
+import (
+ "bytes"
+ "context"
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/hashicorp/go-cleanhttp"
+ "github.com/hashicorp/go-rootcerts"
+)
+
+const (
+ // HTTPAddrEnvName defines an environment variable name which sets
+ // the HTTP address if there is no -http-addr specified.
+ HTTPAddrEnvName = "CONSUL_HTTP_ADDR"
+
+ // HTTPTokenEnvName defines an environment variable name which sets
+ // the HTTP token.
+ HTTPTokenEnvName = "CONSUL_HTTP_TOKEN"
+
+ // HTTPAuthEnvName defines an environment variable name which sets
+ // the HTTP authentication header.
+ HTTPAuthEnvName = "CONSUL_HTTP_AUTH"
+
+ // HTTPSSLEnvName defines an environment variable name which sets
+ // whether or not to use HTTPS.
+ HTTPSSLEnvName = "CONSUL_HTTP_SSL"
+
+ // HTTPCAFile defines an environment variable name which sets the
+ // CA file to use for talking to Consul over TLS.
+ HTTPCAFile = "CONSUL_CACERT"
+
+ // HTTPCAPath defines an environment variable name which sets the
+ // path to a directory of CA certs to use for talking to Consul over TLS.
+ HTTPCAPath = "CONSUL_CAPATH"
+
+ // HTTPClientCert defines an environment variable name which sets the
+ // client cert file to use for talking to Consul over TLS.
+ HTTPClientCert = "CONSUL_CLIENT_CERT"
+
+ // HTTPClientKey defines an environment variable name which sets the
+ // client key file to use for talking to Consul over TLS.
+ HTTPClientKey = "CONSUL_CLIENT_KEY"
+
+ // HTTPTLSServerName defines an environment variable name which sets the
+ // server name to use as the SNI host when connecting via TLS
+ HTTPTLSServerName = "CONSUL_TLS_SERVER_NAME"
+
+ // HTTPSSLVerifyEnvName defines an environment variable name which sets
+ // whether or not to disable certificate checking.
+ HTTPSSLVerifyEnvName = "CONSUL_HTTP_SSL_VERIFY"
+
+ // GRPCAddrEnvName defines an environment variable name which sets the gRPC
+ // address for consul connect envoy. Note this isn't actually used by the api
+ // client in this package but is defined here for consistency with all the
+ // other ENV names we use.
+ GRPCAddrEnvName = "CONSUL_GRPC_ADDR"
+)
+
+// QueryOptions are used to parameterize a query
+type QueryOptions struct {
+ // Providing a datacenter overwrites the DC provided
+ // by the Config
+ Datacenter string
+
+ // AllowStale allows any Consul server (non-leader) to service
+ // a read. This allows for lower latency and higher throughput
+ AllowStale bool
+
+ // RequireConsistent forces the read to be fully consistent.
+ // This is more expensive but prevents ever performing a stale
+ // read.
+ RequireConsistent bool
+
+ // UseCache requests that the agent cache results locally. See
+ // https://www.consul.io/api/index.html#agent-caching for more details on the
+ // semantics.
+ UseCache bool
+
+ // MaxAge limits how old a cached value will be returned if UseCache is true.
+ // If there is a cached response that is older than the MaxAge, it is treated
+ // as a cache miss and a new fetch invoked. If the fetch fails, the error is
+ // returned. Clients that wish to allow for stale results on error can set
+ // StaleIfError to a longer duration to change this behavior. It is ignored
+ // if the endpoint supports background refresh caching. See
+ // https://www.consul.io/api/index.html#agent-caching for more details.
+ MaxAge time.Duration
+
+ // StaleIfError specifies how stale the client will accept a cached response
+ // if the servers are unavailable to fetch a fresh one. Only makes sense when
+ // UseCache is true and MaxAge is set to a lower, non-zero value. It is
+ // ignored if the endpoint supports background refresh caching. See
+ // https://www.consul.io/api/index.html#agent-caching for more details.
+ StaleIfError time.Duration
+
+ // WaitIndex is used to enable a blocking query. Waits
+ // until the timeout or the next index is reached
+ WaitIndex uint64
+
+ // WaitHash is used by some endpoints instead of WaitIndex to perform blocking
+ // on state based on a hash of the response rather than a monotonic index.
+ // This is required when the state being blocked on is not stored in Raft, for
+ // example agent-local proxy configuration.
+ WaitHash string
+
+ // WaitTime is used to bound the duration of a wait.
+ // Defaults to that of the Config, but can be overridden.
+ WaitTime time.Duration
+
+ // Token is used to provide a per-request ACL token
+ // which overrides the agent's default token.
+ Token string
+
+ // Near is used to provide a node name that will sort the results
+ // in ascending order based on the estimated round trip time from
+ // that node. Setting this to "_agent" will use the agent's node
+ // for the sort.
+ Near string
+
+ // NodeMeta is used to filter results by nodes with the given
+ // metadata key/value pairs. Currently, only one key/value pair can
+ // be provided for filtering.
+ NodeMeta map[string]string
+
+ // RelayFactor is used in keyring operations to cause responses to be
+ // relayed back to the sender through N other random nodes. Must be
+ // a value from 0 to 5 (inclusive).
+ RelayFactor uint8
+
+ // Connect filters prepared query execution to only include Connect-capable
+ // services. This currently affects prepared query execution.
+ Connect bool
+
+ // ctx is an optional context pass through to the underlying HTTP
+ // request layer. Use Context() and WithContext() to manage this.
+ ctx context.Context
+}
+
+func (o *QueryOptions) Context() context.Context {
+ if o != nil && o.ctx != nil {
+ return o.ctx
+ }
+ return context.Background()
+}
+
+func (o *QueryOptions) WithContext(ctx context.Context) *QueryOptions {
+ o2 := new(QueryOptions)
+ if o != nil {
+ *o2 = *o
+ }
+ o2.ctx = ctx
+ return o2
+}
+
+// WriteOptions are used to parameterize a write
+type WriteOptions struct {
+ // Providing a datacenter overwrites the DC provided
+ // by the Config
+ Datacenter string
+
+ // Token is used to provide a per-request ACL token
+ // which overrides the agent's default token.
+ Token string
+
+ // RelayFactor is used in keyring operations to cause responses to be
+ // relayed back to the sender through N other random nodes. Must be
+ // a value from 0 to 5 (inclusive).
+ RelayFactor uint8
+
+ // ctx is an optional context pass through to the underlying HTTP
+ // request layer. Use Context() and WithContext() to manage this.
+ ctx context.Context
+}
+
+func (o *WriteOptions) Context() context.Context {
+ if o != nil && o.ctx != nil {
+ return o.ctx
+ }
+ return context.Background()
+}
+
+func (o *WriteOptions) WithContext(ctx context.Context) *WriteOptions {
+ o2 := new(WriteOptions)
+ if o != nil {
+ *o2 = *o
+ }
+ o2.ctx = ctx
+ return o2
+}
+
+// QueryMeta is used to return meta data about a query
+type QueryMeta struct {
+ // LastIndex. This can be used as a WaitIndex to perform
+ // a blocking query
+ LastIndex uint64
+
+ // LastContentHash. This can be used as a WaitHash to perform a blocking query
+ // for endpoints that support hash-based blocking. Endpoints that do not
+ // support it will return an empty hash.
+ LastContentHash string
+
+ // Time of last contact from the leader for the
+ // server servicing the request
+ LastContact time.Duration
+
+ // Is there a known leader
+ KnownLeader bool
+
+ // How long did the request take
+ RequestTime time.Duration
+
+ // Is address translation enabled for HTTP responses on this agent
+ AddressTranslationEnabled bool
+
+ // CacheHit is true if the result was served from agent-local cache.
+ CacheHit bool
+
+ // CacheAge is set if request was ?cached and indicates how stale the cached
+ // response is.
+ CacheAge time.Duration
+}
+
+// WriteMeta is used to return meta data about a write
+type WriteMeta struct {
+ // How long did the request take
+ RequestTime time.Duration
+}
+
+// HttpBasicAuth is used to authenticate http client with HTTP Basic Authentication
+type HttpBasicAuth struct {
+ // Username to use for HTTP Basic Authentication
+ Username string
+
+ // Password to use for HTTP Basic Authentication
+ Password string
+}
+
+// Config is used to configure the creation of a client
+type Config struct {
+ // Address is the address of the Consul server
+ Address string
+
+ // Scheme is the URI scheme for the Consul server
+ Scheme string
+
+ // Datacenter to use. If not provided, the default agent datacenter is used.
+ Datacenter string
+
+ // Transport is the Transport to use for the http client.
+ Transport *http.Transport
+
+ // HttpClient is the client to use. Default will be
+ // used if not provided.
+ HttpClient *http.Client
+
+ // HttpAuth is the auth info to use for http access.
+ HttpAuth *HttpBasicAuth
+
+ // WaitTime limits how long a Watch will block. If not provided,
+ // the agent default values will be used.
+ WaitTime time.Duration
+
+ // Token is used to provide a per-request ACL token
+ // which overrides the agent's default token.
+ Token string
+
+ TLSConfig TLSConfig
+}
+
+// TLSConfig is used to generate a TLSClientConfig that's useful for talking to
+// Consul using TLS.
+type TLSConfig struct {
+ // Address is the optional address of the Consul server. The port, if any
+ // will be removed from here and this will be set to the ServerName of the
+ // resulting config.
+ Address string
+
+ // CAFile is the optional path to the CA certificate used for Consul
+ // communication, defaults to the system bundle if not specified.
+ CAFile string
+
+ // CAPath is the optional path to a directory of CA certificates to use for
+ // Consul communication, defaults to the system bundle if not specified.
+ CAPath string
+
+ // CertFile is the optional path to the certificate for Consul
+ // communication. If this is set then you need to also set KeyFile.
+ CertFile string
+
+ // KeyFile is the optional path to the private key for Consul communication.
+ // If this is set then you need to also set CertFile.
+ KeyFile string
+
+ // InsecureSkipVerify if set to true will disable TLS host verification.
+ InsecureSkipVerify bool
+}
+
+// DefaultConfig returns a default configuration for the client. By default this
+// will pool and reuse idle connections to Consul. If you have a long-lived
+// client object, this is the desired behavior and should make the most efficient
+// use of the connections to Consul. If you don't reuse a client object, which
+// is not recommended, then you may notice idle connections building up over
+// time. To avoid this, use the DefaultNonPooledConfig() instead.
+func DefaultConfig() *Config {
+ return defaultConfig(cleanhttp.DefaultPooledTransport)
+}
+
+// DefaultNonPooledConfig returns a default configuration for the client which
+// does not pool connections. This isn't a recommended configuration because it
+// will reconnect to Consul on every request, but this is useful to avoid the
+// accumulation of idle connections if you make many client objects during the
+// lifetime of your application.
+func DefaultNonPooledConfig() *Config {
+ return defaultConfig(cleanhttp.DefaultTransport)
+}
+
+// defaultConfig returns the default configuration for the client, using the
+// given function to make the transport.
+func defaultConfig(transportFn func() *http.Transport) *Config {
+ config := &Config{
+ Address: "127.0.0.1:8500",
+ Scheme: "http",
+ Transport: transportFn(),
+ }
+
+ if addr := os.Getenv(HTTPAddrEnvName); addr != "" {
+ config.Address = addr
+ }
+
+ if token := os.Getenv(HTTPTokenEnvName); token != "" {
+ config.Token = token
+ }
+
+ if auth := os.Getenv(HTTPAuthEnvName); auth != "" {
+ var username, password string
+ if strings.Contains(auth, ":") {
+ split := strings.SplitN(auth, ":", 2)
+ username = split[0]
+ password = split[1]
+ } else {
+ username = auth
+ }
+
+ config.HttpAuth = &HttpBasicAuth{
+ Username: username,
+ Password: password,
+ }
+ }
+
+ if ssl := os.Getenv(HTTPSSLEnvName); ssl != "" {
+ enabled, err := strconv.ParseBool(ssl)
+ if err != nil {
+ log.Printf("[WARN] client: could not parse %s: %s", HTTPSSLEnvName, err)
+ }
+
+ if enabled {
+ config.Scheme = "https"
+ }
+ }
+
+ if v := os.Getenv(HTTPTLSServerName); v != "" {
+ config.TLSConfig.Address = v
+ }
+ if v := os.Getenv(HTTPCAFile); v != "" {
+ config.TLSConfig.CAFile = v
+ }
+ if v := os.Getenv(HTTPCAPath); v != "" {
+ config.TLSConfig.CAPath = v
+ }
+ if v := os.Getenv(HTTPClientCert); v != "" {
+ config.TLSConfig.CertFile = v
+ }
+ if v := os.Getenv(HTTPClientKey); v != "" {
+ config.TLSConfig.KeyFile = v
+ }
+ if v := os.Getenv(HTTPSSLVerifyEnvName); v != "" {
+ doVerify, err := strconv.ParseBool(v)
+ if err != nil {
+ log.Printf("[WARN] client: could not parse %s: %s", HTTPSSLVerifyEnvName, err)
+ }
+ if !doVerify {
+ config.TLSConfig.InsecureSkipVerify = true
+ }
+ }
+
+ return config
+}
+
+// TLSConfig is used to generate a TLSClientConfig that's useful for talking to
+// Consul using TLS.
+func SetupTLSConfig(tlsConfig *TLSConfig) (*tls.Config, error) {
+ tlsClientConfig := &tls.Config{
+ InsecureSkipVerify: tlsConfig.InsecureSkipVerify,
+ }
+
+ if tlsConfig.Address != "" {
+ server := tlsConfig.Address
+ hasPort := strings.LastIndex(server, ":") > strings.LastIndex(server, "]")
+ if hasPort {
+ var err error
+ server, _, err = net.SplitHostPort(server)
+ if err != nil {
+ return nil, err
+ }
+ }
+ tlsClientConfig.ServerName = server
+ }
+
+ if tlsConfig.CertFile != "" && tlsConfig.KeyFile != "" {
+ tlsCert, err := tls.LoadX509KeyPair(tlsConfig.CertFile, tlsConfig.KeyFile)
+ if err != nil {
+ return nil, err
+ }
+ tlsClientConfig.Certificates = []tls.Certificate{tlsCert}
+ }
+
+ if tlsConfig.CAFile != "" || tlsConfig.CAPath != "" {
+ rootConfig := &rootcerts.Config{
+ CAFile: tlsConfig.CAFile,
+ CAPath: tlsConfig.CAPath,
+ }
+ if err := rootcerts.ConfigureTLS(tlsClientConfig, rootConfig); err != nil {
+ return nil, err
+ }
+ }
+
+ return tlsClientConfig, nil
+}
+
+func (c *Config) GenerateEnv() []string {
+ env := make([]string, 0, 10)
+
+ env = append(env,
+ fmt.Sprintf("%s=%s", HTTPAddrEnvName, c.Address),
+ fmt.Sprintf("%s=%s", HTTPTokenEnvName, c.Token),
+ fmt.Sprintf("%s=%t", HTTPSSLEnvName, c.Scheme == "https"),
+ fmt.Sprintf("%s=%s", HTTPCAFile, c.TLSConfig.CAFile),
+ fmt.Sprintf("%s=%s", HTTPCAPath, c.TLSConfig.CAPath),
+ fmt.Sprintf("%s=%s", HTTPClientCert, c.TLSConfig.CertFile),
+ fmt.Sprintf("%s=%s", HTTPClientKey, c.TLSConfig.KeyFile),
+ fmt.Sprintf("%s=%s", HTTPTLSServerName, c.TLSConfig.Address),
+ fmt.Sprintf("%s=%t", HTTPSSLVerifyEnvName, !c.TLSConfig.InsecureSkipVerify))
+
+ if c.HttpAuth != nil {
+ env = append(env, fmt.Sprintf("%s=%s:%s", HTTPAuthEnvName, c.HttpAuth.Username, c.HttpAuth.Password))
+ } else {
+ env = append(env, fmt.Sprintf("%s=", HTTPAuthEnvName))
+ }
+
+ return env
+}
+
+// Client provides a client to the Consul API
+type Client struct {
+ config Config
+}
+
+// NewClient returns a new client
+func NewClient(config *Config) (*Client, error) {
+ // bootstrap the config
+ defConfig := DefaultConfig()
+
+ if len(config.Address) == 0 {
+ config.Address = defConfig.Address
+ }
+
+ if len(config.Scheme) == 0 {
+ config.Scheme = defConfig.Scheme
+ }
+
+ if config.Transport == nil {
+ config.Transport = defConfig.Transport
+ }
+
+ if config.TLSConfig.Address == "" {
+ config.TLSConfig.Address = defConfig.TLSConfig.Address
+ }
+
+ if config.TLSConfig.CAFile == "" {
+ config.TLSConfig.CAFile = defConfig.TLSConfig.CAFile
+ }
+
+ if config.TLSConfig.CAPath == "" {
+ config.TLSConfig.CAPath = defConfig.TLSConfig.CAPath
+ }
+
+ if config.TLSConfig.CertFile == "" {
+ config.TLSConfig.CertFile = defConfig.TLSConfig.CertFile
+ }
+
+ if config.TLSConfig.KeyFile == "" {
+ config.TLSConfig.KeyFile = defConfig.TLSConfig.KeyFile
+ }
+
+ if !config.TLSConfig.InsecureSkipVerify {
+ config.TLSConfig.InsecureSkipVerify = defConfig.TLSConfig.InsecureSkipVerify
+ }
+
+ if config.HttpClient == nil {
+ var err error
+ config.HttpClient, err = NewHttpClient(config.Transport, config.TLSConfig)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ parts := strings.SplitN(config.Address, "://", 2)
+ if len(parts) == 2 {
+ switch parts[0] {
+ case "http":
+ config.Scheme = "http"
+ case "https":
+ config.Scheme = "https"
+ case "unix":
+ trans := cleanhttp.DefaultTransport()
+ trans.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
+ return net.Dial("unix", parts[1])
+ }
+ config.HttpClient = &http.Client{
+ Transport: trans,
+ }
+ default:
+ return nil, fmt.Errorf("Unknown protocol scheme: %s", parts[0])
+ }
+ config.Address = parts[1]
+ }
+
+ if config.Token == "" {
+ config.Token = defConfig.Token
+ }
+
+ return &Client{config: *config}, nil
+}
+
+// NewHttpClient returns an http client configured with the given Transport and TLS
+// config.
+func NewHttpClient(transport *http.Transport, tlsConf TLSConfig) (*http.Client, error) {
+ client := &http.Client{
+ Transport: transport,
+ }
+
+ // TODO (slackpad) - Once we get some run time on the HTTP/2 support we
+ // should turn it on by default if TLS is enabled. We would basically
+ // just need to call http2.ConfigureTransport(transport) here. We also
+ // don't want to introduce another external dependency on
+ // golang.org/x/net/http2 at this time. For a complete recipe for how
+ // to enable HTTP/2 support on a transport suitable for the API client
+ // library see agent/http_test.go:TestHTTPServer_H2.
+
+ if transport.TLSClientConfig == nil {
+ tlsClientConfig, err := SetupTLSConfig(&tlsConf)
+
+ if err != nil {
+ return nil, err
+ }
+
+ transport.TLSClientConfig = tlsClientConfig
+ }
+
+ return client, nil
+}
+
+// request is used to help build up a request
+type request struct {
+ config *Config
+ method string
+ url *url.URL
+ params url.Values
+ body io.Reader
+ header http.Header
+ obj interface{}
+ ctx context.Context
+}
+
+// setQueryOptions is used to annotate the request with
+// additional query options
+func (r *request) setQueryOptions(q *QueryOptions) {
+ if q == nil {
+ return
+ }
+ if q.Datacenter != "" {
+ r.params.Set("dc", q.Datacenter)
+ }
+ if q.AllowStale {
+ r.params.Set("stale", "")
+ }
+ if q.RequireConsistent {
+ r.params.Set("consistent", "")
+ }
+ if q.WaitIndex != 0 {
+ r.params.Set("index", strconv.FormatUint(q.WaitIndex, 10))
+ }
+ if q.WaitTime != 0 {
+ r.params.Set("wait", durToMsec(q.WaitTime))
+ }
+ if q.WaitHash != "" {
+ r.params.Set("hash", q.WaitHash)
+ }
+ if q.Token != "" {
+ r.header.Set("X-Consul-Token", q.Token)
+ }
+ if q.Near != "" {
+ r.params.Set("near", q.Near)
+ }
+ if len(q.NodeMeta) > 0 {
+ for key, value := range q.NodeMeta {
+ r.params.Add("node-meta", key+":"+value)
+ }
+ }
+ if q.RelayFactor != 0 {
+ r.params.Set("relay-factor", strconv.Itoa(int(q.RelayFactor)))
+ }
+ if q.Connect {
+ r.params.Set("connect", "true")
+ }
+ if q.UseCache && !q.RequireConsistent {
+ r.params.Set("cached", "")
+
+ cc := []string{}
+ if q.MaxAge > 0 {
+ cc = append(cc, fmt.Sprintf("max-age=%.0f", q.MaxAge.Seconds()))
+ }
+ if q.StaleIfError > 0 {
+ cc = append(cc, fmt.Sprintf("stale-if-error=%.0f", q.StaleIfError.Seconds()))
+ }
+ if len(cc) > 0 {
+ r.header.Set("Cache-Control", strings.Join(cc, ", "))
+ }
+ }
+ r.ctx = q.ctx
+}
+
+// durToMsec converts a duration to a millisecond specified string. If the
+// user selected a positive value that rounds to 0 ms, then we will use 1 ms
+// so they get a short delay, otherwise Consul will translate the 0 ms into
+// a huge default delay.
+func durToMsec(dur time.Duration) string {
+ ms := dur / time.Millisecond
+ if dur > 0 && ms == 0 {
+ ms = 1
+ }
+ return fmt.Sprintf("%dms", ms)
+}
+
+// serverError is a string we look for to detect 500 errors.
+const serverError = "Unexpected response code: 500"
+
+// IsRetryableError returns true for 500 errors from the Consul servers, and
+// network connection errors. These are usually retryable at a later time.
+// This applies to reads but NOT to writes. This may return true for errors
+// on writes that may have still gone through, so do not use this to retry
+// any write operations.
+func IsRetryableError(err error) bool {
+ if err == nil {
+ return false
+ }
+
+ if _, ok := err.(net.Error); ok {
+ return true
+ }
+
+ // TODO (slackpad) - Make a real error type here instead of using
+ // a string check.
+ return strings.Contains(err.Error(), serverError)
+}
+
+// setWriteOptions is used to annotate the request with
+// additional write options
+func (r *request) setWriteOptions(q *WriteOptions) {
+ if q == nil {
+ return
+ }
+ if q.Datacenter != "" {
+ r.params.Set("dc", q.Datacenter)
+ }
+ if q.Token != "" {
+ r.header.Set("X-Consul-Token", q.Token)
+ }
+ if q.RelayFactor != 0 {
+ r.params.Set("relay-factor", strconv.Itoa(int(q.RelayFactor)))
+ }
+ r.ctx = q.ctx
+}
+
+// toHTTP converts the request to an HTTP request
+func (r *request) toHTTP() (*http.Request, error) {
+ // Encode the query parameters
+ r.url.RawQuery = r.params.Encode()
+
+ // Check if we should encode the body
+ if r.body == nil && r.obj != nil {
+ b, err := encodeBody(r.obj)
+ if err != nil {
+ return nil, err
+ }
+ r.body = b
+ }
+
+ // Create the HTTP request
+ req, err := http.NewRequest(r.method, r.url.RequestURI(), r.body)
+ if err != nil {
+ return nil, err
+ }
+
+ req.URL.Host = r.url.Host
+ req.URL.Scheme = r.url.Scheme
+ req.Host = r.url.Host
+ req.Header = r.header
+
+ // Setup auth
+ if r.config.HttpAuth != nil {
+ req.SetBasicAuth(r.config.HttpAuth.Username, r.config.HttpAuth.Password)
+ }
+ if r.ctx != nil {
+ return req.WithContext(r.ctx), nil
+ }
+
+ return req, nil
+}
+
+// newRequest is used to create a new request
+func (c *Client) newRequest(method, path string) *request {
+ r := &request{
+ config: &c.config,
+ method: method,
+ url: &url.URL{
+ Scheme: c.config.Scheme,
+ Host: c.config.Address,
+ Path: path,
+ },
+ params: make(map[string][]string),
+ header: make(http.Header),
+ }
+ if c.config.Datacenter != "" {
+ r.params.Set("dc", c.config.Datacenter)
+ }
+ if c.config.WaitTime != 0 {
+ r.params.Set("wait", durToMsec(r.config.WaitTime))
+ }
+ if c.config.Token != "" {
+ r.header.Set("X-Consul-Token", r.config.Token)
+ }
+ return r
+}
+
+// doRequest runs a request with our client
+func (c *Client) doRequest(r *request) (time.Duration, *http.Response, error) {
+ req, err := r.toHTTP()
+ if err != nil {
+ return 0, nil, err
+ }
+ start := time.Now()
+ resp, err := c.config.HttpClient.Do(req)
+ diff := time.Since(start)
+ return diff, resp, err
+}
+
+// Query is used to do a GET request against an endpoint
+// and deserialize the response into an interface using
+// standard Consul conventions.
+func (c *Client) query(endpoint string, out interface{}, q *QueryOptions) (*QueryMeta, error) {
+ r := c.newRequest("GET", endpoint)
+ r.setQueryOptions(q)
+ rtt, resp, err := c.doRequest(r)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ if err := decodeBody(resp, out); err != nil {
+ return nil, err
+ }
+ return qm, nil
+}
+
+// write is used to do a PUT request against an endpoint
+// and serialize/deserialized using the standard Consul conventions.
+func (c *Client) write(endpoint string, in, out interface{}, q *WriteOptions) (*WriteMeta, error) {
+ r := c.newRequest("PUT", endpoint)
+ r.setWriteOptions(q)
+ r.obj = in
+ rtt, resp, err := requireOK(c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+ if out != nil {
+ if err := decodeBody(resp, &out); err != nil {
+ return nil, err
+ }
+ } else if _, err := ioutil.ReadAll(resp.Body); err != nil {
+ return nil, err
+ }
+ return wm, nil
+}
+
+// parseQueryMeta is used to help parse query meta-data
+func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
+ header := resp.Header
+
+ // Parse the X-Consul-Index (if it's set - hash based blocking queries don't
+ // set this)
+ if indexStr := header.Get("X-Consul-Index"); indexStr != "" {
+ index, err := strconv.ParseUint(indexStr, 10, 64)
+ if err != nil {
+ return fmt.Errorf("Failed to parse X-Consul-Index: %v", err)
+ }
+ q.LastIndex = index
+ }
+ q.LastContentHash = header.Get("X-Consul-ContentHash")
+
+ // Parse the X-Consul-LastContact
+ last, err := strconv.ParseUint(header.Get("X-Consul-LastContact"), 10, 64)
+ if err != nil {
+ return fmt.Errorf("Failed to parse X-Consul-LastContact: %v", err)
+ }
+ q.LastContact = time.Duration(last) * time.Millisecond
+
+ // Parse the X-Consul-KnownLeader
+ switch header.Get("X-Consul-KnownLeader") {
+ case "true":
+ q.KnownLeader = true
+ default:
+ q.KnownLeader = false
+ }
+
+ // Parse X-Consul-Translate-Addresses
+ switch header.Get("X-Consul-Translate-Addresses") {
+ case "true":
+ q.AddressTranslationEnabled = true
+ default:
+ q.AddressTranslationEnabled = false
+ }
+
+ // Parse Cache info
+ if cacheStr := header.Get("X-Cache"); cacheStr != "" {
+ q.CacheHit = strings.EqualFold(cacheStr, "HIT")
+ }
+ if ageStr := header.Get("Age"); ageStr != "" {
+ age, err := strconv.ParseUint(ageStr, 10, 64)
+ if err != nil {
+ return fmt.Errorf("Failed to parse Age Header: %v", err)
+ }
+ q.CacheAge = time.Duration(age) * time.Second
+ }
+
+ return nil
+}
+
+// decodeBody is used to JSON decode a body
+func decodeBody(resp *http.Response, out interface{}) error {
+ dec := json.NewDecoder(resp.Body)
+ return dec.Decode(out)
+}
+
+// encodeBody is used to encode a request body
+func encodeBody(obj interface{}) (io.Reader, error) {
+ buf := bytes.NewBuffer(nil)
+ enc := json.NewEncoder(buf)
+ if err := enc.Encode(obj); err != nil {
+ return nil, err
+ }
+ return buf, nil
+}
+
+// requireOK is used to wrap doRequest and check for a 200
+func requireOK(d time.Duration, resp *http.Response, e error) (time.Duration, *http.Response, error) {
+ if e != nil {
+ if resp != nil {
+ resp.Body.Close()
+ }
+ return d, nil, e
+ }
+ if resp.StatusCode != 200 {
+ var buf bytes.Buffer
+ io.Copy(&buf, resp.Body)
+ resp.Body.Close()
+ return d, nil, fmt.Errorf("Unexpected response code: %d (%s)", resp.StatusCode, buf.Bytes())
+ }
+ return d, resp, nil
+}
diff --git a/vendor/github.com/hashicorp/consul/api/catalog.go b/vendor/github.com/hashicorp/consul/api/catalog.go
new file mode 100644
index 0000000..c175c3f
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/catalog.go
@@ -0,0 +1,244 @@
+package api
+
+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 CatalogService struct {
+ ID string
+ Node string
+ Address string
+ Datacenter string
+ TaggedAddresses map[string]string
+ NodeMeta map[string]string
+ ServiceID string
+ ServiceName string
+ ServiceAddress string
+ ServiceTags []string
+ ServiceMeta map[string]string
+ ServicePort int
+ ServiceWeights Weights
+ ServiceEnableTagOverride bool
+ // DEPRECATED (ProxyDestination) - remove the next comment!
+ // We forgot to ever add ServiceProxyDestination here so no need to deprecate!
+ ServiceProxy *AgentServiceConnectProxyConfig
+ CreateIndex uint64
+ Checks HealthChecks
+ ModifyIndex uint64
+}
+
+type CatalogNode struct {
+ Node *Node
+ Services map[string]*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 // Obsolete.
+ Datacenter string
+ ServiceID string
+ CheckID string
+}
+
+// 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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/connect.go b/vendor/github.com/hashicorp/consul/api/connect.go
new file mode 100644
index 0000000..a40d1e2
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/connect.go
@@ -0,0 +1,12 @@
+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}
+}
diff --git a/vendor/github.com/hashicorp/consul/api/connect_ca.go b/vendor/github.com/hashicorp/consul/api/connect_ca.go
new file mode 100644
index 0000000..600a3e0
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/connect_ca.go
@@ -0,0 +1,174 @@
+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{}
+
+ 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
+}
+
+// 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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/connect_intention.go b/vendor/github.com/hashicorp/consul/api/connect_intention.go
new file mode 100644
index 0000000..a996c03
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/connect_intention.go
@@ -0,0 +1,302 @@
+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 a whitelist or blacklist intention.
+ Action IntentionAction
+
+ // DefaultAddr, DefaultPort of the local listening proxy (if any) to
+ // make this connection.
+ DefaultAddr string
+ DefaultPort int
+
+ // 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
+
+ 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 whitelist or blacklist 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
+}
+
+// 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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/coordinate.go b/vendor/github.com/hashicorp/consul/api/coordinate.go
new file mode 100644
index 0000000..53318f1
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/coordinate.go
@@ -0,0 +1,106 @@
+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 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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/debug.go b/vendor/github.com/hashicorp/consul/api/debug.go
new file mode 100644
index 0000000..2380468
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/debug.go
@@ -0,0 +1,106 @@
+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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/event.go b/vendor/github.com/hashicorp/consul/api/event.go
new file mode 100644
index 0000000..85b5b06
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/event.go
@@ -0,0 +1,104 @@
+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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/health.go b/vendor/github.com/hashicorp/consul/api/health.go
new file mode 100644
index 0000000..9faf6b6
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/health.go
@@ -0,0 +1,330 @@
+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 (
+ // 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
+
+ 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
+ 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 (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error {
+ type Alias HealthCheckDefinition
+ aux := &struct {
+ Interval string
+ Timeout string
+ DeregisterCriticalServiceAfter string
+ *Alias
+ }{
+ Alias: (*Alias)(d),
+ }
+ if err := json.Unmarshal(data, &aux); err != nil {
+ return err
+ }
+
+ // Parse the values into both the time.Duration and old ReadableDuration fields.
+ var err error
+ if aux.Interval != "" {
+ if d.IntervalDuration, err = time.ParseDuration(aux.Interval); err != nil {
+ return err
+ }
+ d.Interval = ReadableDuration(d.IntervalDuration)
+ }
+ if aux.Timeout != "" {
+ if d.TimeoutDuration, err = time.ParseDuration(aux.Timeout); err != nil {
+ return err
+ }
+ d.Timeout = ReadableDuration(d.TimeoutDuration)
+ }
+ if aux.DeregisterCriticalServiceAfter != "" {
+ if d.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil {
+ return err
+ }
+ d.DeregisterCriticalServiceAfter = ReadableDuration(d.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 := string(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, false)
+}
+
+func (h *Health) ServiceMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
+ return h.service(service, tags, passingOnly, q, false)
+}
+
+// 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, true)
+}
+
+func (h *Health) ConnectMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
+ return h.service(service, tags, passingOnly, q, true)
+}
+
+func (h *Health) service(service string, tags []string, passingOnly bool, q *QueryOptions, connect bool) ([]*ServiceEntry, *QueryMeta, error) {
+ path := "/v1/health/service/" + service
+ if connect {
+ path = "/v1/health/connect/" + 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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/kv.go b/vendor/github.com/hashicorp/consul/api/kv.go
new file mode 100644
index 0000000..bd45a06
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/kv.go
@@ -0,0 +1,286 @@
+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
+}
+
+// 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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/lock.go b/vendor/github.com/hashicorp/consul/api/lock.go
new file mode 100644
index 0000000..82339cb
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/lock.go
@@ -0,0 +1,386 @@
+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
+}
+
+// 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
+ }
+
+ // 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, nil, 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,
+ }
+
+ 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, nil)
+ 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 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()
+ _, _, err := kv.Release(lockEnt, nil)
+ 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()
+ pair, _, err := kv.Get(l.opts.Key, nil)
+ 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
+ didRemove, _, err := kv.DeleteCAS(pair, nil)
+ 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,
+ }
+ }
+ id, _, err := session.Create(se, nil)
+ 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}
+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
+ }
+}
diff --git a/vendor/github.com/hashicorp/consul/api/operator.go b/vendor/github.com/hashicorp/consul/api/operator.go
new file mode 100644
index 0000000..079e224
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/operator.go
@@ -0,0 +1,11 @@
+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}
+}
diff --git a/vendor/github.com/hashicorp/consul/api/operator_area.go b/vendor/github.com/hashicorp/consul/api/operator_area.go
new file mode 100644
index 0000000..5cf7e49
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/operator_area.go
@@ -0,0 +1,194 @@
+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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/operator_autopilot.go b/vendor/github.com/hashicorp/consul/api/operator_autopilot.go
new file mode 100644
index 0000000..b179406
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/operator_autopilot.go
@@ -0,0 +1,219 @@
+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
+
+ // 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) error {
+ if d == nil {
+ return fmt.Errorf("cannot unmarshal to nil pointer")
+ }
+
+ str := string(raw)
+ if len(str) < 2 || str[0] != '"' || str[len(str)-1] != '"' {
+ return fmt.Errorf("must be enclosed with quotes: %s", str)
+ }
+ dur, err := time.ParseDuration(str[1 : len(str)-1])
+ if err != nil {
+ return err
+ }
+ *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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/operator_keyring.go b/vendor/github.com/hashicorp/consul/api/operator_keyring.go
new file mode 100644
index 0000000..038d5d5
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/operator_keyring.go
@@ -0,0 +1,89 @@
+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
+
+ // 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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/operator_raft.go b/vendor/github.com/hashicorp/consul/api/operator_raft.go
new file mode 100644
index 0000000..a9844df
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/operator_raft.go
@@ -0,0 +1,89 @@
+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", string(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", string(id))
+
+ _, resp, err := requireOK(op.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+
+ resp.Body.Close()
+ return nil
+}
diff --git a/vendor/github.com/hashicorp/consul/api/operator_segment.go b/vendor/github.com/hashicorp/consul/api/operator_segment.go
new file mode 100644
index 0000000..92b05d3
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/operator_segment.go
@@ -0,0 +1,11 @@
+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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/prepared_query.go b/vendor/github.com/hashicorp/consul/api/prepared_query.go
new file mode 100644
index 0000000..0204581
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/prepared_query.go
@@ -0,0 +1,217 @@
+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
+
+ // 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
+
+ // 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
+}
diff --git a/vendor/github.com/hashicorp/consul/api/raw.go b/vendor/github.com/hashicorp/consul/api/raw.go
new file mode 100644
index 0000000..745a208
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/raw.go
@@ -0,0 +1,24 @@
+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)
+}
diff --git a/vendor/github.com/hashicorp/consul/api/semaphore.go b/vendor/github.com/hashicorp/consul/api/semaphore.go
new file mode 100644
index 0000000..bc4f885
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/semaphore.go
@@ -0,0 +1,514 @@
+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
+}
+
+// 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()
+ made, _, err := kv.Acquire(s.contenderEntry(s.lockSession), nil)
+ 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,
+ }
+
+ 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, nil)
+ 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
+ s.isHeld = false
+
+ // Stop the session renew
+ if s.sessionRenew != nil {
+ defer func() {
+ close(s.sessionRenew)
+ s.sessionRenew = nil
+ }()
+ }
+
+ // Get and clear the lock session
+ lockSession := s.lockSession
+ s.lockSession = ""
+
+ // Remove ourselves as a lock holder
+ kv := s.c.KV()
+ key := path.Join(s.opts.Prefix, DefaultSemaphoreKey)
+READ:
+ pair, _, err := kv.Get(key, nil)
+ if err != nil {
+ return err
+ }
+ if pair == nil {
+ pair = &KVPair{}
+ }
+ lock, err := s.decodeLock(pair)
+ if err != nil {
+ return err
+ }
+
+ // Create a new lock without us as a holder
+ if _, ok := lock.Holders[lockSession]; ok {
+ delete(lock.Holders, lockSession)
+ newLock, err := s.encodeLock(lock, pair.ModifyIndex)
+ if err != nil {
+ return err
+ }
+
+ // Swap the locks
+ didSet, _, err := kv.CAS(newLock, nil)
+ if err != nil {
+ return fmt.Errorf("failed to update lock: %v", err)
+ }
+ if !didSet {
+ goto READ
+ }
+ }
+
+ // Destroy the contender entry
+ contenderKey := path.Join(s.opts.Prefix, lockSession)
+ if _, err := kv.Delete(contenderKey, nil); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Destroy is used to cleanup the semaphore entry. It is not necessary
+// to invoke. It will fail if the semaphore is in use.
+func (s *Semaphore) Destroy() 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 ErrSemaphoreHeld
+ }
+
+ // List for the semaphore
+ kv := s.c.KV()
+ pairs, _, err := kv.List(s.opts.Prefix, nil)
+ if err != nil {
+ return fmt.Errorf("failed to read prefix: %v", err)
+ }
+
+ // Find the lock pair, bail if it doesn't exist
+ lockPair := s.findLock(pairs)
+ if lockPair.ModifyIndex == 0 {
+ return nil
+ }
+ if lockPair.Flags != SemaphoreFlagValue {
+ return ErrSemaphoreConflict
+ }
+
+ // Decode the lock
+ lock, err := s.decodeLock(lockPair)
+ if err != nil {
+ return err
+ }
+
+ // Prune the dead holders
+ s.pruneDeadHolders(lock, pairs)
+
+ // Check if there are any holders
+ if len(lock.Holders) > 0 {
+ return ErrSemaphoreInUse
+ }
+
+ // Attempt the delete
+ didRemove, _, err := kv.DeleteCAS(lockPair, nil)
+ if err != nil {
+ return fmt.Errorf("failed to remove semaphore: %v", err)
+ }
+ if !didRemove {
+ return ErrSemaphoreInUse
+ }
+ return nil
+}
+
+// createSession is used to create a new managed session
+func (s *Semaphore) createSession() (string, error) {
+ session := s.c.Session()
+ se := &SessionEntry{
+ Name: s.opts.SessionName,
+ TTL: s.opts.SessionTTL,
+ Behavior: SessionBehaviorDelete,
+ }
+ id, _, err := session.Create(se, nil)
+ if err != nil {
+ return "", err
+ }
+ return id, nil
+}
+
+// contenderEntry returns a formatted KVPair for the contender
+func (s *Semaphore) contenderEntry(session string) *KVPair {
+ return &KVPair{
+ Key: path.Join(s.opts.Prefix, session),
+ Value: s.opts.Value,
+ Session: session,
+ Flags: SemaphoreFlagValue,
+ }
+}
+
+// findLock is used to find the KV Pair which is used for coordination
+func (s *Semaphore) findLock(pairs KVPairs) *KVPair {
+ key := path.Join(s.opts.Prefix, DefaultSemaphoreKey)
+ for _, pair := range pairs {
+ if pair.Key == key {
+ return pair
+ }
+ }
+ return &KVPair{Flags: SemaphoreFlagValue}
+}
+
+// decodeLock is used to decode a semaphoreLock from an
+// entry in Consul
+func (s *Semaphore) decodeLock(pair *KVPair) (*semaphoreLock, error) {
+ // Handle if there is no lock
+ if pair == nil || pair.Value == nil {
+ return &semaphoreLock{
+ Limit: s.opts.Limit,
+ Holders: make(map[string]bool),
+ }, nil
+ }
+
+ l := &semaphoreLock{}
+ if err := json.Unmarshal(pair.Value, l); err != nil {
+ return nil, fmt.Errorf("lock decoding failed: %v", err)
+ }
+ return l, nil
+}
+
+// encodeLock is used to encode a semaphoreLock into a KVPair
+// that can be PUT
+func (s *Semaphore) encodeLock(l *semaphoreLock, oldIndex uint64) (*KVPair, error) {
+ enc, err := json.Marshal(l)
+ if err != nil {
+ return nil, fmt.Errorf("lock encoding failed: %v", err)
+ }
+ pair := &KVPair{
+ Key: path.Join(s.opts.Prefix, DefaultSemaphoreKey),
+ Value: enc,
+ Flags: SemaphoreFlagValue,
+ ModifyIndex: oldIndex,
+ }
+ return pair, nil
+}
+
+// pruneDeadHolders is used to remove all the dead lock holders
+func (s *Semaphore) pruneDeadHolders(lock *semaphoreLock, pairs KVPairs) {
+ // Gather all the live holders
+ alive := make(map[string]struct{}, len(pairs))
+ for _, pair := range pairs {
+ if pair.Session != "" {
+ alive[pair.Session] = struct{}{}
+ }
+ }
+
+ // Remove any holders that are dead
+ for holder := range lock.Holders {
+ if _, ok := alive[holder]; !ok {
+ delete(lock.Holders, holder)
+ }
+ }
+}
+
+// monitorLock is a long running routine to monitor a semaphore ownership
+// It closes the stopCh if we lose our slot.
+func (s *Semaphore) monitorLock(session string, stopCh chan struct{}) {
+ defer close(stopCh)
+ kv := s.c.KV()
+ opts := &QueryOptions{RequireConsistent: true}
+WAIT:
+ retries := s.opts.MonitorRetries
+RETRY:
+ pairs, meta, err := kv.List(s.opts.Prefix, 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(s.opts.MonitorRetryTime)
+ retries--
+ opts.WaitIndex = 0
+ goto RETRY
+ }
+ return
+ }
+ lockPair := s.findLock(pairs)
+ lock, err := s.decodeLock(lockPair)
+ if err != nil {
+ return
+ }
+ s.pruneDeadHolders(lock, pairs)
+ if _, ok := lock.Holders[session]; ok {
+ opts.WaitIndex = meta.LastIndex
+ goto WAIT
+ }
+}
diff --git a/vendor/github.com/hashicorp/consul/api/session.go b/vendor/github.com/hashicorp/consul/api/session.go
new file mode 100644
index 0000000..1613f11
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/session.go
@@ -0,0 +1,224 @@
+package api
+
+import (
+ "errors"
+ "fmt"
+ "time"
+)
+
+const (
+ // SessionBehaviorRelease is the default behavior and causes
+ // all associated locks to be released on session invalidation.
+ SessionBehaviorRelease = "release"
+
+ // SessionBehaviorDelete is new in Consul 0.5 and changes the
+ // behavior to delete all associated locks on session invalidation.
+ // It can be used in a way similar to Ephemeral Nodes in ZooKeeper.
+ SessionBehaviorDelete = "delete"
+)
+
+var ErrSessionExpired = errors.New("session expired")
+
+// SessionEntry represents a session in consul
+type SessionEntry struct {
+ CreateIndex uint64
+ ID string
+ Name string
+ Node string
+ Checks []string
+ LockDelay time.Duration
+ Behavior string
+ TTL string
+}
+
+// Session can be used to query the Session endpoints
+type Session struct {
+ c *Client
+}
+
+// Session returns a handle to the session endpoints
+func (c *Client) Session() *Session {
+ return &Session{c}
+}
+
+// CreateNoChecks is like Create but is used specifically to create
+// a session with no associated health checks.
+func (s *Session) CreateNoChecks(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
+ body := make(map[string]interface{})
+ body["Checks"] = []string{}
+ if se != nil {
+ if se.Name != "" {
+ body["Name"] = se.Name
+ }
+ if se.Node != "" {
+ body["Node"] = se.Node
+ }
+ if se.LockDelay != 0 {
+ body["LockDelay"] = durToMsec(se.LockDelay)
+ }
+ if se.Behavior != "" {
+ body["Behavior"] = se.Behavior
+ }
+ if se.TTL != "" {
+ body["TTL"] = se.TTL
+ }
+ }
+ return s.create(body, q)
+
+}
+
+// Create makes a new session. Providing a session entry can
+// customize the session. It can also be nil to use defaults.
+func (s *Session) Create(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
+ var obj interface{}
+ if se != nil {
+ body := make(map[string]interface{})
+ obj = body
+ if se.Name != "" {
+ body["Name"] = se.Name
+ }
+ if se.Node != "" {
+ body["Node"] = se.Node
+ }
+ if se.LockDelay != 0 {
+ body["LockDelay"] = durToMsec(se.LockDelay)
+ }
+ if len(se.Checks) > 0 {
+ body["Checks"] = se.Checks
+ }
+ if se.Behavior != "" {
+ body["Behavior"] = se.Behavior
+ }
+ if se.TTL != "" {
+ body["TTL"] = se.TTL
+ }
+ }
+ return s.create(obj, q)
+}
+
+func (s *Session) create(obj interface{}, q *WriteOptions) (string, *WriteMeta, error) {
+ var out struct{ ID string }
+ wm, err := s.c.write("/v1/session/create", obj, &out, q)
+ if err != nil {
+ return "", nil, err
+ }
+ return out.ID, wm, nil
+}
+
+// Destroy invalidates a given session
+func (s *Session) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
+ wm, err := s.c.write("/v1/session/destroy/"+id, nil, nil, q)
+ if err != nil {
+ return nil, err
+ }
+ return wm, nil
+}
+
+// Renew renews the TTL on a given session
+func (s *Session) Renew(id string, q *WriteOptions) (*SessionEntry, *WriteMeta, error) {
+ r := s.c.newRequest("PUT", "/v1/session/renew/"+id)
+ r.setWriteOptions(q)
+ rtt, resp, err := s.c.doRequest(r)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ wm := &WriteMeta{RequestTime: rtt}
+
+ if resp.StatusCode == 404 {
+ return nil, wm, nil
+ } else if resp.StatusCode != 200 {
+ return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
+ }
+
+ var entries []*SessionEntry
+ if err := decodeBody(resp, &entries); err != nil {
+ return nil, nil, fmt.Errorf("Failed to read response: %v", err)
+ }
+ if len(entries) > 0 {
+ return entries[0], wm, nil
+ }
+ return nil, wm, nil
+}
+
+// RenewPeriodic is used to periodically invoke Session.Renew on a
+// session until a doneCh is closed. This is meant to be used in a long running
+// goroutine to ensure a session stays valid.
+func (s *Session) RenewPeriodic(initialTTL string, id string, q *WriteOptions, doneCh <-chan struct{}) error {
+ ctx := q.Context()
+
+ ttl, err := time.ParseDuration(initialTTL)
+ if err != nil {
+ return err
+ }
+
+ waitDur := ttl / 2
+ lastRenewTime := time.Now()
+ var lastErr error
+ for {
+ if time.Since(lastRenewTime) > ttl {
+ return lastErr
+ }
+ select {
+ case <-time.After(waitDur):
+ entry, _, err := s.Renew(id, q)
+ if err != nil {
+ waitDur = time.Second
+ lastErr = err
+ continue
+ }
+ if entry == nil {
+ return ErrSessionExpired
+ }
+
+ // Handle the server updating the TTL
+ ttl, _ = time.ParseDuration(entry.TTL)
+ waitDur = ttl / 2
+ lastRenewTime = time.Now()
+
+ case <-doneCh:
+ // Attempt a session destroy
+ s.Destroy(id, q)
+ return nil
+
+ case <-ctx.Done():
+ // Bail immediately since attempting the destroy would
+ // use the canceled context in q, which would just bail.
+ return ctx.Err()
+ }
+ }
+}
+
+// Info looks up a single session
+func (s *Session) Info(id string, q *QueryOptions) (*SessionEntry, *QueryMeta, error) {
+ var entries []*SessionEntry
+ qm, err := s.c.query("/v1/session/info/"+id, &entries, q)
+ if err != nil {
+ return nil, nil, err
+ }
+ if len(entries) > 0 {
+ return entries[0], qm, nil
+ }
+ return nil, qm, nil
+}
+
+// List gets sessions for a node
+func (s *Session) Node(node string, q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
+ var entries []*SessionEntry
+ qm, err := s.c.query("/v1/session/node/"+node, &entries, q)
+ if err != nil {
+ return nil, nil, err
+ }
+ return entries, qm, nil
+}
+
+// List gets all active sessions
+func (s *Session) List(q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
+ var entries []*SessionEntry
+ qm, err := s.c.query("/v1/session/list", &entries, q)
+ if err != nil {
+ return nil, nil, err
+ }
+ return entries, qm, nil
+}
diff --git a/vendor/github.com/hashicorp/consul/api/snapshot.go b/vendor/github.com/hashicorp/consul/api/snapshot.go
new file mode 100644
index 0000000..e902377
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/snapshot.go
@@ -0,0 +1,47 @@
+package api
+
+import (
+ "io"
+)
+
+// Snapshot can be used to query the /v1/snapshot endpoint to take snapshots of
+// Consul's internal state and restore snapshots for disaster recovery.
+type Snapshot struct {
+ c *Client
+}
+
+// Snapshot returns a handle that exposes the snapshot endpoints.
+func (c *Client) Snapshot() *Snapshot {
+ return &Snapshot{c}
+}
+
+// Save requests a new snapshot and provides an io.ReadCloser with the snapshot
+// data to save. If this doesn't return an error, then it's the responsibility
+// of the caller to close it. Only a subset of the QueryOptions are supported:
+// Datacenter, AllowStale, and Token.
+func (s *Snapshot) Save(q *QueryOptions) (io.ReadCloser, *QueryMeta, error) {
+ r := s.c.newRequest("GET", "/v1/snapshot")
+ r.setQueryOptions(q)
+
+ rtt, resp, err := requireOK(s.c.doRequest(r))
+ if err != nil {
+ return nil, nil, err
+ }
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+ return resp.Body, qm, nil
+}
+
+// Restore streams in an existing snapshot and attempts to restore it.
+func (s *Snapshot) Restore(q *WriteOptions, in io.Reader) error {
+ r := s.c.newRequest("PUT", "/v1/snapshot")
+ r.body = in
+ r.setWriteOptions(q)
+ _, _, err := requireOK(s.c.doRequest(r))
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/vendor/github.com/hashicorp/consul/api/status.go b/vendor/github.com/hashicorp/consul/api/status.go
new file mode 100644
index 0000000..74ef61a
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/status.go
@@ -0,0 +1,43 @@
+package api
+
+// Status can be used to query the Status endpoints
+type Status struct {
+ c *Client
+}
+
+// Status returns a handle to the status endpoints
+func (c *Client) Status() *Status {
+ return &Status{c}
+}
+
+// Leader is used to query for a known leader
+func (s *Status) Leader() (string, error) {
+ r := s.c.newRequest("GET", "/v1/status/leader")
+ _, resp, err := requireOK(s.c.doRequest(r))
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+
+ var leader string
+ if err := decodeBody(resp, &leader); err != nil {
+ return "", err
+ }
+ return leader, nil
+}
+
+// Peers is used to query for a known raft peers
+func (s *Status) Peers() ([]string, error) {
+ r := s.c.newRequest("GET", "/v1/status/peers")
+ _, resp, err := requireOK(s.c.doRequest(r))
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var peers []string
+ if err := decodeBody(resp, &peers); err != nil {
+ return nil, err
+ }
+ return peers, nil
+}
diff --git a/vendor/github.com/hashicorp/consul/api/txn.go b/vendor/github.com/hashicorp/consul/api/txn.go
new file mode 100644
index 0000000..65d7a16
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/api/txn.go
@@ -0,0 +1,230 @@
+package api
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+// Txn is used to manipulate the Txn API
+type Txn struct {
+ c *Client
+}
+
+// Txn is used to return a handle to the K/V apis
+func (c *Client) Txn() *Txn {
+ return &Txn{c}
+}
+
+// TxnOp is the internal format we send to Consul. Currently only K/V and
+// check operations are supported.
+type TxnOp struct {
+ KV *KVTxnOp
+ Node *NodeTxnOp
+ Service *ServiceTxnOp
+ Check *CheckTxnOp
+}
+
+// TxnOps is a list of transaction operations.
+type TxnOps []*TxnOp
+
+// TxnResult is the internal format we receive from Consul.
+type TxnResult struct {
+ KV *KVPair
+ Node *Node
+ Service *CatalogService
+ Check *HealthCheck
+}
+
+// TxnResults is a list of TxnResult objects.
+type TxnResults []*TxnResult
+
+// TxnError is used to return information about an operation in a transaction.
+type TxnError struct {
+ OpIndex int
+ What string
+}
+
+// TxnErrors is a list of TxnError objects.
+type TxnErrors []*TxnError
+
+// TxnResponse is the internal format we receive from Consul.
+type TxnResponse struct {
+ Results TxnResults
+ Errors TxnErrors
+}
+
+// KVOp constants give possible operations available in a transaction.
+type KVOp string
+
+const (
+ KVSet KVOp = "set"
+ KVDelete KVOp = "delete"
+ KVDeleteCAS KVOp = "delete-cas"
+ KVDeleteTree KVOp = "delete-tree"
+ KVCAS KVOp = "cas"
+ KVLock KVOp = "lock"
+ KVUnlock KVOp = "unlock"
+ KVGet KVOp = "get"
+ KVGetTree KVOp = "get-tree"
+ KVCheckSession KVOp = "check-session"
+ KVCheckIndex KVOp = "check-index"
+ KVCheckNotExists KVOp = "check-not-exists"
+)
+
+// KVTxnOp defines a single operation inside a transaction.
+type KVTxnOp struct {
+ Verb KVOp
+ Key string
+ Value []byte
+ Flags uint64
+ Index uint64
+ Session string
+}
+
+// KVTxnOps defines a set of operations to be performed inside a single
+// transaction.
+type KVTxnOps []*KVTxnOp
+
+// KVTxnResponse has the outcome of a transaction.
+type KVTxnResponse struct {
+ Results []*KVPair
+ Errors TxnErrors
+}
+
+// NodeOp constants give possible operations available in a transaction.
+type NodeOp string
+
+const (
+ NodeGet NodeOp = "get"
+ NodeSet NodeOp = "set"
+ NodeCAS NodeOp = "cas"
+ NodeDelete NodeOp = "delete"
+ NodeDeleteCAS NodeOp = "delete-cas"
+)
+
+// NodeTxnOp defines a single operation inside a transaction.
+type NodeTxnOp struct {
+ Verb NodeOp
+ Node Node
+}
+
+// ServiceOp constants give possible operations available in a transaction.
+type ServiceOp string
+
+const (
+ ServiceGet ServiceOp = "get"
+ ServiceSet ServiceOp = "set"
+ ServiceCAS ServiceOp = "cas"
+ ServiceDelete ServiceOp = "delete"
+ ServiceDeleteCAS ServiceOp = "delete-cas"
+)
+
+// ServiceTxnOp defines a single operation inside a transaction.
+type ServiceTxnOp struct {
+ Verb ServiceOp
+ Node string
+ Service AgentService
+}
+
+// CheckOp constants give possible operations available in a transaction.
+type CheckOp string
+
+const (
+ CheckGet CheckOp = "get"
+ CheckSet CheckOp = "set"
+ CheckCAS CheckOp = "cas"
+ CheckDelete CheckOp = "delete"
+ CheckDeleteCAS CheckOp = "delete-cas"
+)
+
+// CheckTxnOp defines a single operation inside a transaction.
+type CheckTxnOp struct {
+ Verb CheckOp
+ Check HealthCheck
+}
+
+// Txn is used to apply multiple Consul operations in a single, atomic transaction.
+//
+// Note that Go will perform the required base64 encoding on the values
+// automatically because the type is a byte slice. Transactions are defined as a
+// list of operations to perform, using the different fields in the TxnOp structure
+// to define operations. If any operation fails, none of the changes are applied
+// to the state store.
+//
+// Even though this is generally a write operation, we take a QueryOptions input
+// and return a QueryMeta output. If the transaction contains only read ops, then
+// Consul will fast-path it to a different endpoint internally which supports
+// consistency controls, but not blocking. If there are write operations then
+// the request will always be routed through raft and any consistency settings
+// will be ignored.
+//
+// Here's an example:
+//
+// ops := KVTxnOps{
+// &KVTxnOp{
+// Verb: KVLock,
+// Key: "test/lock",
+// Session: "adf4238a-882b-9ddc-4a9d-5b6758e4159e",
+// Value: []byte("hello"),
+// },
+// &KVTxnOp{
+// Verb: KVGet,
+// Key: "another/key",
+// },
+// &CheckTxnOp{
+// Verb: CheckSet,
+// HealthCheck: HealthCheck{
+// Node: "foo",
+// CheckID: "redis:a",
+// Name: "Redis Health Check",
+// Status: "passing",
+// },
+// }
+// }
+// ok, response, _, err := kv.Txn(&ops, nil)
+//
+// If there is a problem making the transaction request then an error will be
+// returned. Otherwise, the ok value will be true if the transaction succeeded
+// or false if it was rolled back. The response is a structured return value which
+// will have the outcome of the transaction. Its Results member will have entries
+// for each operation. For KV operations, Deleted keys will have a nil entry in the
+// results, and to save space, the Value of each key in the Results will be nil
+// unless the operation is a KVGet. If the transaction was rolled back, the Errors
+// member will have entries referencing the index of the operation that failed
+// along with an error message.
+func (t *Txn) Txn(txn TxnOps, q *QueryOptions) (bool, *TxnResponse, *QueryMeta, error) {
+ return t.c.txn(txn, q)
+}
+
+func (c *Client) txn(txn TxnOps, q *QueryOptions) (bool, *TxnResponse, *QueryMeta, error) {
+ r := c.newRequest("PUT", "/v1/txn")
+ r.setQueryOptions(q)
+
+ r.obj = txn
+ rtt, resp, err := c.doRequest(r)
+ if err != nil {
+ return false, nil, nil, err
+ }
+ defer resp.Body.Close()
+
+ qm := &QueryMeta{}
+ parseQueryMeta(resp, qm)
+ qm.RequestTime = rtt
+
+ if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusConflict {
+ var txnResp TxnResponse
+ if err := decodeBody(resp, &txnResp); err != nil {
+ return false, nil, nil, err
+ }
+
+ return resp.StatusCode == http.StatusOK, &txnResp, qm, nil
+ }
+
+ var buf bytes.Buffer
+ if _, err := io.Copy(&buf, resp.Body); err != nil {
+ return false, nil, nil, fmt.Errorf("Failed to read response: %v", err)
+ }
+ return false, nil, nil, fmt.Errorf("Failed request: %s", buf.String())
+}
diff --git a/vendor/github.com/hashicorp/consul/ui-v2/app/styles/components/notice.scss b/vendor/github.com/hashicorp/consul/ui-v2/app/styles/components/notice.scss
new file mode 100644
index 0000000..3d0a22d
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/ui-v2/app/styles/components/notice.scss
@@ -0,0 +1,10 @@
+@import './notice/index';
+.notice.warning {
+ @extend %notice-warning;
+}
+.notice.info {
+ @extend %notice-info;
+}
+.notice.policy-management {
+ @extend %notice-highlight;
+}
diff --git a/vendor/github.com/hashicorp/consul/ui-v2/lib/block-slots/LICENSE.md b/vendor/github.com/hashicorp/consul/ui-v2/lib/block-slots/LICENSE.md
new file mode 100644
index 0000000..c75ad2a
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/ui-v2/lib/block-slots/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Ciena Corporation.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/hashicorp/consul/website/LICENSE.md b/vendor/github.com/hashicorp/consul/website/LICENSE.md
new file mode 100644
index 0000000..3189f43
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/website/LICENSE.md
@@ -0,0 +1,10 @@
+# Proprietary License
+
+This license is temporary while a more official one is drafted. However,
+this should make it clear:
+
+The text contents of this website are MPL 2.0 licensed.
+
+The design contents of this website are proprietary and may not be reproduced
+or reused in any way other than to run the website locally. The license for
+the design is owned solely by HashiCorp, Inc.
diff --git a/vendor/github.com/hashicorp/consul/website/source/api/operator/license.html.md b/vendor/github.com/hashicorp/consul/website/source/api/operator/license.html.md
new file mode 100644
index 0000000..dc2c2c3
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/website/source/api/operator/license.html.md
@@ -0,0 +1,145 @@
+---
+layout: api
+page_title: License - Operator - HTTP API
+sidebar_current: api-operator-license
+description: |-
+ The /operator/license endpoints allow for setting and retrieving the Consul
+ Enterprise License.
+---
+
+# License - Operator HTTP API
+
+~> **Enterprise Only!** This API endpoint and functionality only exists in
+Consul Enterprise. This is not present in the open source version of Consul.
+
+The licensing functionality described here is available only in
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) version 1.1.0 and later.
+
+## Getting the Consul License
+
+This endpoint gets information about the current license.
+
+| Method | Path | Produces |
+| ------ | ---------------------------- | -------------------------- |
+| `GET` | `/operator/license` | `application/json` |
+
+The table below shows this endpoint's support for
+[blocking queries](/api/index.html#blocking-queries),
+[consistency modes](/api/index.html#consistency-modes),
+[agent caching](/api/index.html#agent-caching), and
+[required ACLs](/api/index.html#acls).
+
+| Blocking Queries | Consistency Modes | Agent Caching | ACL Required |
+| ---------------- | ----------------- | ------------- | ---------------- |
+| `NO` | `all` | `none` | `none` |
+
+### Parameters
+
+- `dc` `(string: "")` - Specifies the datacenter whose license should be retrieved.
+ This will default to the datacenter of the agent serving the HTTP request.
+ This is specified as a URL query parameter.
+
+### Sample Request
+
+```text
+$ curl \
+ http://127.0.0.1:8500/v1/operator/license
+```
+
+### Sample Response
+
+```json
+{
+ "Valid": true,
+ "License": {
+ "license_id": "2afbf681-0d1a-0649-cb6c-333ec9f0989c",
+ "customer_id": "0259271d-8ffc-e85e-0830-c0822c1f5f2b",
+ "installation_id": "*",
+ "issue_time": "2018-05-21T20:03:35.911567355Z",
+ "start_time": "2018-05-21T04:00:00Z",
+ "expiration_time": "2019-05-22T03:59:59.999Z",
+ "product": "consul",
+ "flags": {
+ "package": "premium"
+ },
+ "features": [
+ "Automated Backups",
+ "Automated Upgrades",
+ "Enhanced Read Scalability",
+ "Network Segments",
+ "Redundancy Zone",
+ "Advanced Network Federation"
+ ],
+ "temporary": false
+ },
+ "Warnings": []
+}
+```
+
+## Updating the Consul License
+
+This endpoint updates the Consul license and returns some of the
+license contents as well as any warning messages regarding its validity.
+
+| Method | Path | Produces |
+| ------ | ---------------------------- | -------------------------- |
+| `PUT` | `/operator/license` | `application/json` |
+
+The table below shows this endpoint's support for
+[blocking queries](/api/index.html#blocking-queries),
+[consistency modes](/api/index.html#consistency-modes),
+[agent caching](/api/index.html#agent-caching), and
+[required ACLs](/api/index.html#acls).
+
+| Blocking Queries | Consistency Modes | Agent Caching | ACL Required |
+| ---------------- | ----------------- | ------------- | ---------------- |
+| `NO` | `none` | `none` | `operator:write` |
+
+### Parameters
+
+- `dc` `(string: "")` - Specifies the datacenter whose license should be updated.
+ This will default to the datacenter of the agent serving the HTTP request.
+ This is specified as a URL query parameter.
+
+### Sample Payload
+
+The payload is the raw license blob.
+
+### Sample Request
+
+```text
+$ curl \
+ --request PUT \
+ --data @consul.license \
+ http://127.0.0.1:8500/v1/operator/license
+```
+
+### Sample Response
+
+```json
+{
+ "Valid": true,
+ "License": {
+ "license_id": "2afbf681-0d1a-0649-cb6c-333ec9f0989c",
+ "customer_id": "0259271d-8ffc-e85e-0830-c0822c1f5f2b",
+ "installation_id": "*",
+ "issue_time": "2018-05-21T20:03:35.911567355Z",
+ "start_time": "2018-05-21T04:00:00Z",
+ "expiration_time": "2019-05-22T03:59:59.999Z",
+ "product": "consul",
+ "flags": {
+ "package": "premium"
+ },
+ "features": [
+ "Automated Backups",
+ "Automated Upgrades",
+ "Enhanced Read Scalability",
+ "Network Segments",
+ "Redundancy Zone",
+ "Advanced Network Federation"
+ ],
+ "temporary": false
+ },
+ "Warnings": []
+}
+```
diff --git a/vendor/github.com/hashicorp/consul/website/source/docs/commands/license.html.markdown.erb b/vendor/github.com/hashicorp/consul/website/source/docs/commands/license.html.markdown.erb
new file mode 100644
index 0000000..b65d171
--- /dev/null
+++ b/vendor/github.com/hashicorp/consul/website/source/docs/commands/license.html.markdown.erb
@@ -0,0 +1,109 @@
+---
+layout: "docs"
+page_title: "Commands: License"
+sidebar_current: "docs-commands-license"
+description: >
+ The license command provides datacenter-level management of the Consul Enterprise license.
+---
+
+# Consul License
+
+Command: `consul license`
+
+<%= enterprise_alert :consul %>
+
+The `license` command provides datacenter-level management of the Consul Enterprise license. This was added in Consul 1.1.0.
+
+If ACLs are enabled then a token with operator privileges may be required in
+order to use this command. Requests are forwarded internally to the leader
+if required, so this can be run from any Consul node in a cluster. See the
+[ACL Guide](/docs/guides/acl.html#operator) for more information.
+
+
+```text
+Usage: consul license <subcommand> [options] [args]
+
+ This command has subcommands for managing the Consul Enterprise license
+ Here are some simple examples, and more detailed examples are
+ available in the subcommands or the documentation.
+
+ Install a new license from a file:
+
+ $ consul license put @consul.license
+
+ Install a new license from stdin:
+
+ $ consul license put -
+
+ Install a new license from a string:
+
+ $ consul license put "<license blob>"
+
+ Retrieve the current license:
+
+ $ consul license get
+
+ For more examples, ask for subcommand help or view the documentation.
+
+Subcommands:
+ get Get the current license
+ put Puts a new license in the datacenter
+```
+
+## put
+
+This command sets the Consul Enterprise license.
+
+Usage: `consul license put [options] LICENSE`
+
+#### API Options
+
+<%= partial "docs/commands/http_api_options_client" %>
+<%= partial "docs/commands/http_api_options_server" %>
+
+The output looks like this:
+
+```
+License is valid
+License ID: 2afbf681-0d1a-0649-cb6c-333ec9f0989c
+Customer ID: 0259271d-8ffc-e85e-0830-c0822c1f5f2b
+Expires At: 2019-05-22 03:59:59.999 +0000 UTC
+Datacenter: *
+Package: premium
+Licensed Features:
+ Automated Backups
+ Automated Upgrades
+ Enhanced Read Scalability
+ Network Segments
+ Redundancy Zone
+ Advanced Network Federation
+```
+
+## get
+
+This command gets the Consul Enterprise license.
+
+Usage: `consul license get [options]`
+
+#### API Options
+
+<%= partial "docs/commands/http_api_options_client" %>
+<%= partial "docs/commands/http_api_options_server" %>
+
+The output looks like this:
+
+```
+License is valid
+License ID: 2afbf681-0d1a-0649-cb6c-333ec9f0989c
+Customer ID: 0259271d-8ffc-e85e-0830-c0822c1f5f2b
+Expires At: 2019-05-22 03:59:59.999 +0000 UTC
+Datacenter: *
+Package: premium
+Licensed Features:
+ Automated Backups
+ Automated Upgrades
+ Enhanced Read Scalability
+ Network Segments
+ Redundancy Zone
+ Advanced Network Federation
+```
\ No newline at end of file