diff --git a/configuration.go b/configuration.go index ffb2723bad..e59c95d979 100644 --- a/configuration.go +++ b/configuration.go @@ -334,7 +334,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { var defaultSidecar provider.Sidecar defaultSidecar.Watch = true defaultSidecar.Endpoint = "http://127.0.0.1:7777" - defaultSidecar.Frontend = "sidecar.toml" + defaultSidecar.Filename = "sidecar.toml" defaultSidecar.RefreshConn = flaeg.Duration(60 * time.Second) //default Docker diff --git a/glide.lock b/glide.lock index 24e57d3b69..5ff4e5be3a 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 10d4f5988aabcae824802eaba4ec35512453559752a0333969b94c2e69a0d7e5 -updated: 2017-04-13T16:14:00.147796817+01:00 +hash: 36b390c0e0b297694059c6138281cc84d24cff125217652d29a91188386083ae +updated: 2017-05-03T17:36:00.521286119+01:00 imports: - name: cloud.google.com/go version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c @@ -54,7 +54,7 @@ imports: - service/route53 - service/sts - name: github.com/Azure/azure-sdk-for-go - version: 088007b3b08cc02b27f2eadfdcd870958460ce7e + version: 8dd1f3ff407c300cff0a4bfedd969111ca5a7903 subpackages: - arm/dns - name: github.com/Azure/go-autorest @@ -85,7 +85,7 @@ imports: - name: github.com/codegangsta/cli version: bf4a526f48af7badd25d2cb02d587e1b01be3b50 - name: github.com/codegangsta/negroni - version: c0db5feaa33826cd5117930c8f4ee5c0f565eec6 + version: 0c3570ad2938a95d5d584eb76304b7f70642e4aa - name: github.com/containous/flaeg version: b5d2dc5878df07c2d74413348186982e7b865871 - name: github.com/containous/mux @@ -249,11 +249,11 @@ imports: - name: github.com/golang/glog version: fca8c8854093a154ff1eb580aae10276ad6b1b5f - name: github.com/golang/protobuf - version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef + version: 18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8 subpackages: - proto - name: github.com/google/go-github - version: de33c46a823d3f723514fc5333b75ef2e937b7df + version: e8d46665e050742f457a58088b1e6b794b2ae966 subpackages: - github - name: github.com/google/go-querystring @@ -269,7 +269,7 @@ imports: - name: github.com/gorilla/websocket version: a91eba7f97777409bc2c443f5534d41dd20c5720 - name: github.com/hashicorp/consul - version: a44c50eaf11944605cc2cfcca9e5de22edfe4ac9 + version: 10acfb76826d5378c2c6df349cd236c965492a30 subpackages: - api - name: github.com/hashicorp/errwrap @@ -282,18 +282,18 @@ imports: - codec - name: github.com/hashicorp/go-multierror version: d30f09973e19c1dfcd120b2d9c4f168e68d6b5d5 +- name: github.com/hashicorp/go-rootcerts + version: 6bb64b370b90e7ef1fa532be9e591a81c3493e00 - name: github.com/hashicorp/go-version version: 03c5bf6be031b6dd45afec16b1cf94fc8938bc77 - name: github.com/hashicorp/serf - version: 3d6a974239f0b515c87479d5beefe575ad866805 + version: 65c2babe73c7a096cd24e9eeec67613eb4e436c9 subpackages: - coordinate - name: github.com/JamesClonk/vultr - version: 0f156dd232bc4ebf8a32ba83fec57c0e4c9db69f + version: 7047a71e8edb5ccaa98e20cefe37a118dd7bfcb0 subpackages: - lib -- name: github.com/jarcoal/httpmock - version: 4442edb3db31196622da56482fd8d0fa375fba4d - name: github.com/jmespath/go-jmespath version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d - name: github.com/jonboulle/clockwork @@ -345,11 +345,13 @@ imports: - records/state - util - name: github.com/Microsoft/go-winio - version: fff283ad5116362ca252298cfc9b95828956d85d + version: f3b1913901892ada21d52d0cffe1d4c7080d36b7 - name: github.com/miekg/dns version: 8060d9f51305bbe024b99679454e62f552cd0b0b +- name: github.com/mitchellh/go-homedir + version: b8bc1bf767474819792c23f32d8286a45736f1c6 - name: github.com/mitchellh/mapstructure - version: 53818660ed4955e899c0bcafa97299a388bd7c8e + version: cc8532a8e9a55ea36402aa21efdf403a60d34096 - name: github.com/mvdan/xurls version: db96455566f05ffe42bd6ac671f05eeb1152b45d - name: github.com/Nitro/memberlist @@ -592,13 +594,15 @@ imports: version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 - name: gopkg.in/ini.v1 version: e7fea39b01aea8d5671f6858f0532f56e8bff3a5 +- name: gopkg.in/jarcoal/httpmock.v1 + version: cf52904a3cf0f78f199ecade6a6df8e245d5b25a - name: gopkg.in/mgo.v2 version: 3f83fa5005286a7fe593b055f0d7771a7dce4655 subpackages: - bson - internal/json - name: gopkg.in/ns1/ns1-go.v2 - version: 2abc76c60bf88ba33b15d1d87a13f624d8dff956 + version: c563826f4cbef9c11bebeb9f20a3f7afe9c1e2f4 subpackages: - rest - rest/model/account diff --git a/glide.yaml b/glide.yaml index f253403283..c72b44a840 100644 --- a/glide.yaml +++ b/glide.yaml @@ -165,8 +165,8 @@ import: version: a1dba9ce8baed984a2495b658c82687f8157b98f subpackages: - xfs -- package: github.com/jarcoal/httpmock - version: 4442edb3db31196622da56482fd8d0fa375fba4d +- package: gopkg.in/jarcoal/httpmock.v1 + version: cf52904a3cf0f78f199ecade6a6df8e245d5b25a - package: github.com/smartystreets/assertions version: edb6e295a22c57a2aa353b2f0729b6eff9f9f4b7 subpackages: diff --git a/provider/sidecar.go b/provider/sidecar.go index 214c941db3..03b24bcc00 100644 --- a/provider/sidecar.go +++ b/provider/sidecar.go @@ -1,6 +1,8 @@ package provider import ( + "encoding/json" + "io" "io/ioutil" "net" "net/http" @@ -10,8 +12,6 @@ import ( "strings" "time" - fsnotify "gopkg.in/fsnotify.v1" - "github.com/BurntSushi/toml" "github.com/Nitro/sidecar/catalog" "github.com/Nitro/sidecar/service" @@ -19,12 +19,28 @@ import ( "github.com/containous/traefik/log" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" + fsnotify "gopkg.in/fsnotify.v1" ) const ( - method = "wrr" - weight = 0 - sticky = false + method = "wrr" + weight = 0 + sticky = false + defaultMaxConnAmount = 2000 + defaultMaxConnExtractorFunc = "request.host" +) + +var ( + // Disable all timeouts for watcher requests + watcherHTTPTransport = &http.Transport{ResponseHeaderTimeout: 0} + watcherHTTPClient = &http.Client{ + Timeout: 0, + Transport: watcherHTTPTransport, + } + + sidecarHTTPClient = &http.Client{ + Timeout: 5 * time.Second, + } ) var _ Provider = (*Sidecar)(nil) @@ -33,22 +49,33 @@ var _ Provider = (*Sidecar)(nil) type Sidecar struct { BaseProvider `mapstructure:",squash"` Endpoint string `description:"Sidecar URL"` - Frontend string `description:"Configuration file for frontend"` configurationChan chan<- types.ConfigMessage RefreshConn flaeg.Duration `description:"How often to refresh the connection to Sidecar backend"` connTimer *time.Timer } +type sidecarFrontend struct { + *types.Frontend + MaxConn *types.MaxConn `json:"maxConn,omitempty"` +} + +type sidecarConfig struct { + Frontends map[string]*sidecarFrontend `json:"frontends,omitempty"` +} + type callback func(map[string][]*service.Service, error) // Provide allows the provider to provide configurations to traefik // using the given configuration channel. func (provider *Sidecar) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error { provider.configurationChan = configurationChan + if provider.Watch { - safe.Go(func() { - provider.sidecarWatcher() - }) + pool.Go( + func(stop chan bool) { + provider.sidecarWatcher(stop, pool) + }, + ) watcher, err := fsnotify.NewWatcher() if err != nil { @@ -56,7 +83,7 @@ func (provider *Sidecar) Provide(configurationChan chan<- types.ConfigMessage, p return err } - file, err := os.Open(provider.Frontend) + file, err := os.Open(provider.Filename) if err != nil { log.Errorln("Error opening file", err) return err @@ -75,7 +102,10 @@ func (provider *Sidecar) Provide(configurationChan chan<- types.ConfigMessage, p if errState != nil { log.Errorln("Error reloading Sidecar config", errState) } - provider.loadSidecarConfig(states.ByService()) + errLoadConfig := provider.loadSidecarConfig(states) + if errLoadConfig != nil { + log.Errorf("Error loading Sidecar config: %s", errLoadConfig) + } } case errWatcher := <-watcher.Errors: log.Errorln("Watcher event error", errWatcher) @@ -92,29 +122,47 @@ func (provider *Sidecar) Provide(configurationChan chan<- types.ConfigMessage, p if err != nil { log.Fatalln("Error reloading Sidecar config", err) } - err = provider.loadSidecarConfig(states.ByService()) - if err != nil { - return err - } - return nil + + return provider.loadSidecarConfig(states) } -func (provider *Sidecar) constructConfig(sidecarStates map[string][]*service.Service) (*types.Configuration, error) { +func (provider *Sidecar) constructConfig(sidecarStates *catalog.ServicesState) (*types.Configuration, error) { log.Infoln("loading sidecar config") - sidecarConfig := types.Configuration{Backends: provider.makeBackends(sidecarStates)} - var err error - sidecarConfig.Frontends, err = provider.makeFrontend() + sidecarConfig := types.Configuration{ + Backends: provider.makeBackends(sidecarStates), + Frontends: map[string]*types.Frontend{}, + } + + frontends, err := provider.makeFrontends() if err != nil { return nil, err } + + for name, frontend := range frontends { + sidecarConfig.Frontends[name] = frontend.Frontend + + if backend, ok := sidecarConfig.Backends[name]; ok { + backend.MaxConn = frontend.MaxConn + + if backend.MaxConn == nil { + backend.MaxConn = &types.MaxConn{ + Amount: defaultMaxConnAmount, + ExtractorFunc: defaultMaxConnExtractorFunc, + } + } + + } + } + return &sidecarConfig, nil } -func (provider *Sidecar) loadSidecarConfig(sidecarStates map[string][]*service.Service) error { +func (provider *Sidecar) loadSidecarConfig(sidecarStates *catalog.ServicesState) error { conf, err := provider.constructConfig(sidecarStates) if err != nil { return err } + provider.configurationChan <- types.ConfigMessage{ ProviderName: "sidecar", Configuration: conf, @@ -122,99 +170,123 @@ func (provider *Sidecar) loadSidecarConfig(sidecarStates map[string][]*service.S return nil } -func (provider *Sidecar) sidecarWatcher() error { - //set timeout to be just a bot more than connection refresh interval +func (provider *Sidecar) sidecarWatcher(stop chan bool, pool *safe.Pool) { + //set timeout to be just a bit more than connection refresh interval provider.connTimer = time.NewTimer(time.Duration(provider.RefreshConn)) - tr := &http.Transport{ResponseHeaderTimeout: 0} - client := &http.Client{ - Timeout: 0, - Transport: tr} + log.Debugf("Using %s Sidecar connection refresh interval", provider.RefreshConn) - provider.recycleConn(client, tr) - return nil + provider.recycleConn(stop, pool) } -func (provider *Sidecar) recycleConn(client *http.Client, tr *http.Transport) { +func (provider *Sidecar) recycleConn(stop chan bool, pool *safe.Pool) { var err error var resp *http.Response var req *http.Request - for { //use refresh interval to occasionally reconnect to Sidecar in case the stream connection is lost - req, err = http.NewRequest("GET", provider.Endpoint+"/watch", nil) - if err != nil { - log.Errorf("Error creating http request to Sidecar: %s, Error: %s", provider.Endpoint, err) - continue - } - resp, err = client.Do(req) - if err != nil { - log.Errorf("Error connecting to Sidecar: %s, Error: %s", provider.Endpoint, err) - time.Sleep(5 * time.Second) - continue - } - safe.Go(func() { catalog.DecodeStream(resp.Body, provider.callbackLoader) }) + for { + select { + case <-stop: + return + default: + //use refresh interval to occasionally reconnect to Sidecar in case the stream connection is lost + req, err = http.NewRequest("GET", provider.Endpoint+"/watch?by_service=false", nil) + if err != nil { + log.Errorf("Error creating http request to Sidecar: %s, Error: %s", provider.Endpoint, err) + continue + } + resp, err = watcherHTTPClient.Do(req) + if err != nil { + log.Errorf("Error connecting to Sidecar: %s, Error: %s", provider.Endpoint, err) + time.Sleep(5 * time.Second) + continue + } - //wait on refresh connection timer. If this expires we haven't seen an update in a - //while and should cancel the request, reset the time, and reconnect just in case - <-provider.connTimer.C - provider.connTimer.Reset(time.Duration(provider.RefreshConn)) - tr.CancelRequest(req) + safe.Go(func() { decodeStream(resp.Body, provider.callbackLoader) }) + + //wait on refresh connection timer. If this expires we haven't seen an update in a + //while and should cancel the request, reset the time, and reconnect just in case + <-provider.connTimer.C + provider.connTimer.Reset(time.Duration(provider.RefreshConn)) + + //TODO: Deprecated method. Refactor this to use a context. + watcherHTTPTransport.CancelRequest(req) + } } } -func (provider *Sidecar) callbackLoader(sidecarStates map[string][]*service.Service, err error) { +func (provider *Sidecar) callbackLoader(sidecarStates *catalog.ServicesState, err error) { //load config regardless - provider.loadSidecarConfig(sidecarStates) + errLoadConfig := provider.loadSidecarConfig(sidecarStates) + if errLoadConfig != nil { + log.Error("Error reloading config: %s", errLoadConfig) + } if err != nil { return } + //else reset connection timer if !provider.connTimer.Stop() { <-provider.connTimer.C } + provider.connTimer.Reset(time.Duration(provider.RefreshConn)) - return } -func (provider *Sidecar) makeFrontend() (map[string]*types.Frontend, error) { - configuration := new(types.Configuration) - if _, err := toml.DecodeFile(provider.Frontend, configuration); err != nil { +func (provider *Sidecar) makeFrontends() (map[string]*sidecarFrontend, error) { + configuration := new(sidecarConfig) + if _, err := toml.DecodeFile(provider.Filename, configuration); err != nil { log.Errorf("Error reading file: %s", err) return nil, err } + return configuration.Frontends, nil } -func (provider *Sidecar) makeBackends(sidecarStates map[string][]*service.Service) map[string]*types.Backend { +func (provider *Sidecar) makeBackends(sidecarStates *catalog.ServicesState) map[string]*types.Backend { sidecarBacks := make(map[string]*types.Backend) - for serviceName, services := range sidecarStates { - newServers := make(map[string]types.Server) - newBackend := &types.Backend{LoadBalancer: &types.LoadBalancer{Method: method, Sticky: sticky}, - Servers: newServers} - for _, serv := range services { - if serv.IsAlive() { - for i := 0; i < len(serv.Ports); i++ { - ipAddr, err := net.LookupIP(serv.Hostname) + + sidecarStates.EachService( + func(hostname *string, serviceId *string, svc *service.Service) { + var backend *types.Backend + var ok bool + if backend, ok = sidecarBacks[svc.Name]; !ok { + backend = &types.Backend{ + LoadBalancer: &types.LoadBalancer{Method: method, Sticky: sticky}, + Servers: make(map[string]types.Server), + } + + sidecarBacks[svc.Name] = backend + } + + if svc.IsAlive() { + for i := 0; i < len(svc.Ports); i++ { + ipAddr, err := net.LookupIP(svc.Hostname) if err != nil { log.Errorln("Error resolving Ip address, ", err) - newBackend.Servers[serv.Hostname] = types.Server{URL: "http://" + serv.Hostname + ":" + strconv.FormatInt(serv.Ports[i].Port, 10)} + backend.Servers[svc.Hostname] = types.Server{ + URL: "http://" + svc.Hostname + ":" + strconv.FormatInt(svc.Ports[i].Port, 10), + } } else { - newBackend.Servers[serv.Hostname] = types.Server{URL: "http://" + ipAddr[0].String() + ":" + strconv.FormatInt(serv.Ports[i].Port, 10)} + backend.Servers[svc.Hostname] = types.Server{ + URL: "http://" + ipAddr[0].String() + ":" + strconv.FormatInt(svc.Ports[i].Port, 10), + } } } } - } - sidecarBacks[serviceName] = newBackend - } + }, + ) + return sidecarBacks } func (provider *Sidecar) fetchState() (*catalog.ServicesState, error) { - client := &http.Client{Timeout: 5 * time.Second} - resp, err := client.Get(provider.Endpoint + "/state.json") + resp, err := sidecarHTTPClient.Get(provider.Endpoint + "/state.json") if err != nil { return nil, err } + defer resp.Body.Close() + bytes, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err @@ -226,3 +298,19 @@ func (provider *Sidecar) fetchState() (*catalog.ServicesState, error) { } return state, nil } + +func decodeStream(input io.Reader, callback func(*catalog.ServicesState, error)) error { + dec := json.NewDecoder(input) + for dec.More() { + var conf catalog.ServicesState + err := dec.Decode(&conf) + + callback(&conf, err) + + if err != nil { + log.Errorf("Error decoding stream: %s", err) + return err + } + } + return nil +} diff --git a/provider/sidecar_test.go b/provider/sidecar_test.go index 7c9619fbf5..5be32e1755 100644 --- a/provider/sidecar_test.go +++ b/provider/sidecar_test.go @@ -9,28 +9,49 @@ import ( "github.com/Nitro/sidecar/catalog" "github.com/Nitro/sidecar/service" + "github.com/containous/flaeg" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" "github.com/jarcoal/httpmock" . "github.com/smartystreets/goconvey/convey" ) -func Test_FetchState(t *testing.T) { - Convey("Verify Fetching State handler", t, func() { - var baseTime = time.Now().UTC().Round(time.Second) - var testPort = service.Port{Type: "tcp", Port: 8000, ServicePort: 8000} +func TestSidecar(t *testing.T) { + Convey("Sidecar should", t, func() { + // httpmock only works with the default HTTP client + origSidecarHTTPClient := sidecarHTTPClient + origWatcherHTTPClient := watcherHTTPClient + sidecarHTTPClient = http.DefaultClient + watcherHTTPClient = http.DefaultClient + + dummyState := catalog.NewServicesState() + dummyState.AddServiceEntry( + service.Service{ + ID: "007", + Name: "web", + Hostname: "some-aws-host", + Status: 0, + Ports: []service.Port{ + service.Port{ + Type: "tcp", + Port: 8000, + ServicePort: 8000, + }, + }, + }, + ) httpmock.Activate() - defer httpmock.DeactivateAndReset() - httpmock.RegisterResponder("GET", "http://some.dummy.service/state.json", + httpmock.RegisterResponder(http.MethodGet, "http://some.dummy.service", func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(200, ""), nil + }, + ) - service := service.Service{ID: "007", Name: "api", Hostname: "some-aws-host", - Updated: baseTime, Status: 1, Ports: []service.Port{testPort}} - returnState := catalog.NewServicesState() - returnState.AddServiceEntry(service) - resp, err := httpmock.NewJsonResponse(200, returnState) + httpmock.RegisterResponder(http.MethodGet, "http://some.dummy.service/state.json", + func(req *http.Request) (*http.Response, error) { + resp, err := httpmock.NewJsonResponse(200, dummyState) if err != nil { return httpmock.NewStringResponse(500, ""), nil } @@ -38,192 +59,225 @@ func Test_FetchState(t *testing.T) { }, ) - prov := Sidecar{ - Endpoint: "http://some.dummy.service", - } + Convey("fetch state", func() { + prov := Sidecar{ + Endpoint: "http://some.dummy.service", + } - testState, err := prov.fetchState() - testServices := testState.ByService() + receivedState, err := prov.fetchState() + So(err, ShouldBeNil) - compareState := catalog.NewServicesState() - service := &service.Service{ID: "007", Name: "api", Hostname: "some-aws-host", - Updated: baseTime, Status: 1, Ports: []service.Port{testPort}} - compareState.AddServiceEntry(*service) - compareServices := compareState.ByService() + receivedServices := receivedState.ByService() - So(err, ShouldBeNil) - So(reflect.DeepEqual(testServices["api"][0].Ports, compareServices["api"][0].Ports), ShouldBeTrue) - So(testServices["api"][0].Hostname, ShouldEqual, compareServices["api"][0].Hostname) + expectedServices := dummyState.ByService() - compareServices["api"][0].Hostname = "wrong-host" - So(testServices["api"][0].Hostname, ShouldNotEqual, compareServices["api"][0].Hostname) + So(reflect.DeepEqual(receivedServices["web"][0].Ports, expectedServices["web"][0].Ports), ShouldBeTrue) + So(receivedServices["web"][0].Hostname, ShouldEqual, expectedServices["web"][0].Hostname) - prov.Endpoint = "http://yetanother.dummy.service" - _, err = prov.fetchState() - So(err, ShouldNotBeNil) - }) -} + expectedServices["web"][0].Hostname = "wrong-host" + So(receivedServices["web"][0].Hostname, ShouldNotEqual, expectedServices["web"][0].Hostname) -func Test_FetchBackend(t *testing.T) { - Convey("Verify Fetching Backend", t, func() { - httpmock.Activate() - defer httpmock.DeactivateAndReset() + prov.Endpoint = "http://yetanother.dummy.service" + _, err = prov.fetchState() + So(err, ShouldNotBeNil) + }) - prov := Sidecar{ - Endpoint: "http://some.dummy.service", - } + Convey("create backends", func() { + prov := Sidecar{ + Endpoint: "http://some.dummy.service", + } - httpmock.RegisterResponder("GET", "http://some.dummy.service/state.json", - func(req *http.Request) (*http.Response, error) { + dummyState.AddServiceEntry( + service.Service{ + ID: "008", + Name: "api", + Hostname: "another-aws-host", + Status: 1, + }, + ) - testPort := service.Port{Type: "tcp", Port: 8000, ServicePort: 8000} - returnState := catalog.NewServicesState() - baseTime := time.Now().UTC().Round(time.Second) - serviceA := service.Service{ID: "007", Name: "web", Hostname: "some-aws-host", - Updated: baseTime.Add(5 * time.Second), Status: 0, Ports: []service.Port{testPort}} - serviceB := service.Service{ID: "008", Name: "api", Hostname: "another-aws-host", - Updated: baseTime, Status: 1} - returnState.AddServiceEntry(serviceA) - returnState.AddServiceEntry(serviceB) - resp, err := httpmock.NewJsonResponse(200, returnState) - if err != nil { - return httpmock.NewStringResponse(500, ""), nil - } - return resp, nil - }, - ) - states, err := prov.fetchState() - sidecarStates := states.ByService() - backs := prov.makeBackends(sidecarStates) - - So(err, ShouldBeNil) - So(backs["web"].LoadBalancer.Method, ShouldEqual, "wrr") - So(backs["web"].Servers["some-aws-host"].URL, ShouldEqual, "http://some-aws-host:8000") - So(backs["api"].Servers["another-aws-host"], ShouldBeZeroValue) - }) -} + states, err := prov.fetchState() + So(err, ShouldBeNil) -func Test_MakeFrontEnd(t *testing.T) { - Convey("Verify Sidecar Frontend Config Loader", t, func() { - httpmock.Activate() - defer httpmock.DeactivateAndReset() + backs := prov.makeBackends(states) - httpmock.RegisterResponder("GET", "http://some.dummy.service", - func(req *http.Request) (*http.Response, error) { + So(backs["web"].LoadBalancer.Method, ShouldEqual, "wrr") + So(backs["web"].Servers["some-aws-host"].URL, ShouldEqual, "http://some-aws-host:8000") + So(backs["api"].Servers["another-aws-host"], ShouldBeZeroValue) - returnState := catalog.NewServicesState() - resp, err := httpmock.NewJsonResponse(200, returnState) - if err != nil { - return httpmock.NewStringResponse(500, ""), nil - } - return resp, nil - }, - ) - prov := Sidecar{ - Endpoint: "http://some.dummy.service", - } - prov.Watch = true - prov.Frontend = "testdata/sidecar_testdata.toml" - conf, err := prov.makeFrontend() - So(err, ShouldEqual, nil) - So(conf["web"].PassHostHeader, ShouldEqual, true) - So(conf["web"].EntryPoints, ShouldResemble, []string{"http", "https"}) - So(conf["web"].Routes["test_1"].Rule, ShouldEqual, "Host: some-aws-host") - prov.Frontend = "testdata/dummyfile.toml" - _, err = prov.makeFrontend() - So(err, ShouldNotBeNil) - }) -} + So(backs["web"].MaxConn, ShouldBeNil) + So(backs["api"].MaxConn, ShouldBeNil) -func Test_SidecarProvider(t *testing.T) { - Convey("Verify Sidecar Provider", t, func() { - httpmock.Activate() - defer httpmock.DeactivateAndReset() + }) - httpmock.RegisterResponder("GET", "http://some.dummy.service/state.json", - func(req *http.Request) (*http.Response, error) { + Convey("construct config", func() { + prov := Sidecar{ + BaseProvider: BaseProvider{ + Filename: "testdata/sidecar_testdata.toml", + }, + Endpoint: "http://some.dummy.service", + } - testPort := service.Port{Type: "tcp", Port: 8000, ServicePort: 8000} - returnState := catalog.NewServicesState() - baseTime := time.Now().UTC().Round(time.Second) - serv := service.Service{ID: "007", Name: "web", Hostname: "some-aws-host", - Updated: baseTime.Add(5 * time.Second), Status: 0, Ports: []service.Port{testPort}} - returnState.AddServiceEntry(serv) - resp, err := httpmock.NewJsonResponse(200, returnState) - if err != nil { - return httpmock.NewStringResponse(500, ""), nil - } - return resp, nil - }, - ) - prov := Sidecar{ - Endpoint: "http://some.dummy.service", - } - prov.Watch = false - prov.Frontend = "testdata/sidecar_testdata.toml" - - configurationChan := make(chan types.ConfigMessage, 1) - constraints := types.Constraints{} - pool := safe.NewPool(context.Background()) - err := prov.Provide(configurationChan, pool, constraints) - configMsg, _ := <-configurationChan - So(err, ShouldBeNil) - So(configMsg.ProviderName, ShouldEqual, "sidecar") - So(configMsg.Configuration.Frontends["web"].Routes["test_1"].Rule, ShouldEqual, "Host: some-aws-host") - So(configMsg.Configuration.Backends["web"].Servers["some-aws-host"].URL, ShouldEndWith, "http://some-aws-host:8000") - }) -} + dummyState.AddServiceEntry( + service.Service{ + ID: "008", + Name: "api", + Hostname: "another-aws-host", + Status: 1, + }, + ) -func Test_SidecarWatcher(t *testing.T) { - Convey("Verify Sidecar Provider", t, func() { - httpmock.Activate() - defer httpmock.DeactivateAndReset() + dummyState.AddServiceEntry( + service.Service{ + ID: "009", + Name: "sso", + Hostname: "yet-another-aws-host", + Status: 1, + }, + ) - httpmock.RegisterResponder("GET", "http://some.dummy.service/state.json", - func(req *http.Request) (*http.Response, error) { + states, err := prov.fetchState() + So(err, ShouldBeNil) - testPort := service.Port{Type: "tcp", Port: 9000, ServicePort: 9000} - returnState := catalog.NewServicesState() - baseTime := time.Now().UTC().Round(time.Second) - serv := service.Service{ID: "007", Name: "web", Hostname: "some-aws-host", - Updated: baseTime.Add(5 * time.Second), Status: 0, Ports: []service.Port{testPort}} - returnState.AddServiceEntry(serv) - resp, err := httpmock.NewJsonResponse(200, returnState) - if err != nil { - return httpmock.NewStringResponse(500, ""), nil - } - return resp, nil - }, - ) + config, err := prov.constructConfig(states) + So(err, ShouldBeNil) - httpmock.RegisterResponder("GET", "http://some.dummy.service/watch", - func(req *http.Request) (*http.Response, error) { + So(config.Frontends, ShouldContainKey, "web") + So(config.Frontends, ShouldContainKey, "api") + So(config.Frontends, ShouldNotContainKey, "sso") - testPort := service.Port{Type: "tcp", Port: 9000, ServicePort: 9000} - returnState := catalog.NewServicesState() - baseTime := time.Now().UTC().Round(time.Second) - serv := service.Service{ID: "007", Name: "web", Hostname: "some-aws-host", - Updated: baseTime.Add(5 * time.Second), Status: 0, Ports: []service.Port{testPort}} - returnState.AddServiceEntry(serv) - resp, err := httpmock.NewJsonResponse(200, returnState.ByService()) - if err != nil { - return httpmock.NewStringResponse(500, ""), nil - } - return resp, nil - }, - ) + So(config.Backends, ShouldContainKey, "web") + So(config.Backends, ShouldContainKey, "api") + So(config.Backends, ShouldContainKey, "sso") + + So(config.Backends["web"].MaxConn.Amount, ShouldEqual, 10) + So(config.Backends["web"].MaxConn.ExtractorFunc, ShouldEqual, "client.ip") + So(config.Backends["api"].MaxConn.Amount, ShouldEqual, defaultMaxConnAmount) + So(config.Backends["api"].MaxConn.ExtractorFunc, ShouldEqual, defaultMaxConnExtractorFunc) + So(config.Backends["sso"].MaxConn, ShouldBeNil) + }) + + Convey("run Provide", func() { + prov := Sidecar{ + BaseProvider: BaseProvider{ + Watch: false, + Filename: "testdata/sidecar_testdata.toml", + }, + Endpoint: "http://some.dummy.service", + } + + configurationChan := make(chan types.ConfigMessage, 1) + err := prov.Provide(configurationChan, nil, nil) + configMsg := <-configurationChan + + So(err, ShouldBeNil) + So(configMsg.ProviderName, ShouldEqual, "sidecar") + So(configMsg.Configuration.Frontends["web"].Routes["test_1"].Rule, ShouldEqual, "Host: some-aws-host") + So(configMsg.Configuration.Backends["web"].Servers["some-aws-host"].URL, ShouldEqual, "http://some-aws-host:8000") + }) + + Convey("run Provide() in watcher mode", func(c C) { + // Return the watcher mock HTTP response on demand + releaseWatch := make(chan bool) + httpmock.RegisterResponder(http.MethodGet, "http://some.dummy.service/watch", + func(req *http.Request) (*http.Response, error) { + <-releaseWatch + + resp, err := httpmock.NewJsonResponse(200, dummyState) + if err != nil { + return httpmock.NewStringResponse(500, ""), nil + } + return resp, nil + }, + ) + + prov := Sidecar{ + BaseProvider: BaseProvider{ + Watch: true, + Filename: "testdata/sidecar_testdata.toml", + }, + Endpoint: "http://some.dummy.service", + RefreshConn: flaeg.Duration(100 * time.Millisecond), + } + + configMsgChan := make(chan types.ConfigMessage) + pool := safe.NewPool(context.Background()) + defer pool.Cleanup() + go func() { + err := prov.Provide( + configMsgChan, + pool, + nil, + ) + + c.So(err, ShouldBeNil) + }() + + // Catch the loadSidecarConfig from the end of Provide() + configMsg := <-configMsgChan + + // Unblock the first call to recycleConn() + releaseWatch <- true + configMsg = <-configMsgChan + + So(configMsg.ProviderName, ShouldEqual, "sidecar") + So(configMsg.Configuration.Frontends["web"].Routes["test_1"].Rule, ShouldEqual, "Host: some-aws-host") + So(configMsg.Configuration.Backends["web"].Servers["some-aws-host"].URL, ShouldEqual, "http://some-aws-host:8000") + + dummyState.AddServiceEntry( + service.Service{ + ID: "009", + Name: "api", + Hostname: "another-aws-host", + Status: 0, + Ports: []service.Port{ + service.Port{ + Type: "tcp", + Port: 9000, + ServicePort: 9000, + }, + }, + }, + ) + + // Unblock the rest of the calls to recycleConn() to receive the updated config + close(releaseWatch) + configMsg = <-configMsgChan + + So(configMsg.Configuration.Backends, ShouldContainKey, "api") + So(configMsg.Configuration.Backends["api"].Servers["another-aws-host"].URL, ShouldEqual, "http://another-aws-host:9000") + + }) + + Reset(func() { + httpmock.DeactivateAndReset() + + sidecarHTTPClient = origSidecarHTTPClient + watcherHTTPClient = origWatcherHTTPClient + }) + }) +} + +func TestSidecarMakeFrontend(t *testing.T) { + Convey("Verify Sidecar Frontend config loading", t, func() { prov := Sidecar{ + BaseProvider: BaseProvider{ + Watch: false, + Filename: "testdata/sidecar_testdata.toml", + }, Endpoint: "http://some.dummy.service", } - prov.Watch = true - prov.Frontend = "testdata/sidecar_testdata.toml" - configurationChan := make(chan types.ConfigMessage, 100) - constraints := types.Constraints{} - pool := safe.NewPool(context.Background()) - go prov.Provide(configurationChan, pool, constraints) - configMsg, _ := <-configurationChan - So(configMsg.ProviderName, ShouldEqual, "sidecar") - So(configMsg.Configuration.Frontends["web"].Routes["test_1"].Rule, ShouldEqual, "Host: some-aws-host") - So(configMsg.Configuration.Backends["web"].Servers["some-aws-host"].URL, ShouldEndWith, "http://some-aws-host:9000") + + frontends, err := prov.makeFrontends() + So(err, ShouldEqual, nil) + So(frontends, ShouldContainKey, "web") + So(frontends, ShouldContainKey, "api") + So(frontends["web"].PassHostHeader, ShouldEqual, true) + So(frontends["web"].EntryPoints, ShouldResemble, []string{"http", "https"}) + So(frontends["web"].Routes["test_1"].Rule, ShouldEqual, "Host: some-aws-host") + + prov.Filename = "testdata/dummyfile.toml" + _, err = prov.makeFrontends() + So(err, ShouldNotBeNil) }) } diff --git a/provider/testdata/sidecar_testdata.toml b/provider/testdata/sidecar_testdata.toml index b74b477d74..aae8fd2c2e 100644 --- a/provider/testdata/sidecar_testdata.toml +++ b/provider/testdata/sidecar_testdata.toml @@ -1,7 +1,13 @@ [frontends] [frontends.web] - backend = "web" - passHostHeader = true - entrypoints = ["http","https"] + backend = "web" + passHostHeader = true + entrypoints = ["http","https"] [frontends.web.routes.test_1] - rule = "Host: some-aws-host" + rule = "Host: some-aws-host" + [frontends.web.maxconn] + amount = 10 + extractorfunc = "client.ip" + [frontends.api] + backend = "api" + passHostHeader = true diff --git a/vendor/github.com/Microsoft/go-winio/file.go b/vendor/github.com/Microsoft/go-winio/file.go index 8c15e4124c..231863c811 100644 --- a/vendor/github.com/Microsoft/go-winio/file.go +++ b/vendor/github.com/Microsoft/go-winio/file.go @@ -219,3 +219,7 @@ func (f *win32File) SetWriteDeadline(t time.Time) error { f.writeDeadline = t return nil } + +func (f *win32File) Flush() error { + return syscall.FlushFileBuffers(f.handle) +} diff --git a/vendor/github.com/Microsoft/go-winio/pipe.go b/vendor/github.com/Microsoft/go-winio/pipe.go index b85b2eef46..e1b02580d0 100644 --- a/vendor/github.com/Microsoft/go-winio/pipe.go +++ b/vendor/github.com/Microsoft/go-winio/pipe.go @@ -87,7 +87,11 @@ func (f *win32MessageBytePipe) CloseWrite() error { if f.writeClosed { return errPipeWriteClosed } - _, err := f.win32File.Write(nil) + err := f.win32File.Flush() + if err != nil { + return err + } + _, err = f.win32File.Write(nil) if err != nil { return err } diff --git a/vendor/github.com/codegangsta/negroni/negroni.go b/vendor/github.com/codegangsta/negroni/negroni.go index 7a00b38f69..d81a5fdf3a 100644 --- a/vendor/github.com/codegangsta/negroni/negroni.go +++ b/vendor/github.com/codegangsta/negroni/negroni.go @@ -6,6 +6,11 @@ import ( "os" ) +const ( + // DefaultAddress is used if no other is specified. + DefaultAddress = ":8080" +) + // Handler handler is an interface that objects can implement to be registered to serve as middleware // in the Negroni middleware stack. // ServeHTTP should yield to the next middleware in the chain by invoking the next http.HandlerFunc @@ -107,11 +112,23 @@ func (n *Negroni) UseHandlerFunc(handlerFunc func(rw http.ResponseWriter, r *htt } // Run is a convenience function that runs the negroni stack as an HTTP -// server. The addr string takes the same format as http.ListenAndServe. -func (n *Negroni) Run(addr string) { +// server. The addr string, if provided, takes the same format as http.ListenAndServe. +// If no address is provided but the PORT environment variable is set, the PORT value is used. +// If neither is provided, the address' value will equal the DefaultAddress constant. +func (n *Negroni) Run(addr ...string) { l := log.New(os.Stdout, "[negroni] ", 0) l.Printf("listening on %s", addr) - l.Fatal(http.ListenAndServe(addr, n)) + l.Fatal(http.ListenAndServe(detectAddress(addr...), n)) +} + +func detectAddress(addr ...string) string { + if len(addr) > 0 { + return addr[0] + } + if port := os.Getenv("PORT"); port != "" { + return ":" + port + } + return DefaultAddress } // Returns a list of all the handlers in the current Negroni middleware chain. diff --git a/vendor/github.com/google/go-github/github/doc.go b/vendor/github.com/google/go-github/github/doc.go index 0acf3281ff..3821c99ef9 100644 --- a/vendor/github.com/google/go-github/github/doc.go +++ b/vendor/github.com/google/go-github/github/doc.go @@ -71,11 +71,10 @@ limited to 60 requests per hour, while authenticated clients can make up to that are not issued on behalf of a user, use the UnauthenticatedRateLimitedTransport. -The Rate method on a client returns the rate limit information based on the most -recent API call. This is updated on every call, but may be out of date if it's -been some time since the last API call and other clients have made subsequent -requests since then. You can always call RateLimits() directly to get the most -up-to-date rate limit data for the client. +The returned Response.Rate value contains the rate limit information +from the most recent API call. If a recent enough response isn't +available, you can use RateLimits to fetch the most up-to-date rate +limit data for the client. To detect an API rate limit error, you can check if its type is *github.RateLimitError: diff --git a/vendor/github.com/google/go-github/github/git_refs.go b/vendor/github.com/google/go-github/github/git_refs.go index bd5df3f72a..e78fdc6c09 100644 --- a/vendor/github.com/google/go-github/github/git_refs.go +++ b/vendor/github.com/google/go-github/github/git_refs.go @@ -7,6 +7,8 @@ package github import ( "context" + "encoding/json" + "errors" "fmt" "strings" ) @@ -45,7 +47,11 @@ type updateRefRequest struct { Force *bool `json:"force"` } -// GetRef fetches the Reference object for a given Git ref. +// GetRef fetches a single Reference object for a given Git ref. +// If there is no exact match, GetRef will return an error. +// +// Note: The GitHub API can return multiple matches. +// If you wish to use this functionality please use the GetRefs() method. // // GitHub API docs: https://developer.github.com/v3/git/refs/#get-a-reference func (s *GitService) GetRef(ctx context.Context, owner string, repo string, ref string) (*Reference, *Response, error) { @@ -58,13 +64,61 @@ func (s *GitService) GetRef(ctx context.Context, owner string, repo string, ref r := new(Reference) resp, err := s.client.Do(ctx, req, r) - if err != nil { + if _, ok := err.(*json.UnmarshalTypeError); ok { + // Multiple refs, means there wasn't an exact match. + return nil, resp, errors.New("no exact match found for this ref") + } else if err != nil { return nil, resp, err } return r, resp, nil } +// GetRefs fetches a slice of Reference objects for a given Git ref. +// If there is an exact match, only that ref is returned. +// If there is no exact match, GitHub returns all refs that start with ref. +// If returned error is nil, there will be at least 1 ref returned. +// For example: +// +// "heads/featureA" -> ["refs/heads/featureA"] // Exact match, single ref is returned. +// "heads/feature" -> ["refs/heads/featureA", "refs/heads/featureB"] // All refs that start with ref. +// "heads/notexist" -> [] // Returns an error. +// +// GitHub API docs: https://developer.github.com/v3/git/refs/#get-a-reference +func (s *GitService) GetRefs(ctx context.Context, owner string, repo string, ref string) ([]*Reference, *Response, error) { + ref = strings.TrimPrefix(ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var rawJSON json.RawMessage + resp, err := s.client.Do(ctx, req, &rawJSON) + if err != nil { + return nil, resp, err + } + + // Prioritize the most common case: a single returned ref. + r := new(Reference) + singleUnmarshalError := json.Unmarshal(rawJSON, r) + if singleUnmarshalError == nil { + return []*Reference{r}, resp, nil + } + + // Attempt to unmarshal multiple refs. + var rs []*Reference + multipleUnmarshalError := json.Unmarshal(rawJSON, &rs) + if multipleUnmarshalError == nil { + if len(rs) == 0 { + return nil, resp, fmt.Errorf("unexpected response from GitHub API: an array of refs with length 0") + } + return rs, resp, nil + } + + return nil, resp, fmt.Errorf("unmarshalling failed for both single and multiple refs: %s and %s", singleUnmarshalError, multipleUnmarshalError) +} + // ReferenceListOptions specifies optional parameters to the // GitService.ListRefs method. type ReferenceListOptions struct { diff --git a/vendor/github.com/google/go-github/github/github-accessors.go b/vendor/github.com/google/go-github/github/github-accessors.go index c74c025484..794f5e2fde 100644 --- a/vendor/github.com/google/go-github/github/github-accessors.go +++ b/vendor/github.com/google/go-github/github/github-accessors.go @@ -20,6 +20,14 @@ func (a *AbuseRateLimitError) GetRetryAfter() time.Duration { return *a.RetryAfter } +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (a *AdminEnforcement) GetURL() string { + if a == nil || a.URL == nil { + return "" + } + return *a.URL +} + // GetVerifiablePasswordAuthentication returns the VerifiablePasswordAuthentication field if it's non-nil, zero value otherwise. func (a *APIMeta) GetVerifiablePasswordAuthentication() bool { if a == nil || a.VerifiablePasswordAuthentication == nil { diff --git a/vendor/github.com/google/go-github/github/github.go b/vendor/github.com/google/go-github/github/github.go index decdaaae67..b1742e4dcd 100644 --- a/vendor/github.com/google/go-github/github/github.go +++ b/vendor/github.com/google/go-github/github/github.go @@ -396,7 +396,10 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res // If we've hit rate limit, don't make further requests before Reset time. if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil { - return nil, err + return &Response{ + Response: err.Response, + Rate: err.Rate, + }, err } resp, err := c.client.Do(req) @@ -457,7 +460,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res // current client state in order to quickly check if *RateLimitError can be immediately returned // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily. // Otherwise it returns nil, and Client.Do should proceed normally. -func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) error { +func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError { c.rateMu.Lock() rate := c.rateLimits[rateLimitCategory] c.rateMu.Unlock() diff --git a/vendor/github.com/google/go-github/github/messages.go b/vendor/github.com/google/go-github/github/messages.go index a7ec65fba2..f8baffebcf 100644 --- a/vendor/github.com/google/go-github/github/messages.go +++ b/vendor/github.com/google/go-github/github/messages.go @@ -33,6 +33,8 @@ const ( signatureHeader = "X-Hub-Signature" // eventTypeHeader is the GitHub header key used to pass the event type. eventTypeHeader = "X-Github-Event" + // deliveryIDHeader is the GitHub header key used to pass the unique ID for the webhook event. + deliveryIDHeader = "X-Github-Delivery" ) var ( @@ -159,10 +161,19 @@ func validateSignature(signature string, payload, secretKey []byte) error { } // WebHookType returns the event type of webhook request r. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#webhook-headers func WebHookType(r *http.Request) string { return r.Header.Get(eventTypeHeader) } +// DeliveryID returns the unique delivery ID of webhook request r. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#webhook-headers +func DeliveryID(r *http.Request) string { + return r.Header.Get(deliveryIDHeader) +} + // ParseWebHook parses the event payload. For recognized event types, a // value of the corresponding struct type will be returned (as returned // by Event.ParsePayload()). An error will be returned for unrecognized event diff --git a/vendor/github.com/google/go-github/github/orgs_users_blocking.go b/vendor/github.com/google/go-github/github/orgs_users_blocking.go new file mode 100644 index 0000000000..b1aecf4453 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_users_blocking.go @@ -0,0 +1,91 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListBlockedUsers lists all the users blocked by an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/blocking/#list-blocked-users +func (s *OrganizationsService) ListBlockedUsers(ctx context.Context, org string, opt *ListOptions) ([]*User, *Response, error) { + u := fmt.Sprintf("orgs/%v/blocks", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + var blockedUsers []*User + resp, err := s.client.Do(ctx, req, &blockedUsers) + if err != nil { + return nil, resp, err + } + + return blockedUsers, resp, nil +} + +// IsBlocked reports whether specified user is blocked from an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/blocking/#check-whether-a-user-is-blocked-from-an-organization +func (s *OrganizationsService) IsBlocked(ctx context.Context, org string, user string) (bool, *Response, error) { + u := fmt.Sprintf("orgs/%v/blocks/%v", org, user) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + resp, err := s.client.Do(ctx, req, nil) + isBlocked, err := parseBoolResponse(err) + return isBlocked, resp, err +} + +// BlockUser blocks specified user from an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/blocking/#block-a-user +func (s *OrganizationsService) BlockUser(ctx context.Context, org string, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/blocks/%v", org, user) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + return s.client.Do(ctx, req, nil) +} + +// UnblockUser unblocks specified user from an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/blocking/#unblock-a-user +func (s *OrganizationsService) UnblockUser(ctx context.Context, org string, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/blocks/%v", org, user) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/repos.go b/vendor/github.com/google/go-github/github/repos.go index 058f149dda..41b72e3dc8 100644 --- a/vendor/github.com/google/go-github/github/repos.go +++ b/vendor/github.com/google/go-github/github/repos.go @@ -511,6 +511,7 @@ type Branch struct { type Protection struct { RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks"` RequiredPullRequestReviews *RequiredPullRequestReviews `json:"required_pull_request_reviews"` + EnforceAdmins *AdminEnforcement `json:"enforce_admins"` Restrictions *BranchRestrictions `json:"restrictions"` } @@ -518,12 +519,14 @@ type Protection struct { type ProtectionRequest struct { RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks"` RequiredPullRequestReviews *RequiredPullRequestReviews `json:"required_pull_request_reviews"` + EnforceAdmins bool `json:"enforce_admins"` Restrictions *BranchRestrictionsRequest `json:"restrictions"` } // RequiredStatusChecks represents the protection status of a individual branch. type RequiredStatusChecks struct { // Enforce required status checks for repository administrators. (Required.) + // Deprecated: Use EnforceAdmins instead. IncludeAdmins bool `json:"include_admins"` // Require branches to be up to date before merging. (Required.) Strict bool `json:"strict"` @@ -535,9 +538,16 @@ type RequiredStatusChecks struct { // RequiredPullRequestReviews represents the protection configuration for pull requests. type RequiredPullRequestReviews struct { // Enforce pull request reviews for repository administrators. (Required.) + // Deprecated: Use EnforceAdmins instead. IncludeAdmins bool `json:"include_admins"` } +// AdminEnforcement represents the configuration to enforce required status checks for repository administrators. +type AdminEnforcement struct { + URL *string `json:"url,omitempty"` + Enabled bool `json:"enabled"` +} + // BranchRestrictions represents the restriction that only certain users or // teams may push to a branch. type BranchRestrictions struct { diff --git a/vendor/github.com/google/go-github/github/repos_contents.go b/vendor/github.com/google/go-github/github/repos_contents.go index dfcbe33d2b..f9adaf7a03 100644 --- a/vendor/github.com/google/go-github/github/repos_contents.go +++ b/vendor/github.com/google/go-github/github/repos_contents.go @@ -164,7 +164,7 @@ func (s *RepositoriesService) GetContents(ctx context.Context, owner, repo, path if directoryUnmarshalError == nil { return nil, directoryContent, resp, nil } - return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s ", fileUnmarshalError, directoryUnmarshalError) + return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s", fileUnmarshalError, directoryUnmarshalError) } // CreateFile creates a new file in a repository at the given path and returns diff --git a/vendor/github.com/hashicorp/consul/api/acl.go b/vendor/github.com/hashicorp/consul/api/acl.go index c3fb0d53aa..15d1f9f5aa 100644 --- a/vendor/github.com/hashicorp/consul/api/acl.go +++ b/vendor/github.com/hashicorp/consul/api/acl.go @@ -1,5 +1,9 @@ package api +import ( + "time" +) + const ( // ACLCLientType is the client type token ACLClientType = "client" @@ -18,6 +22,16 @@ type ACLEntry struct { Rules string } +// ACLReplicationStatus is used to represent the status of ACL replication. +type ACLReplicationStatus struct { + Enabled bool + Running bool + SourceDatacenter string + ReplicatedIndex uint64 + LastSuccess time.Time + LastError time.Time +} + // ACL can be used to query the ACL endpoints type ACL struct { c *Client @@ -138,3 +152,24 @@ func (a *ACL) List(q *QueryOptions) ([]*ACLEntry, *QueryMeta, error) { } 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 +} diff --git a/vendor/github.com/hashicorp/consul/api/agent.go b/vendor/github.com/hashicorp/consul/api/agent.go index 1893d1cf35..35c0676b51 100644 --- a/vendor/github.com/hashicorp/consul/api/agent.go +++ b/vendor/github.com/hashicorp/consul/api/agent.go @@ -25,6 +25,8 @@ type AgentService struct { Port int Address string EnableTagOverride bool + CreateIndex uint64 + ModifyIndex uint64 } // AgentMember represents a cluster member known to the agent diff --git a/vendor/github.com/hashicorp/consul/api/api.go b/vendor/github.com/hashicorp/consul/api/api.go index f6fe5a1bde..2225dd3bf3 100644 --- a/vendor/github.com/hashicorp/consul/api/api.go +++ b/vendor/github.com/hashicorp/consul/api/api.go @@ -4,11 +4,9 @@ import ( "bytes" "context" "crypto/tls" - "crypto/x509" "encoding/json" "fmt" "io" - "io/ioutil" "log" "net" "net/http" @@ -19,6 +17,7 @@ import ( "time" "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/go-rootcerts" ) const ( @@ -38,6 +37,26 @@ const ( // 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" @@ -149,6 +168,9 @@ type Config struct { // 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 @@ -163,6 +185,8 @@ type Config struct { // 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 @@ -177,6 +201,10 @@ type TLSConfig struct { // 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 @@ -212,11 +240,9 @@ func DefaultNonPooledConfig() *Config { // given function to make the transport. func defaultConfig(transportFn func() *http.Transport) *Config { config := &Config{ - Address: "127.0.0.1:8500", - Scheme: "http", - HttpClient: &http.Client{ - Transport: transportFn(), - }, + Address: "127.0.0.1:8500", + Scheme: "http", + Transport: transportFn(), } if addr := os.Getenv(HTTPAddrEnvName); addr != "" { @@ -254,27 +280,28 @@ func defaultConfig(transportFn func() *http.Transport) *Config { } } - if verify := os.Getenv(HTTPSSLVerifyEnvName); verify != "" { - doVerify, err := strconv.ParseBool(verify) + 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 { - tlsClientConfig, err := SetupTLSConfig(&TLSConfig{ - InsecureSkipVerify: true, - }) - - // We don't expect this to fail given that we aren't - // parsing any of the input, but we panic just in case - // since this doesn't have an error return. - if err != nil { - panic(err) - } - - transport := transportFn() - transport.TLSClientConfig = tlsClientConfig - config.HttpClient.Transport = transport + config.TLSConfig.InsecureSkipVerify = true } } @@ -309,17 +336,12 @@ func SetupTLSConfig(tlsConfig *TLSConfig) (*tls.Config, error) { tlsClientConfig.Certificates = []tls.Certificate{tlsCert} } - if tlsConfig.CAFile != "" { - data, err := ioutil.ReadFile(tlsConfig.CAFile) - if err != nil { - return nil, fmt.Errorf("failed to read CA file: %v", err) - } - - caPool := x509.NewCertPool() - if !caPool.AppendCertsFromPEM(data) { - return nil, fmt.Errorf("failed to parse CA certificate") - } - tlsClientConfig.RootCAs = caPool + rootConfig := &rootcerts.Config{ + CAFile: tlsConfig.CAFile, + CAPath: tlsConfig.CAPath, + } + if err := rootcerts.ConfigureTLS(tlsClientConfig, rootConfig); err != nil { + return nil, err } return tlsClientConfig, nil @@ -343,10 +365,46 @@ func NewClient(config *Config) (*Client, error) { config.Scheme = defConfig.Scheme } + if config.Transport == nil { + config.Transport = defConfig.Transport + } + if config.HttpClient == nil { config.HttpClient = defConfig.HttpClient } + 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] { @@ -373,6 +431,23 @@ func NewClient(config *Config) (*Client, error) { return client, nil } +// NewHttpClient returns an http client configured with the given Transport and TLS +// config. +func NewHttpClient(transport *http.Transport, tlsConf TLSConfig) (*http.Client, error) { + tlsClientConfig, err := SetupTLSConfig(&tlsConf) + + if err != nil { + return nil, err + } + + transport.TLSClientConfig = tlsClientConfig + client := &http.Client{ + Transport: transport, + } + + return client, nil +} + // request is used to help build up a request type request struct { config *Config @@ -472,11 +547,11 @@ func (r *request) toHTTP() (*http.Request, error) { // Check if we should encode the body if r.body == nil && r.obj != nil { - if b, err := encodeBody(r.obj); err != nil { + b, err := encodeBody(r.obj) + if err != nil { return nil, err - } else { - r.body = b } + r.body = b } // Create the HTTP request diff --git a/vendor/github.com/hashicorp/consul/api/catalog.go b/vendor/github.com/hashicorp/consul/api/catalog.go index 96226f11f8..babfc9a1df 100644 --- a/vendor/github.com/hashicorp/consul/api/catalog.go +++ b/vendor/github.com/hashicorp/consul/api/catalog.go @@ -4,14 +4,18 @@ 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 diff --git a/vendor/github.com/hashicorp/consul/api/health.go b/vendor/github.com/hashicorp/consul/api/health.go index 8abe2393ad..38c105fdb9 100644 --- a/vendor/github.com/hashicorp/consul/api/health.go +++ b/vendor/github.com/hashicorp/consul/api/health.go @@ -33,6 +33,7 @@ type HealthCheck struct { Output string ServiceID string ServiceName string + ServiceTags []string } // HealthChecks is a collection of HealthCheck structs. diff --git a/vendor/github.com/hashicorp/consul/api/kv.go b/vendor/github.com/hashicorp/consul/api/kv.go index 44e06bbb47..f91bb50fce 100644 --- a/vendor/github.com/hashicorp/consul/api/kv.go +++ b/vendor/github.com/hashicorp/consul/api/kv.go @@ -49,17 +49,18 @@ type KVPairs []*KVPair 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" + 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. diff --git a/vendor/github.com/hashicorp/consul/api/lock.go b/vendor/github.com/hashicorp/consul/api/lock.go index 9f9845a432..466ef5fdf1 100644 --- a/vendor/github.com/hashicorp/consul/api/lock.go +++ b/vendor/github.com/hashicorp/consul/api/lock.go @@ -143,22 +143,23 @@ func (l *Lock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) { // Check if we need to create a session first l.lockSession = l.opts.Session if l.lockSession == "" { - if s, err := l.createSession(); err != nil { + s, err := l.createSession() + if err != nil { return nil, fmt.Errorf("failed to create session: %v", err) - } else { - 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 - } - }() } + + 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 diff --git a/vendor/github.com/hashicorp/consul/api/semaphore.go b/vendor/github.com/hashicorp/consul/api/semaphore.go index e6645ac1d3..9ddbdc49e7 100644 --- a/vendor/github.com/hashicorp/consul/api/semaphore.go +++ b/vendor/github.com/hashicorp/consul/api/semaphore.go @@ -155,22 +155,23 @@ func (s *Semaphore) Acquire(stopCh <-chan struct{}) (<-chan struct{}, error) { // Check if we need to create a session first s.lockSession = s.opts.Session if s.lockSession == "" { - if sess, err := s.createSession(); err != nil { + sess, err := s.createSession() + if err != nil { return nil, fmt.Errorf("failed to create session: %v", err) - } else { - 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 - } - }() } + + 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 diff --git a/vendor/github.com/hashicorp/consul/commands.go b/vendor/github.com/hashicorp/consul/commands.go index 99406274a8..e8d3c4f608 100644 --- a/vendor/github.com/hashicorp/consul/commands.go +++ b/vendor/github.com/hashicorp/consul/commands.go @@ -23,7 +23,7 @@ func init() { return &agent.Command{ Command: base.Command{ Flags: base.FlagSetNone, - Ui: ui, + UI: ui, }, Revision: version.GitCommit, Version: version.Version, @@ -37,7 +37,7 @@ func init() { return &command.ConfigTestCommand{ Command: base.Command{ Flags: base.FlagSetNone, - Ui: ui, + UI: ui, }, }, nil }, @@ -46,7 +46,7 @@ func init() { return &command.EventCommand{ Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -56,7 +56,7 @@ func init() { ShutdownCh: makeShutdownCh(), Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -65,7 +65,7 @@ func init() { return &command.ForceLeaveCommand{ Command: base.Command{ Flags: base.FlagSetClientHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -73,7 +73,7 @@ func init() { "info": func() (cli.Command, error) { return &command.InfoCommand{ Command: base.Command{ - Ui: ui, + UI: ui, Flags: base.FlagSetClientHTTP, }, }, nil @@ -82,7 +82,7 @@ func init() { "join": func() (cli.Command, error) { return &command.JoinCommand{ Command: base.Command{ - Ui: ui, + UI: ui, Flags: base.FlagSetClientHTTP, }, }, nil @@ -91,7 +91,7 @@ func init() { "keygen": func() (cli.Command, error) { return &command.KeygenCommand{ Command: base.Command{ - Ui: ui, + UI: ui, Flags: base.FlagSetNone, }, }, nil @@ -100,7 +100,7 @@ func init() { "keyring": func() (cli.Command, error) { return &command.KeyringCommand{ Command: base.Command{ - Ui: ui, + UI: ui, Flags: base.FlagSetClientHTTP, }, }, nil @@ -109,7 +109,7 @@ func init() { "kv": func() (cli.Command, error) { return &command.KVCommand{ Command: base.Command{ - Ui: ui, + UI: ui, Flags: base.FlagSetNone, }, }, nil @@ -118,7 +118,7 @@ func init() { "kv delete": func() (cli.Command, error) { return &command.KVDeleteCommand{ Command: base.Command{ - Ui: ui, + UI: ui, Flags: base.FlagSetHTTP, }, }, nil @@ -127,7 +127,7 @@ func init() { "kv get": func() (cli.Command, error) { return &command.KVGetCommand{ Command: base.Command{ - Ui: ui, + UI: ui, Flags: base.FlagSetHTTP, }, }, nil @@ -136,7 +136,7 @@ func init() { "kv put": func() (cli.Command, error) { return &command.KVPutCommand{ Command: base.Command{ - Ui: ui, + UI: ui, Flags: base.FlagSetHTTP, }, }, nil @@ -145,7 +145,7 @@ func init() { "kv export": func() (cli.Command, error) { return &command.KVExportCommand{ Command: base.Command{ - Ui: ui, + UI: ui, Flags: base.FlagSetHTTP, }, }, nil @@ -154,7 +154,7 @@ func init() { "kv import": func() (cli.Command, error) { return &command.KVImportCommand{ Command: base.Command{ - Ui: ui, + UI: ui, Flags: base.FlagSetHTTP, }, }, nil @@ -164,7 +164,7 @@ func init() { return &command.LeaveCommand{ Command: base.Command{ Flags: base.FlagSetClientHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -174,7 +174,7 @@ func init() { ShutdownCh: makeShutdownCh(), Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -183,7 +183,7 @@ func init() { return &command.MaintCommand{ Command: base.Command{ Flags: base.FlagSetClientHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -192,7 +192,7 @@ func init() { return &command.MembersCommand{ Command: base.Command{ Flags: base.FlagSetClientHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -202,7 +202,7 @@ func init() { ShutdownCh: makeShutdownCh(), Command: base.Command{ Flags: base.FlagSetClientHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -211,7 +211,7 @@ func init() { return &command.OperatorCommand{ Command: base.Command{ Flags: base.FlagSetNone, - Ui: ui, + UI: ui, }, }, nil }, @@ -220,7 +220,7 @@ func init() { return &command.OperatorAutopilotCommand{ Command: base.Command{ Flags: base.FlagSetNone, - Ui: ui, + UI: ui, }, }, nil }, @@ -229,7 +229,7 @@ func init() { return &command.OperatorAutopilotGetCommand{ Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -238,7 +238,7 @@ func init() { return &command.OperatorAutopilotSetCommand{ Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -247,7 +247,7 @@ func init() { return &command.OperatorRaftCommand{ Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -256,7 +256,7 @@ func init() { return &command.OperatorRaftListCommand{ Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -265,7 +265,7 @@ func init() { return &command.OperatorRaftRemoveCommand{ Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -274,7 +274,7 @@ func init() { return &command.ReloadCommand{ Command: base.Command{ Flags: base.FlagSetClientHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -283,14 +283,14 @@ func init() { return &command.RTTCommand{ Command: base.Command{ Flags: base.FlagSetClientHTTP, - Ui: ui, + UI: ui, }, }, nil }, "snapshot": func() (cli.Command, error) { return &command.SnapshotCommand{ - Ui: ui, + UI: ui, }, nil }, @@ -298,7 +298,7 @@ func init() { return &command.SnapshotRestoreCommand{ Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -307,7 +307,7 @@ func init() { return &command.SnapshotSaveCommand{ Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, @@ -316,7 +316,7 @@ func init() { return &command.SnapshotInspectCommand{ Command: base.Command{ Flags: base.FlagSetNone, - Ui: ui, + UI: ui, }, }, nil }, @@ -325,7 +325,7 @@ func init() { return &command.ValidateCommand{ Command: base.Command{ Flags: base.FlagSetNone, - Ui: ui, + UI: ui, }, }, nil }, @@ -333,7 +333,7 @@ func init() { "version": func() (cli.Command, error) { return &command.VersionCommand{ HumanVersion: version.GetHumanVersion(), - Ui: ui, + UI: ui, }, nil }, @@ -342,7 +342,7 @@ func init() { ShutdownCh: makeShutdownCh(), Command: base.Command{ Flags: base.FlagSetHTTP, - Ui: ui, + UI: ui, }, }, nil }, diff --git a/vendor/github.com/hashicorp/consul/main.go b/vendor/github.com/hashicorp/consul/main.go index 1867a73f4c..831c007522 100644 --- a/vendor/github.com/hashicorp/consul/main.go +++ b/vendor/github.com/hashicorp/consul/main.go @@ -2,12 +2,12 @@ package main import ( "fmt" - "github.com/mitchellh/cli" "io/ioutil" "log" "os" "github.com/hashicorp/consul/lib" + "github.com/mitchellh/cli" ) func init() { diff --git a/vendor/github.com/hashicorp/go-rootcerts/LICENSE b/vendor/github.com/hashicorp/go-rootcerts/LICENSE new file mode 100644 index 0000000000..e87a115e46 --- /dev/null +++ b/vendor/github.com/hashicorp/go-rootcerts/LICENSE @@ -0,0 +1,363 @@ +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/go-rootcerts/doc.go b/vendor/github.com/hashicorp/go-rootcerts/doc.go new file mode 100644 index 0000000000..b55cc62848 --- /dev/null +++ b/vendor/github.com/hashicorp/go-rootcerts/doc.go @@ -0,0 +1,9 @@ +// Package rootcerts contains functions to aid in loading CA certificates for +// TLS connections. +// +// In addition, its default behavior on Darwin works around an open issue [1] +// in Go's crypto/x509 that prevents certicates from being loaded from the +// System or Login keychains. +// +// [1] https://github.com/golang/go/issues/14514 +package rootcerts diff --git a/vendor/github.com/hashicorp/go-rootcerts/rootcerts.go b/vendor/github.com/hashicorp/go-rootcerts/rootcerts.go new file mode 100644 index 0000000000..aeb30ece32 --- /dev/null +++ b/vendor/github.com/hashicorp/go-rootcerts/rootcerts.go @@ -0,0 +1,103 @@ +package rootcerts + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +// Config determines where LoadCACerts will load certificates from. When both +// CAFile and CAPath are blank, this library's functions will either load +// system roots explicitly and return them, or set the CertPool to nil to allow +// Go's standard library to load system certs. +type Config struct { + // CAFile is a path to a PEM-encoded certificate file or bundle. Takes + // precedence over CAPath. + CAFile string + + // CAPath is a path to a directory populated with PEM-encoded certificates. + CAPath string +} + +// ConfigureTLS sets up the RootCAs on the provided tls.Config based on the +// Config specified. +func ConfigureTLS(t *tls.Config, c *Config) error { + if t == nil { + return nil + } + pool, err := LoadCACerts(c) + if err != nil { + return err + } + t.RootCAs = pool + return nil +} + +// LoadCACerts loads a CertPool based on the Config specified. +func LoadCACerts(c *Config) (*x509.CertPool, error) { + if c == nil { + c = &Config{} + } + if c.CAFile != "" { + return LoadCAFile(c.CAFile) + } + if c.CAPath != "" { + return LoadCAPath(c.CAPath) + } + + return LoadSystemCAs() +} + +// LoadCAFile loads a single PEM-encoded file from the path specified. +func LoadCAFile(caFile string) (*x509.CertPool, error) { + pool := x509.NewCertPool() + + pem, err := ioutil.ReadFile(caFile) + if err != nil { + return nil, fmt.Errorf("Error loading CA File: %s", err) + } + + ok := pool.AppendCertsFromPEM(pem) + if !ok { + return nil, fmt.Errorf("Error loading CA File: Couldn't parse PEM in: %s", caFile) + } + + return pool, nil +} + +// LoadCAPath walks the provided path and loads all certificates encounted into +// a pool. +func LoadCAPath(caPath string) (*x509.CertPool, error) { + pool := x509.NewCertPool() + walkFn := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + pem, err := ioutil.ReadFile(path) + if err != nil { + return fmt.Errorf("Error loading file from CAPath: %s", err) + } + + ok := pool.AppendCertsFromPEM(pem) + if !ok { + return fmt.Errorf("Error loading CA Path: Couldn't parse PEM in: %s", path) + } + + return nil + } + + err := filepath.Walk(caPath, walkFn) + if err != nil { + return nil, err + } + + return pool, nil +} diff --git a/vendor/github.com/hashicorp/go-rootcerts/rootcerts_base.go b/vendor/github.com/hashicorp/go-rootcerts/rootcerts_base.go new file mode 100644 index 0000000000..66b1472c4a --- /dev/null +++ b/vendor/github.com/hashicorp/go-rootcerts/rootcerts_base.go @@ -0,0 +1,12 @@ +// +build !darwin + +package rootcerts + +import "crypto/x509" + +// LoadSystemCAs does nothing on non-Darwin systems. We return nil so that +// default behavior of standard TLS config libraries is triggered, which is to +// load system certs. +func LoadSystemCAs() (*x509.CertPool, error) { + return nil, nil +} diff --git a/vendor/github.com/hashicorp/go-rootcerts/rootcerts_darwin.go b/vendor/github.com/hashicorp/go-rootcerts/rootcerts_darwin.go new file mode 100644 index 0000000000..a9a040657f --- /dev/null +++ b/vendor/github.com/hashicorp/go-rootcerts/rootcerts_darwin.go @@ -0,0 +1,48 @@ +package rootcerts + +import ( + "crypto/x509" + "os/exec" + "path" + + "github.com/mitchellh/go-homedir" +) + +// LoadSystemCAs has special behavior on Darwin systems to work around +func LoadSystemCAs() (*x509.CertPool, error) { + pool := x509.NewCertPool() + + for _, keychain := range certKeychains() { + err := addCertsFromKeychain(pool, keychain) + if err != nil { + return nil, err + } + } + + return pool, nil +} + +func addCertsFromKeychain(pool *x509.CertPool, keychain string) error { + cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", keychain) + data, err := cmd.Output() + if err != nil { + return err + } + + pool.AppendCertsFromPEM(data) + + return nil +} + +func certKeychains() []string { + keychains := []string{ + "/System/Library/Keychains/SystemRootCertificates.keychain", + "/Library/Keychains/System.keychain", + } + home, err := homedir.Dir() + if err == nil { + loginKeychain := path.Join(home, "Library", "Keychains", "login.keychain") + keychains = append(keychains, loginKeychain) + } + return keychains +} diff --git a/vendor/github.com/mitchellh/go-homedir/LICENSE b/vendor/github.com/mitchellh/go-homedir/LICENSE new file mode 100644 index 0000000000..f9c841a51e --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mitchell Hashimoto + +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/mitchellh/go-homedir/homedir.go b/vendor/github.com/mitchellh/go-homedir/homedir.go new file mode 100644 index 0000000000..47e1f9ef8e --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/homedir.go @@ -0,0 +1,137 @@ +package homedir + +import ( + "bytes" + "errors" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" +) + +// DisableCache will disable caching of the home directory. Caching is enabled +// by default. +var DisableCache bool + +var homedirCache string +var cacheLock sync.RWMutex + +// Dir returns the home directory for the executing user. +// +// This uses an OS-specific method for discovering the home directory. +// An error is returned if a home directory cannot be detected. +func Dir() (string, error) { + if !DisableCache { + cacheLock.RLock() + cached := homedirCache + cacheLock.RUnlock() + if cached != "" { + return cached, nil + } + } + + cacheLock.Lock() + defer cacheLock.Unlock() + + var result string + var err error + if runtime.GOOS == "windows" { + result, err = dirWindows() + } else { + // Unix-like system, so just assume Unix + result, err = dirUnix() + } + + if err != nil { + return "", err + } + homedirCache = result + return result, nil +} + +// Expand expands the path to include the home directory if the path +// is prefixed with `~`. If it isn't prefixed with `~`, the path is +// returned as-is. +func Expand(path string) (string, error) { + if len(path) == 0 { + return path, nil + } + + if path[0] != '~' { + return path, nil + } + + if len(path) > 1 && path[1] != '/' && path[1] != '\\' { + return "", errors.New("cannot expand user-specific home dir") + } + + dir, err := Dir() + if err != nil { + return "", err + } + + return filepath.Join(dir, path[1:]), nil +} + +func dirUnix() (string, error) { + // First prefer the HOME environmental variable + if home := os.Getenv("HOME"); home != "" { + return home, nil + } + + // If that fails, try getent + var stdout bytes.Buffer + cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid())) + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + // If the error is ErrNotFound, we ignore it. Otherwise, return it. + if err != exec.ErrNotFound { + return "", err + } + } else { + if passwd := strings.TrimSpace(stdout.String()); passwd != "" { + // username:password:uid:gid:gecos:home:shell + passwdParts := strings.SplitN(passwd, ":", 7) + if len(passwdParts) > 5 { + return passwdParts[5], nil + } + } + } + + // If all else fails, try the shell + stdout.Reset() + cmd = exec.Command("sh", "-c", "cd && pwd") + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return "", err + } + + result := strings.TrimSpace(stdout.String()) + if result == "" { + return "", errors.New("blank output when reading home directory") + } + + return result, nil +} + +func dirWindows() (string, error) { + // First prefer the HOME environmental variable + if home := os.Getenv("HOME"); home != "" { + return home, nil + } + + drive := os.Getenv("HOMEDRIVE") + path := os.Getenv("HOMEPATH") + home := drive + path + if drive == "" || path == "" { + home = os.Getenv("USERPROFILE") + } + if home == "" { + return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank") + } + + return home, nil +} diff --git a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go index 115ae67c11..acdaadefeb 100644 --- a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go +++ b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go @@ -121,6 +121,11 @@ func StringToTimeDurationHookFunc() DecodeHookFunc { } } +// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to +// the decoder. +// +// Note that this is significantly different from the WeaklyTypedInput option +// of the DecoderConfig. func WeaklyTypedHook( f reflect.Kind, t reflect.Kind, @@ -132,9 +137,8 @@ func WeaklyTypedHook( case reflect.Bool: if dataVal.Bool() { return "1", nil - } else { - return "0", nil } + return "0", nil case reflect.Float32: return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil case reflect.Int: diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go index 6dee0ef0a2..6ec5c33357 100644 --- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go +++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go @@ -1,5 +1,5 @@ -// The mapstructure package exposes functionality to convert an -// arbitrary map[string]interface{} into a native Go structure. +// Package mapstructure exposes functionality to convert an arbitrary +// map[string]interface{} into a native Go structure. // // The Go structure can be arbitrarily complex, containing slices, // other structs, etc. and the decoder will properly decode nested @@ -32,7 +32,12 @@ import ( // both. type DecodeHookFunc interface{} +// DecodeHookFuncType is a DecodeHookFunc which has complete information about +// the source and target types. type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error) + +// DecodeHookFuncKind is a DecodeHookFunc which knows only the Kinds of the +// source and target types. type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) // DecoderConfig is the configuration that is used to create a new decoder @@ -436,7 +441,7 @@ func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) case dataKind == reflect.Uint: val.SetFloat(float64(dataVal.Uint())) case dataKind == reflect.Float32: - val.SetFloat(float64(dataVal.Float())) + val.SetFloat(dataVal.Float()) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: if dataVal.Bool() { val.SetFloat(1) diff --git a/vendor/github.com/jarcoal/httpmock/LICENSE b/vendor/gopkg.in/jarcoal/httpmock.v1/LICENSE similarity index 100% rename from vendor/github.com/jarcoal/httpmock/LICENSE rename to vendor/gopkg.in/jarcoal/httpmock.v1/LICENSE diff --git a/vendor/github.com/jarcoal/httpmock/doc.go b/vendor/gopkg.in/jarcoal/httpmock.v1/doc.go similarity index 100% rename from vendor/github.com/jarcoal/httpmock/doc.go rename to vendor/gopkg.in/jarcoal/httpmock.v1/doc.go diff --git a/vendor/github.com/jarcoal/httpmock/env.go b/vendor/gopkg.in/jarcoal/httpmock.v1/env.go similarity index 100% rename from vendor/github.com/jarcoal/httpmock/env.go rename to vendor/gopkg.in/jarcoal/httpmock.v1/env.go diff --git a/vendor/github.com/jarcoal/httpmock/response.go b/vendor/gopkg.in/jarcoal/httpmock.v1/response.go similarity index 90% rename from vendor/github.com/jarcoal/httpmock/response.go rename to vendor/gopkg.in/jarcoal/httpmock.v1/response.go index 5cbd7d837c..bd39d7f0ce 100644 --- a/vendor/github.com/jarcoal/httpmock/response.go +++ b/vendor/gopkg.in/jarcoal/httpmock.v1/response.go @@ -13,7 +13,19 @@ import ( // ResponderFromResponse wraps an *http.Response in a Responder func ResponderFromResponse(resp *http.Response) Responder { return func(req *http.Request) (*http.Response, error) { - return resp, nil + res := new(http.Response) + *res = *resp + res.Request = req + return res, nil + } +} + +// NewErrorResponder creates a Responder that returns an empty request and the +// given error. This can be used to e.g. imitate more deep http errors for the +// client. +func NewErrorResponder(err error) Responder { + return func(req *http.Request) (*http.Response, error) { + return nil, err } } diff --git a/vendor/github.com/jarcoal/httpmock/transport.go b/vendor/gopkg.in/jarcoal/httpmock.v1/transport.go similarity index 70% rename from vendor/github.com/jarcoal/httpmock/transport.go rename to vendor/gopkg.in/jarcoal/httpmock.v1/transport.go index 8753d8f1d7..9a0e8f612d 100644 --- a/vendor/github.com/jarcoal/httpmock/transport.go +++ b/vendor/gopkg.in/jarcoal/httpmock.v1/transport.go @@ -2,8 +2,10 @@ package httpmock import ( "errors" + "fmt" "net/http" "strings" + "sync" ) // Responders are callbacks that receive and http request and return a mocked response. @@ -20,15 +22,21 @@ func ConnectionFailure(*http.Request) (*http.Response, error) { // NewMockTransport creates a new *MockTransport with no responders. func NewMockTransport() *MockTransport { - return &MockTransport{make(map[string]Responder), nil} + return &MockTransport{ + responders: make(map[string]Responder), + callCountInfo: make(map[string]int), + } } // MockTransport implements http.RoundTripper, which fulfills single http requests issued by // an http.Client. This implementation doesn't actually make the call, instead deferring to // the registered list of responders. type MockTransport struct { - responders map[string]Responder - noResponder Responder + mu sync.RWMutex + responders map[string]Responder + noResponder Responder + callCountInfo map[string]int + totalCallCount int } // RoundTrip receives HTTP requests and routes them to the appropriate responder. It is required to @@ -38,24 +46,84 @@ func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) { url := req.URL.String() // try and get a responder that matches the method and URL - responder := m.responderForKey(req.Method + " " + url) + key := req.Method + " " + url + responder := m.responderForKey(key) // if we weren't able to find a responder and the URL contains a querystring // then we strip off the querystring and try again. if responder == nil && strings.Contains(url, "?") { - responder = m.responderForKey(req.Method + " " + strings.Split(url, "?")[0]) + key = req.Method + " " + strings.Split(url, "?")[0] + responder = m.responderForKey(key) } - + m.mu.Lock() + defer m.mu.Unlock() // if we found a responder, call it if responder != nil { - return responder(req) + m.callCountInfo[key]++ + m.totalCallCount++ + return runCancelable(responder, req) } // we didn't find a responder, so fire the 'no responder' responder if m.noResponder == nil { return ConnectionFailure(req) } - return m.noResponder(req) + m.callCountInfo["NO_RESPONDER"]++ + m.totalCallCount++ + return runCancelable(m.noResponder, req) +} + +func runCancelable(responder Responder, req *http.Request) (*http.Response, error) { + if req.Cancel == nil { + return responder(req) + } + + // Set up a goroutine that translates a close(req.Cancel) into a + // "request canceled" error, and another one that runs the + // responder. Then race them: first to the result channel wins. + + type result struct { + response *http.Response + err error + } + resultch := make(chan result, 1) + done := make(chan struct{}, 1) + + go func() { + select { + case <-req.Cancel: + resultch <- result{ + response: nil, + err: errors.New("request canceled"), + } + case <-done: + } + }() + + go func() { + defer func() { + if err := recover(); err != nil { + resultch <- result{ + response: nil, + err: fmt.Errorf("panic in responder: got %q", err), + } + } + }() + + response, err := responder(req) + resultch <- result{ + response: response, + err: err, + } + }() + + r := <-resultch + + // if a close(req.Cancel) is never coming, + // we'll need to unblock the first goroutine. + done <- struct{}{} + + return r.response, r.err } // do nothing with timeout @@ -63,6 +131,8 @@ func (m *MockTransport) CancelRequest(req *http.Request) {} // responderForKey returns a responder for a given key func (m *MockTransport) responderForKey(key string) Responder { + m.mu.RLock() + defer m.mu.RUnlock() for k, r := range m.responders { if k != key { continue @@ -75,19 +145,49 @@ func (m *MockTransport) responderForKey(key string) Responder { // RegisterResponder adds a new responder, associated with a given HTTP method and URL. When a // request comes in that matches, the responder will be called and the response returned to the client. func (m *MockTransport) RegisterResponder(method, url string, responder Responder) { - m.responders[method+" "+url] = responder + key := method + " " + url + + m.mu.Lock() + m.responders[key] = responder + m.callCountInfo[key] = 0 + m.mu.Unlock() } // RegisterNoResponder is used to register a responder that will be called if no other responder is // found. The default is ConnectionFailure. func (m *MockTransport) RegisterNoResponder(responder Responder) { + m.mu.Lock() m.noResponder = responder + m.mu.Unlock() } // Reset removes all registered responders (including the no responder) from the MockTransport func (m *MockTransport) Reset() { + m.mu.Lock() m.responders = make(map[string]Responder) m.noResponder = nil + m.callCountInfo = make(map[string]int) + m.totalCallCount = 0 + m.mu.Unlock() +} + +// GetCallCountInfo returns callCountInfo +func (m *MockTransport) GetCallCountInfo() map[string]int { + res := map[string]int{} + m.mu.RLock() + for k, v := range m.callCountInfo { + res[k] = v + } + m.mu.RUnlock() + return res +} + +// GetTotalCallCount returns the totalCallCount +func (m *MockTransport) GetTotalCallCount() int { + m.mu.RLock() + count := m.totalCallCount + m.mu.RUnlock() + return count } // DefaultTransport is the default mock transport used by Activate, Deactivate, Reset, @@ -147,6 +247,19 @@ func ActivateNonDefault(client *http.Client) { client.Transport = DefaultTransport } +// GetCallCountInfo gets the info on all the calls httpmock has taken since it was activated or +// reset. The info is returned as a map of the calling keys with the number of calls made to them +// as their value. The key is the method, a space, and the url all concatenated together. +func GetCallCountInfo() map[string]int { + return DefaultTransport.GetCallCountInfo() +} + +// GetTotalCallCount gets the total number of calls httpmock has taken since it was activated or +// reset. +func GetTotalCallCount() int { + return DefaultTransport.GetTotalCallCount() +} + // Deactivate shuts down the mock environment. Any HTTP calls made after this will use a live // transport. //