pax_global_header00006660000000000000000000000064137527463630014532gustar00rootroot0000000000000052 comment=0bb4dbc1eed4f83462c379a0f7682e9883760c6a vip-manager-1.0.1/000077500000000000000000000000001375274636300137375ustar00rootroot00000000000000vip-manager-1.0.1/.github/000077500000000000000000000000001375274636300152775ustar00rootroot00000000000000vip-manager-1.0.1/.github/workflows/000077500000000000000000000000001375274636300173345ustar00rootroot00000000000000vip-manager-1.0.1/.github/workflows/build.yml000066400000000000000000000034551375274636300211650ustar00rootroot00000000000000name: Go Build & Test on: push: paths: - '**.go' - '**.yaml' - '**.yml' - '**.sql' branches: - '*' tags-ignore: - '*' jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest] name: Build & Test steps: - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Get dependencies run: | go mod download go version go build - name: Remove windows dependencies for Ubuntu if: runner.os == 'Linux' run: | rm -r iphlpapi - name: GolangCI-Lint on Linux if: runner.os == 'Linux' uses: golangci/golangci-lint-action@v1 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. version: v1.29 # Optional: golangci-lint command line arguments. args: --verbose - name: GolangCI-Lint on Windows if: runner.os == 'Windows' run: | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.29.0 ./bin/golangci-lint run --verbose # - name: Test # run: go test -v -p 1 -coverprofile=profile.cov ./... # - name: Convert coverage to lcov # uses: jandelgado/gcov2lcov-action@v1.0.2 # with: # infile: profile.cov # outfile: coverage.lcov # - name: Coveralls # uses: coverallsapp/github-action@master # with: # github-token: ${{ secrets.GITHUB_TOKEN }} # path-to-lcov: coverage.lcov # - name: Run GoReleaser # uses: goreleaser/goreleaser-action@master # with: # args: release --snapshot --skip-publish --rm-distvip-manager-1.0.1/.gitignore000066400000000000000000000000541375274636300157260ustar00rootroot00000000000000/vip-manager *.deb *.rpm *.exe tmp/ .vscode/vip-manager-1.0.1/.golangci.yml000066400000000000000000000012071375274636300163230ustar00rootroot00000000000000linters: enable: - gocyclo - golint - misspell - unused linters-settings: gocyclo: # minimal code complexity to report, 30 by default (but we recommend 10-20) min-complexity: 16 golint: # minimal confidence for issues, default is 0.8 min-confidence: 0.8 issues: # List of regexps of issue texts to exclude, empty list by default. # But independently from this option we use default exclude patterns, # it can be disabled by `exclude-use-default: false`. To list all # excluded by default patterns execute `golangci-lint run --help` exclude: - SA5008 # ignore staticcheck for go-flags vip-manager-1.0.1/Dockerfile000066400000000000000000000004471375274636300157360ustar00rootroot00000000000000FROM golang:1.9.4-alpine3.7 AS build ENV GOPATH /app WORKDIR /app/src/github.com/cybertec-postgresql/vip-manager COPY . . RUN go install FROM alpine:latest RUN apk add --no-cache iproute2 dumb-init COPY --from=build /app/bin/vip-manager / ENTRYPOINT ["/usr/bin/dumb-init", "--", "/vip-manager"] vip-manager-1.0.1/LICENSE000066400000000000000000000024771375274636300147560ustar00rootroot00000000000000BSD 2-Clause License Copyright (c) 2017-2019, Cybertec Schönig & Schönig GmbH All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. vip-manager-1.0.1/Makefile000066400000000000000000000040741375274636300154040ustar00rootroot00000000000000NAME=vip-manager VERSION=1.0.1-1 # when specifying a beta version or something, make sure to stay compatible with .deb conventions, e.g. "1.0~beta3-1" ARCH=amd64 LICENSE="BSD 2-Clause License" MAINTAINER="Julian Markwort " DESCRIPTION="Manages a virtual IP based on state kept in etcd/consul." HOMEPAGE="http://www.cybertec.at/" GIT="git://github.com/cybertec-postgresql/vip-manager.git" GITBROWSER="https://github.com/cybertec-postgresql/vip-manager" GOENV=CGO_ENABLED=0 all: vip-manager vip-manager: *.go */*.go $(GOENV) go build -ldflags="-s -w" . install: install -d $(DESTDIR)/usr/bin install vip-manager $(DESTDIR)/usr/bin/vip-manager install -d $(DESTDIR)/lib/systemd/system install package/scripts/init-systemd.service $(DESTDIR)/lib/systemd/system/vip-manager.service install -d $(DESTDIR)/etc/init.d/ install package/scripts/init-systemv.sh $(DESTDIR)/etc/init.d/vip-manager install -d $(DESTDIR)/etc/default install vipconfig/vip-manager.yml $(DESTDIR)/etc/default/vip-manager.yml DESTDIR=tmp .PHONY: package package: package-deb package-rpm package-deb: vip-manager install -d $(DESTDIR)/usr/bin install vip-manager $(DESTDIR)/usr/bin/vip-manager install -d $(DESTDIR)/usr/share/doc/$(NAME) install --mode=644 package/DEBIAN/copyright $(DESTDIR)/usr/share/doc/$(NAME)/copyright fpm -f -s dir -t deb -n $(NAME) -v $(VERSION) -C $(DESTDIR) \ -p $(NAME)_$(VERSION)_$(ARCH).deb \ --license $(LICENSE) \ --maintainer $(MAINTAINER) \ --vendor $(MAINTAINER) \ --description $(DESCRIPTION) \ --url $(HOMEPAGE) \ --deb-field 'Vcs-Git: $(GIT)' \ --deb-field 'Vcs-Browser: $(GITBROWSER)' \ --deb-upstream-changelog package/DEBIAN/changelog \ --deb-no-default-config-files \ --deb-default vipconfig/vip-manager.yml \ --deb-systemd package/scripts/vip-manager.service \ usr/bin usr/share/doc/ package-rpm: package-deb fpm -f -s deb -t rpm -n $(NAME) -v $(VERSION) -C $(DESTDIR) \ -p $(NAME)_$(VERSION)_$(ARCH).rpm \ $(NAME)_$(VERSION)_$(ARCH).deb clean: rm -f vip-manager rm -f vip-manager*.deb rm -f vip-manager*.rpm rm -fr $(DESTDIR) vip-manager-1.0.1/README.md000066400000000000000000000303361375274636300152230ustar00rootroot00000000000000[![License: MIT](https://img.shields.io/badge/License-BSD-green.svg)](https://opensource.org/licenses/BSD-2) ![](https://github.com/cybertec-postgresql/vip-manager/workflows/Go%20Build%20&%20Test/badge.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/cybertec-postgresql/vip-manager)](https://goreportcard.com/report/github.com/cybertec-postgresql/vip-manager) [![Release](https://img.shields.io/github/release/cybertec-postgresql/vip-manager.svg)](https://github.com/cybertec-postgresql/vip-manager/releases/latest) # vip-manager Manages a virtual IP based on state kept in etcd or Consul. Monitors state in etcd ## Table of Contents - [Building](#building) - [Installing from package](#Installing-from-package) - [Installing by hand](#installing-by-hand) - [PostgreSQL prerequisites](#PostgreSQL-prerequisites) - [Configuration](#Configuration) - [Migrating configuration from releases before v1.0](#migrating-configuration-from-releases-before-v10) - [Migration for Service Files using Environment Variables](#Migration-for-Service-Files-using-Environment-Variables) - [Migration for Service Files using YAML config files](#Migration-for-Service-Files-using-YAML-config-files) - [Configuration - Hetzner](#Configuration---Hetzner) - [Credential File - Hetzmer](#Credential-File---Hetzner) - [Debugging](#Debugging) - [Author](#Author) ## Building 1. Make sure you have at least version 1.14 of Golang for proper module support. You can get by with go 1.12 or 1.13, but YMMV. 2. To make sure that internal includes (the vipconfig and the checker package) are satisfied, place the base directory of this project properly into your `$GOPATH`. The resulting location should be `$GOPATH/src/github.com/cybertec-postgresql/vip-manager/`. The easiest way to do this is: ```go get github.com/cybertec-postgresql/vip-manager``` 3. Build the binary using `make`. 4. To build your own .deb or .rpm, `fpm` is required. Install it, add it to your path and try running `make package`, which will generate a .deb package and will also convert that into a matching .rpm file. > note: on debianoids, rpmbuild will be required to create the rpm package... ## Installing from package You can download .rpm or .deb packages here, on the [Releases](https://github.com/cybertec-postgresql/vip-manager/releases) page. On Debian and Ubuntu, the universe repositories should provide you with vip-manager, though the version may be not as recent. > NB! Our .deb is probably not compatible with the one from those repositories, do not try to install them side-by-side. ## Installing by hand * Build the vip-manager binary using `make`. * Copy the resulting `vip-manager` binary to `/usr/bin/vip-manager`. * Install service file from `package/scripts/vip-manager.service` to `/etc/systemd/system/` * Install configuration file from `vipconfig/vip-manager.yml` `/etc/default/vip-manager.yml` * Edit config to your needs, then `systemctl daemon-reload`, then `systemctl start vip-manager`. ## PostgreSQL prerequisites For any virtual IP based solutions to work in general with Postgres you need to make sure that it is configured to automatically scan and bind to all found network interfaces. So something like `*` or `0.0.0.0` (IPv4 only) is needed for the `listen_addresses` parameter to activate the automatic binding. This again might not be suitable for all use cases where security is paramount for example. ## Configuration The configuration can be passed to the executable through argument flags, environment variables or through a YAML config file. Run `vip-manager --help` to see the available flags. > The location of the YAML config file can be specified with the --config flag. > An exemplary config file is installed into `/etc/default/vip-manager_default.yml` or is available in the vipconfig directory in the repository of the software. Configuration is now (from release v1.0 on) handled using the [`viper`](https://github.com/spf13/viper) library. This means that environment variables, command line flags, and config files can be used to configure vip-manager. When using different configuration sources simultaneously, this is the precedence order: - flag - env - config > So flags always overwrite env variables and entries from the config file. Env variables overwrite the config file entries. All flags and file entries are written in lower case. To make longer multi-word flags and entries readable, they are separated by dashes. > e.g. `retry-num` If you put a flag or file entry into uppercase and replace dashes with underscores, you end up with the format of environment variables. To avoid overlapping configuration with other applications, the env variables are additionall prefixed with `VIP_`. > e.g. `VIP_RETRY_NUM` This is a list of all avaiable configuration items: | flag/yaml key | env notation | required | example | description | | ----------------- | --------------------- | --------- | ------------------------- | ----------- | `ip` | `VIP_IP` | yes | 10.10.10.123 | The virtual IP address that will be managed. `netmask` | `VIP_NETMASK` | yes | 24 | The netmask that is associated with the subnet that the virtual IP `vip` is part of. `interface` | `VIP_INTERFACE` | yes | eth0 | A local network interface on the machine that runs vip-manager. Required when using `manager-type=basic`. The vip will be added to and removed from this interface. `trigger-key` | `VIP_TRIGGER_KEY` | yes | /service/pgcluster/leader | The key in the DCS that will be monitored by vip-manager. Must match `//leader` from Patroni config. When the value returned by the DCS equals `trigger-value`, vip-manager will make sure that the virtual IP is registered to this machine. If it does not match, vip-manager makes sure that the virtual IP is not registered to this machine. `trigger-value` | `VIP_TRIGGER_VALUE` | yes | pgcluster_member_1 | The value that the DCS' answer for `trigger-key` will be matched to. Must match `` from Patroni config. This is usually set to the name of the patroni cluster member that this vip-manager instance is associated with. Defaults to the machine's hostname. `manager-type` | `VIP_MANAGER_TYPE` | no | basic | Either `basic` or `hetzner`. This describes the mechanism that is used to manage the virtual IP. Defaults to `basic`. `dcs-type` | `VIP_DCS_TYPE` | no | etcd | The type of DCS that vip-manager will use to monitor the `trigger-key`. Defaults to `etcd`. `dcs-endpoints` | `VIP_DCS_ENDPOINTS` | no | http://10.10.11.1:2379 | A url that defines where to reach the DCS. Multiple endpoints can be passed to the flag or env variable using a comma-separated-list. In the config file, a list can be specified, see the sample config for an example. Defaults to `http://127.0.0.1:2379` for `dcs-type=etcd` and `http://127.0.0.1:8500` for `dcs-type=consul`. `etcd-user` | `VIP_ETCD_USER` | no | patroni | A username that is allowed to look at the `trigger-key` in an etcd DCS. Optional when using `dcs-type=etcd` . `etcd-password` | `VIP_ETCD_PASSWORD` | no | snakeoil | The password for `etcd-user`. Optional when using `dcs-type=etcd` . Requires that `etcd-user` is also set. `consul-token` | `VIP_CONSUL_TOKEN` | no | snakeoil | A token that can be used with the consul-API for authentication. Optional when using `dcs-type=consul` . `interval` | `VIP_INTERVAL` | no | 1000 | The time vip-manager main loop sleeps before checking for changes. Measured in ms. Defaults to `1000`. `retry-after` | `VIP_RETRY_AFTER` | no | 250 | The time to wait before retrying interactions with components outside of vip-manager. Measured in ms. Defaults to `250`. `retry-num` | `VIP_RETRY_NUM` | no | 3 | The number of times interactions with components outside of vip-manager are retried. Measured in ms. Defaults to `250`. `etcd-ca-file` | `VIP_ETCD_CA_FILE` | no | /etc/etcd/ca.cert.pem | A certificate authority file that can be used to verify the certificate provided by etcd endpoints. Make sure to change `dcs-endpoints` to reflect that `https` is used. `etcd-cert-file` | `VIP_ETCD_CERT_FILE` | no | /etc/etcd/client.cert.pem | A client certificate that is used to authenticate against etcd endpoints. Requires `etcd-ca-file` to be set as well. `etcd-key-file` | `VIP_ETCD_KEY_FILE` | no | /etc/etcd/client.key.pem | A private key for the client certificate, used to decrypt messages sent by etcd endpoints. Required when `etcd-cert-file` is specified. `verbose` | `VIP_VERBOSE` | no | true | Enable more verbose logging. Currently only the manager-type=hetzner provides additional logs. ### Migrating configuration from releases before v1.0 As stated above, the configuration method has been changed from v1.0 onwards. The breaking changes with regards to config handling are thus: - Config flags are no longer prefixed with a single dash `-`, but in POSIX style with double dashes `--`. - Some config keys have received new names, but the value handling has not changed. However, some consideration has been made to ease migration: - The old key names in VIP_* environment variables and in YAML config files are mapped to the new keys, a deprecation warning will be emitted for each old key that is used. - If both old and new key names are used and have different values, vip-manager will exit; If both values are identical, the duplication will be ignored. This means that migration should be pretty straight forward. #### Migration for Service Files using Environment Variables > These Service Files have been published in releases =v1.0) will pick them up itself. It will pick up the old keys and use only emit log messages indicating the deprecation of those keys. > You could even consider another shim, that will take the environment variables using old keys, get their values and assign them to environment variables using the new keys. Then unset the old keys and you will no longer see deprecation messages. #### Migration for Service Files using YAML config files: > These Service FIles have been published in release v0.6 In these "config file style" Service Files, the yaml configuration file was passed as a flag to the vip-manager. ```bash ExecStart=/usr/bin/vip-manager -config=/etc/default/vip-manager.yml ``` Because the old keys in the YAML config files will be remapped to new keys, all that needs to be done is to add a single dash (`sed -i 's/-config/--config/'`): ```bash ExecStart=/usr/bin/vip-manager --config=/etc/default/vip-manager.yml ``` ## Configuration - Hetzner To use vip-manager with Hetzner Robot API you need a Credential file, set hosting_type to `hetzner` and your Floating-IP must be added on all Servers. The Floating-IP (VIP) will not be added or removed on the current Master node interface, Hetzner will route it to the current one. Set `hosting_type` to `hetzner` in `/etc/default/vip-manager.yml` ### Credential File - Hetzner Add the File `/etc/hetzner` with your Username and Password ``` user="myUsername" pass="myPassword" ``` ## Debugging Either: * run `vip-manager` with `--verbose` flag or * set `verbose` to `true` in `/etc/default/vip-manager.yml` * set `VIP_VERBOSE=true` (currently only supported for `hetzner`) ## Author Cybertec Schönig & Schönig GmbH, https://www.cybertec-postgresql.com vip-manager-1.0.1/checker/000077500000000000000000000000001375274636300153435ustar00rootroot00000000000000vip-manager-1.0.1/checker/consul_leader_checker.go000066400000000000000000000040261375274636300221770ustar00rootroot00000000000000package checker import ( "context" "log" "net/url" "time" "github.com/cybertec-postgresql/vip-manager/vipconfig" "github.com/hashicorp/consul/api" ) // ConsulLeaderChecker is used to check state of the leader key in Consul type ConsulLeaderChecker struct { key string nodename string apiClient *api.Client } //naming this cConf to avoid conflict with conf in etcd_leader_checker.go var cConf *vipconfig.Config // NewConsulLeaderChecker returns a new instance func NewConsulLeaderChecker(con *vipconfig.Config) (*ConsulLeaderChecker, error) { cConf = con lc := &ConsulLeaderChecker{ key: cConf.Key, nodename: cConf.Nodename, } url, err := url.Parse(cConf.Endpoints[0]) if err != nil { return nil, err } address := url.Hostname() + ":" + url.Port() config := &api.Config{ Address: address, Scheme: url.Scheme, WaitTime: time.Second, } if cConf.ConsulToken != "" { config.Token = cConf.ConsulToken } apiClient, err := api.NewClient(config) if err != nil { return nil, err } lc.apiClient = apiClient return lc, nil } // GetChangeNotificationStream checks the status in the loop func (c *ConsulLeaderChecker) GetChangeNotificationStream(ctx context.Context, out chan<- bool) error { kv := c.apiClient.KV() queryOptions := &api.QueryOptions{ RequireConsistent: true, } checkLoop: for { resp, _, err := kv.Get(c.key, queryOptions) if err != nil { if ctx.Err() != nil { break checkLoop } log.Printf("consul error: %s", err) out <- false time.Sleep(time.Duration(cConf.Interval) * time.Millisecond) continue } if resp == nil { log.Printf("Cannot get variable for key %s. Will try again in a second.", c.key) out <- false time.Sleep(time.Duration(cConf.Interval) * time.Millisecond) continue } state := string(resp.Value) == c.nodename queryOptions.WaitIndex = resp.ModifyIndex select { case <-ctx.Done(): break checkLoop case out <- state: time.Sleep(time.Duration(cConf.Interval) * time.Millisecond) continue } } return ctx.Err() } vip-manager-1.0.1/checker/etcd_leader_checker.go000066400000000000000000000060641375274636300216170ustar00rootroot00000000000000package checker import ( "context" "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "log" "net" "net/http" "time" "github.com/coreos/etcd/client" "github.com/cybertec-postgresql/vip-manager/vipconfig" ) // EtcdLeaderChecker is used to check state of the leader key in Etcd type EtcdLeaderChecker struct { key string nodename string kapi client.KeysAPI } //naming this c_conf to avoid conflict with conf in etcd_leader_checker.go var eConf *vipconfig.Config func getTransport(conf *vipconfig.Config) (client.CancelableTransport, error) { var caCertPool *x509.CertPool // create valid CertPool only if the ca certificate file exists if conf.EtcdCAFile != "" { caCert, err := ioutil.ReadFile(conf.EtcdCAFile) if err != nil { return nil, fmt.Errorf("cannot load CA file: %s", err) } caCertPool = x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) } var certificates []tls.Certificate // create valid []Certificate only if the client cert and key files exists if conf.EtcdCertFile != "" && conf.EtcdKeyFile != "" { cert, err := tls.LoadX509KeyPair(conf.EtcdCertFile, conf.EtcdKeyFile) if err != nil { return nil, fmt.Errorf("cannot load client cert or key file: %s", err) } certificates = []tls.Certificate{cert} } tlsClientConfig := new(tls.Config) if caCertPool != nil { tlsClientConfig.RootCAs = caCertPool if certificates != nil { tlsClientConfig.Certificates = certificates } } // TODO: make these timeouts adjustable return &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).Dial, TLSClientConfig: tlsClientConfig, TLSHandshakeTimeout: 10 * time.Second, }, nil } // NewEtcdLeaderChecker returns a new instance func NewEtcdLeaderChecker(con *vipconfig.Config) (*EtcdLeaderChecker, error) { eConf = con e := &EtcdLeaderChecker{key: eConf.Key, nodename: eConf.Nodename} transport, err := getTransport(eConf) if err != nil { return nil, err } cfg := client.Config{ Endpoints: eConf.Endpoints, Transport: transport, HeaderTimeoutPerRequest: time.Second, Username: eConf.EtcdUser, Password: eConf.EtcdPassword, } c, err := client.New(cfg) if err != nil { return nil, err } e.kapi = client.NewKeysAPI(c) return e, nil } // GetChangeNotificationStream checks the status in the loop func (e *EtcdLeaderChecker) GetChangeNotificationStream(ctx context.Context, out chan<- bool) error { clientOptions := &client.GetOptions{ Quorum: true, Recursive: false, } checkLoop: for { resp, err := e.kapi.Get(ctx, e.key, clientOptions) if err != nil { if ctx.Err() != nil { break checkLoop } log.Printf("etcd error: %s", err) out <- false time.Sleep(time.Duration(eConf.Interval) * time.Millisecond) continue } state := resp.Node.Value == e.nodename select { case <-ctx.Done(): break checkLoop case out <- state: time.Sleep(time.Duration(eConf.Interval) * time.Millisecond) continue } } return ctx.Err() } vip-manager-1.0.1/checker/leader_checker.go000066400000000000000000000014661375274636300206210ustar00rootroot00000000000000package checker import ( "context" "errors" "github.com/cybertec-postgresql/vip-manager/vipconfig" ) // ErrUnsupportedEndpointType is returned for an unsupported endpoint var ErrUnsupportedEndpointType = errors.New("given endpoint type not supported") // LeaderChecker is the interface for checking leadership type LeaderChecker interface { GetChangeNotificationStream(ctx context.Context, out chan<- bool) error } // NewLeaderChecker returns a new LeaderChecker instance depending on the configuration func NewLeaderChecker(con *vipconfig.Config) (LeaderChecker, error) { var lc LeaderChecker var err error switch con.EndpointType { case "consul": lc, err = NewConsulLeaderChecker(con) case "etcd": lc, err = NewEtcdLeaderChecker(con) default: err = ErrUnsupportedEndpointType } return lc, err } vip-manager-1.0.1/go.mod000066400000000000000000000026011375274636300150440ustar00rootroot00000000000000module github.com/cybertec-postgresql/vip-manager go 1.14 require ( github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect github.com/coreos/etcd v3.3.13+incompatible github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d // indirect github.com/hashicorp/consul/api v1.5.0 github.com/hashicorp/go-immutable-radix v1.1.0 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/serf v0.9.3 // indirect github.com/json-iterator/go v1.1.10 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 // indirect github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 // indirect github.com/mitchellh/go-testing-interface v1.14.0 // indirect github.com/mitchellh/mapstructure v1.2.3 // indirect github.com/prometheus/client_golang v1.0.0 // indirect github.com/sirupsen/logrus v1.4.2 // indirect github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.0 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 // indirect golang.org/x/net v0.0.0-20200513185701-a91f0712d120 // indirect golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 google.golang.org/grpc v1.23.0 // indirect ) vip-manager-1.0.1/go.sum000066400000000000000000001327431375274636300151040ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.5.0 h1:Yo2bneoGy68A7aNwmuETFnPhjyBEm7n3vzRacEVMjvI= github.com/hashicorp/consul/api v1.5.0/go.mod h1:LqwrLNW876eYSuUOo4ZLHBcdKc038txr/IMfbLPATa4= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.5.0 h1:WC4594Wp/LkEeML/OdQKEC1yqBmEYkRp6i7X5u0zDAs= github.com/hashicorp/consul/sdk v0.5.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1 h1:XFSOubp8KWB+Jd2PDyaX5xUd5bhSP/+pTDZVDMzZJM8= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU= github.com/hashicorp/serf v0.9.3 h1:AVF6JDQQens6nMHT9OGERBvK0f8rPrAGILnsKLr6lzM= github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc h1:m7rJJJeXrYCFpsxXYapkDW53wJCDmf9bsIXUg0HoeQY= github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc/go.mod h1:eOj1DDj3NAZ6yv+WafaKzY37MFZ58TdfIhQ+8nQbiis= github.com/mdlayher/ethernet v0.0.0-20190313224307-5b5fc417d966/go.mod h1:5s5p/sMJ6sNsFl6uCh85lkFGV8kLuIYJCRJLavVJwvg= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= github.com/mdlayher/raw v0.0.0-20190313224157-43dbcdd7739d/go.mod h1:r1fbeITl2xL/zLbVnNHFyOzQJTgr/3fpf1lJX/cjzR8= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI= github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU= github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= vip-manager-1.0.1/iphlpapi/000077500000000000000000000000001375274636300155455ustar00rootroot00000000000000vip-manager-1.0.1/iphlpapi/iphlpapi.go000066400000000000000000000003711375274636300177030ustar00rootroot00000000000000package iphlpapi //sys AddIPAddress(Address uint32, IpMask uint32, IfIndex uint32, NTEContext *uint32, NTEInstance *uint32) (errcode error) = iphlpapi.AddIPAddress //sys DeleteIPAddress(NTEContext uint32) (errcode error) = iphlpapi.DeleteIPAddress vip-manager-1.0.1/iphlpapi/mksyscall.go000066400000000000000000000004411375274636300200750ustar00rootroot00000000000000// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build generate package iphlpapi //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output ziphlapi.go iphlpapi.go vip-manager-1.0.1/iphlpapi/ziphlapi.go000066400000000000000000000026631375274636300177230ustar00rootroot00000000000000// Code generated by 'go generate'; DO NOT EDIT. package iphlpapi import ( "syscall" "unsafe" "golang.org/x/sys/windows" ) var _ unsafe.Pointer // Do the interface allocations only once for common // Errno values. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return nil case errnoERROR_IO_PENDING: return errERROR_IO_PENDING } // TODO: add more here, after collecting data on the common // error values see on Windows. (perhaps when running // all.bat?) return e } var ( modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") procAddIPAddress = modiphlpapi.NewProc("AddIPAddress") procDeleteIPAddress = modiphlpapi.NewProc("DeleteIPAddress") ) func AddIPAddress(Address uint32, IpMask uint32, IfIndex uint32, NTEContext *uint32, NTEInstance *uint32) (errcode error) { r0, _, _ := syscall.Syscall6(procAddIPAddress.Addr(), 5, uintptr(Address), uintptr(IpMask), uintptr(IfIndex), uintptr(unsafe.Pointer(NTEContext)), uintptr(unsafe.Pointer(NTEInstance)), 0) if r0 != 0 { errcode = syscall.Errno(r0) } return } func DeleteIPAddress(NTEContext uint32) (errcode error) { r0, _, _ := syscall.Syscall(procDeleteIPAddress.Addr(), 1, uintptr(NTEContext), 0, 0) if r0 != 0 { errcode = syscall.Errno(r0) } return } vip-manager-1.0.1/ipmanager/000077500000000000000000000000001375274636300157025ustar00rootroot00000000000000vip-manager-1.0.1/ipmanager/basicConfigurer.go000066400000000000000000000027321375274636300213420ustar00rootroot00000000000000package ipmanager import ( "errors" "net" "strings" arp "github.com/mdlayher/arp" ) // BasicConfigurer can be used to enable vip-management on nodes // that handle their own network connection, in setups where it is // sufficient to add the virtual ip using `ip addr add ...` . // After adding the virtual ip to the specified interface, // a gratuitous ARP package is sent out to update the tables of // nearby routers and other devices. type BasicConfigurer struct { *IPConfiguration arpClient *arp.Client ntecontext uint32 //used by Windows to delete IP address } func newBasicConfigurer(config *IPConfiguration) (*BasicConfigurer, error) { c := &BasicConfigurer{IPConfiguration: config, ntecontext: 0} if c.Iface.HardwareAddr == nil || c.Iface.HardwareAddr.String() == "00:00:00:00:00:00" { return nil, errors.New(`Cannot run vip-manager on the loopback device as its hardware address is the local address (00:00:00:00:00:00), which prohibits sending of gratuitous ARP messages`) } return c, nil } // queryAddress returns if the address is assigned func (c *BasicConfigurer) queryAddress() bool { iface, err := net.InterfaceByName(c.Iface.Name) if err != nil { return false } addresses, err := iface.Addrs() if err != nil { return false } for _, address := range addresses { if strings.Contains(address.String(), c.VIP.String()) { return true } } return false } func (c *BasicConfigurer) cleanupArp() { if c.arpClient != nil { c.arpClient.Close() } } vip-manager-1.0.1/ipmanager/basicConfigurer_linux.go000066400000000000000000000104521375274636300225570ustar00rootroot00000000000000package ipmanager import ( "log" "net" "os/exec" "time" arp "github.com/mdlayher/arp" ) const ( arpRequestOp = 1 arpReplyOp = 2 ) var ( ethernetBroadcast = net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff} ) // configureAddress assigns virtual IP address func (c *BasicConfigurer) configureAddress() bool { if c.arpClient == nil { err := c.createArpClient() if err != nil { log.Fatalf("Couldn't create an Arp client: %s", err) } } log.Printf("Configuring address %s on %s", c.getCIDR(), c.Iface.Name) result := c.runAddressConfiguration("add") if result { // For now it is save to say that also working even if a // gratuitous arp message could not be send but logging an // errror should be enough. _ = c.arpSendGratuitous() } return result } // deconfigureAddress drops virtual IP address func (c *BasicConfigurer) deconfigureAddress() bool { log.Printf("Removing address %s on %s", c.getCIDR(), c.Iface.Name) return c.runAddressConfiguration("delete") } func (c *BasicConfigurer) runAddressConfiguration(action string) bool { cmd := exec.Command("ip", "addr", action, c.getCIDR(), "dev", c.Iface.Name) output, err := cmd.CombinedOutput() switch err.(type) { case *exec.ExitError: log.Printf("Got error %s", output) return false } if err != nil { log.Printf("Error running ip address %s %s on %s: %s", action, c.VIP, c.Iface.Name, err) return false } return true } func (c *BasicConfigurer) createArpClient() error { var err error var arpClient *arp.Client for i := 0; i < c.RetryNum; i++ { arpClient, err = arp.Dial(&c.Iface) if err != nil { log.Printf("Problems with producing the arp client: %s", err) } else { break } time.Sleep(time.Duration(c.RetryAfter) * time.Millisecond) } if err != nil { log.Print("too many retries") return err } c.arpClient = arpClient return nil } // sends a gratuitous ARP request and reply func (c *BasicConfigurer) arpSendGratuitous() error { /* While RFC 2002 does not say whether a gratuitous ARP request or reply is preferred * to update ones neighbours' MAC tables, the Wireshark Wiki recommends sending both. * https://wiki.wireshark.org/Gratuitous_ARP * This site also recommends sending a reply, as requests might be ignored by some hardware: * https://support.citrix.com/article/CTX112701 */ gratuitousReplyPackage, err := arp.NewPacket( arpReplyOp, c.Iface.HardwareAddr, c.VIP, c.Iface.HardwareAddr, c.VIP, ) if err != nil { log.Printf("Gratuitous arp reply package is malformed: %s", err) return err } /* RFC 2002 specifies (in section 4.6) that a gratuitous ARP request * should "not set" the target Hardware Address (THA). * Since the arp package offers no option to leave the THA out, we specify the Zero-MAC. * If parsing that fails for some reason, we'll just use the local interface's address. * The field is probably ignored by the receivers' implementation anyway. */ arpRequestDestMac, err := net.ParseMAC("00:00:00:00:00:00") if err != nil { // not entirely RFC-2002 conform but better then nothing. arpRequestDestMac = c.Iface.HardwareAddr } gratuitousRequestPackage, err := arp.NewPacket( arpRequestOp, c.Iface.HardwareAddr, c.VIP, arpRequestDestMac, c.VIP, ) if err != nil { log.Printf("Gratuitous arp request package is malformed: %s", err) return err } for i := 0; i < c.RetryNum; i++ { errReply := c.arpClient.WriteTo(gratuitousReplyPackage, ethernetBroadcast) if err != nil { log.Printf("Couldn't write to the arpClient: %s", errReply) } else { log.Println("Sent gratuitous ARP reply") } errRequest := c.arpClient.WriteTo(gratuitousRequestPackage, ethernetBroadcast) if err != nil { log.Printf("Couldn't write to the arpClient: %s", errRequest) } else { log.Println("Sent gratuitous ARP request") } if errReply != nil || errRequest != nil { /* If something went wrong while sending the packages, we'll recreate the ARP client for the next try, * to avoid having a stale client that gives "network is down" error. */ err = c.createArpClient() } else { //TODO: think about whether to leave this out to achieve simple repeat sending of GARP packages break } time.Sleep(time.Duration(c.RetryAfter) * time.Millisecond) } if err != nil { log.Print("too many retries") return err } return nil } vip-manager-1.0.1/ipmanager/basicConfigurer_windows.go000066400000000000000000000023001375274636300231030ustar00rootroot00000000000000package ipmanager import ( "encoding/binary" "log" "net" "github.com/cybertec-postgresql/vip-manager/iphlpapi" ) // configureAddress assigns virtual IP address func (c *BasicConfigurer) configureAddress() bool { log.Printf("Configuring address %s on %s", c.getCIDR(), c.Iface.Name) var ( ip uint32 = binary.LittleEndian.Uint32(c.VIP.To4()) mask uint32 = binary.LittleEndian.Uint32(c.Netmask) nteinstance uint32 ) iface, err := net.InterfaceByName(c.Iface.Name) if err != nil { log.Printf("Got error: %v", err) return false } err = iphlpapi.AddIPAddress(ip, mask, uint32(iface.Index), &c.ntecontext, &nteinstance) if err != nil { log.Printf("Got error: %v", err) return false } // For now it is save to say that also working even if a // gratuitous arp message could not be send but logging an // errror should be enough. //_ = c.ARPSendGratuitous() return true } // deconfigureAddress drops virtual IP address func (c *BasicConfigurer) deconfigureAddress() bool { log.Printf("Removing address %s on %s", c.getCIDR(), c.Iface.Name) err := iphlpapi.DeleteIPAddress(c.ntecontext) if err != nil { log.Printf("Got error: %v", err) return false } return true } vip-manager-1.0.1/ipmanager/hetznerConfigurer.go000066400000000000000000000167551375274636300217520ustar00rootroot00000000000000package ipmanager import ( "bufio" "encoding/json" "errors" "log" "net" "os" "os/exec" "time" ) const ( unknown = iota // c0 == 0 configured = iota // c1 == 1 released = iota // c2 == 2 ) // The HetznerConfigurer can be used to enable vip-management on nodes // rented in a Hetzner Datacenter. // Since Hetzner provides an API that handles failover-ip routing, // this API is used to manage the vip, whenever hostintype `hetzner` is set. type HetznerConfigurer struct { *IPConfiguration cachedState int lastAPICheck time.Time verbose bool } func newHetznerConfigurer(config *IPConfiguration, verbose bool) (*HetznerConfigurer, error) { c := &HetznerConfigurer{ IPConfiguration: config, cachedState: unknown, lastAPICheck: time.Unix(0, 0), verbose: verbose} return c, nil } /** * In order to tell the Hetzner API to route the failover-ip to * this machine, we must attach our own IP address to the API request. */ func getOutboundIP() net.IP { conn, err := net.Dial("udp", "8.8.8.8:80") if err != nil || conn == nil { log.Println("error dialing 8.8.8.8 to retrieve preferred outbound IP", err) return nil } defer conn.Close() localAddr := conn.LocalAddr().(*net.UDPAddr) return localAddr.IP } func (c *HetznerConfigurer) curlQueryFailover(post bool) (string, error) { /** * The credentials for the API are loaded from a file stored in /etc/hetzner . */ //TODO: make credentialsFile dynamically changeable? credentialsFile := "/etc/hetzner" f, err := os.Open(credentialsFile) if err != nil { log.Println("can't open passwordfile", err) return "", err } defer f.Close() /** * The retrieval of username and password from the file is rather static, * so the credentials file must conform to the offsets down below perfectly. */ var user string var password string scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() switch line[:4] { case "user": user = line[6 : len(line)-1] case "pass": password = line[6 : len(line)-1] } } if user == "" || password == "" { log.Println("Couldn't retrieve username or password from file", credentialsFile) return "", errors.New("Couldn't retrieve username or password from file") } /** * As Hetzner API only allows IPv4 connections, we rely on curl * instead of GO's own http package, * as selecting IPv4 transport there doesn't seem trivial. * * If post is set to true, a failover will be triggered. * If it is set to false, the current state (i.e. route) * for the failover-ip will be retrieved. */ var cmd *exec.Cmd if post { myOwnIP := getOutboundIP() if myOwnIP == nil { log.Printf("Error determining this machine's IP address.") return "", errors.New("Error determining this machine's IP address") } log.Printf("my_own_ip: %s\n", myOwnIP.String()) cmd = exec.Command("curl", "--ipv4", "-u", user+":"+password, "https://robot-ws.your-server.de/failover/"+c.IPConfiguration.VIP.String(), "-d", "active_server_ip="+myOwnIP.String()) if c.verbose { log.Printf("%s %s %s '%s' %s %s %s", "curl", "--ipv4", "-u", user+":XXXXXX", "https://robot-ws.your-server.de/failover/"+c.IPConfiguration.VIP.String(), "-d", "active_server_ip="+myOwnIP.String()) } } else { cmd = exec.Command("curl", "--ipv4", "-u", user+":"+password, "https://robot-ws.your-server.de/failover/"+c.IPConfiguration.VIP.String()) if c.verbose { log.Printf("%s %s %s %s %s", "curl", "--ipv4", "-u", user+":XXXXXX", "https://robot-ws.your-server.de/failover/"+c.IPConfiguration.VIP.String()) } } out, err := cmd.Output() if err != nil { return "", err } retStr := string(out[:]) return retStr, nil } /** * This function is used to parse the response which comes from the * curlQueryFailover function and in turn from the curl calls to the API. */ func (c *HetznerConfigurer) getActiveIPFromJSON(str string) (net.IP, error) { var f map[string]interface{} if c.verbose { log.Printf("JSON response: %s\n", str) } err := json.Unmarshal([]byte(str), &f) if err != nil { log.Println(err) return nil, err } if f["error"] != nil { errormap := f["error"].(map[string]interface{}) log.Printf("There was an error accessing the Hetzner API!\n"+ " status: %f\n code: %s\n message: %s\n", errormap["status"].(float64), errormap["code"].(string), errormap["message"].(string)) return nil, errors.New("Hetzner API returned error response") } if f["failover"] != nil { failovermap := f["failover"].(map[string]interface{}) ip := failovermap["ip"].(string) netmask := failovermap["netmask"].(string) serverIP := failovermap["server_ip"].(string) serverNumber := failovermap["server_number"].(float64) activeServerIP := failovermap["active_server_ip"].(string) log.Println("Result of the failover query was: ", "failover-ip=", ip, "netmask=", netmask, "server_ip=", serverIP, "server_number=", serverNumber, "active_server_ip=", activeServerIP, ) return net.ParseIP(activeServerIP), nil } return nil, errors.New("why did we end up here?") } func (c *HetznerConfigurer) queryAddress() bool { if (time.Since(c.lastAPICheck) / time.Hour) > 1 { /**We need to recheck the status! * Don't check too often because of stupid API rate limits */ log.Println("Cached state was too old.") c.cachedState = unknown } else { /** no need to check, we can use "cached" state if set. * if it is set to UNKNOWN, a check will be done. */ if c.cachedState == configured { return true } else if c.cachedState == released { return false } } str, err := c.curlQueryFailover(false) if err != nil { //TODO c.cachedState = unknown } else { c.lastAPICheck = time.Now() } currentFailoverDestinationIP, err := c.getActiveIPFromJSON(str) if err != nil { //TODO c.cachedState = unknown } if currentFailoverDestinationIP.Equal(getOutboundIP()) { //We "are" the current failover destination. c.cachedState = configured return true } c.cachedState = released return false } func (c *HetznerConfigurer) configureAddress() bool { //log.Printf("Configuring address %s on %s", m.GetCIDR(), m.iface.Name) return c.runAddressConfiguration("set") } func (c *HetznerConfigurer) deconfigureAddress() bool { //The address doesn't need deconfiguring since Hetzner API // is used to point the VIP address somewhere else. c.cachedState = released return true } func (c *HetznerConfigurer) runAddressConfiguration(action string) bool { str, err := c.curlQueryFailover(true) if err != nil { log.Printf("Error while configuring Hetzner failover-ip! Error message: %s", err) c.cachedState = unknown return false } currentFailoverDestinationIP, err := c.getActiveIPFromJSON(str) if err != nil { c.cachedState = unknown return false } c.lastAPICheck = time.Now() if currentFailoverDestinationIP.Equal(getOutboundIP()) { //We "are" the current failover destination. log.Printf("Failover was successfully executed!") c.cachedState = configured return true } log.Printf("The failover command was issued, but the current Failover destination (%s) is different from what it should be (%s).", currentFailoverDestinationIP.String(), getOutboundIP().String()) //Something must have gone wrong while trying to switch IP's... c.cachedState = unknown return false } func (c *HetznerConfigurer) cleanupArp() { // dummy function as the usage of interfaces requires us to have this function. // It is sufficient for the leader to tell Hetzner to switch the IP, no cleanup needed. } vip-manager-1.0.1/ipmanager/ip_configuration.go000066400000000000000000000010441375274636300215670ustar00rootroot00000000000000package ipmanager import ( "fmt" "net" ) // IPConfiguration holds the configuration for VIP manager type IPConfiguration struct { VIP net.IP Netmask net.IPMask Iface net.Interface RetryNum int RetryAfter int } // getCIDR returns the CIDR composed from the given address and mask func (c *IPConfiguration) getCIDR() string { return fmt.Sprintf("%s/%d", c.VIP.String(), netmaskSize(c.Netmask)) } func netmaskSize(mask net.IPMask) int { ones, bits := mask.Size() if bits == 0 { panic("Invalid mask") } return ones } vip-manager-1.0.1/ipmanager/ip_manager.go000066400000000000000000000051001375274636300203270ustar00rootroot00000000000000package ipmanager import ( "context" "log" "sync" "time" ) type ipConfigurer interface { queryAddress() bool configureAddress() bool deconfigureAddress() bool getCIDR() string cleanupArp() } // IPManager implements the main functionality of the VIP manager type IPManager struct { configurer ipConfigurer states <-chan bool currentState bool stateLock sync.Mutex recheck *sync.Cond } // NewIPManager returns a new instance of IPManager func NewIPManager(hostingType string, config *IPConfiguration, states <-chan bool, verbose bool) (m *IPManager, err error) { m = &IPManager{ states: states, currentState: false, } m.recheck = sync.NewCond(&m.stateLock) switch hostingType { case "hetzner": m.configurer, err = newHetznerConfigurer(config, verbose) if err != nil { return nil, err } case "basic": fallthrough default: m.configurer, err = newBasicConfigurer(config) } if err != nil { m = nil } return } func (m *IPManager) applyLoop(ctx context.Context) { timeout := 0 for { // Check if we should exit select { case <-ctx.Done(): m.configurer.deconfigureAddress() return case <-time.After(time.Duration(timeout) * time.Second): actualState := m.configurer.queryAddress() m.stateLock.Lock() desiredState := m.currentState log.Printf("IP address %s state is %t, desired %t", m.configurer.getCIDR(), actualState, desiredState) if actualState != desiredState { m.stateLock.Unlock() var configureState bool if desiredState { configureState = m.configurer.configureAddress() } else { configureState = m.configurer.deconfigureAddress() } if !configureState { log.Printf("Error while acquiring virtual ip for this machine") //Sleep a little bit to avoid busy waiting due to the for loop. timeout = 10 } else { timeout = 0 } } else { // Wait for notification m.recheck.Wait() // Want to query actual state anyway, so unlock m.stateLock.Unlock() } } } } // SyncStates implements states synchronization func (m *IPManager) SyncStates(ctx context.Context, states <-chan bool) { ticker := time.NewTicker(10 * time.Second) var wg sync.WaitGroup wg.Add(1) go func() { m.applyLoop(ctx) wg.Done() }() for { select { case newState := <-states: m.stateLock.Lock() if m.currentState != newState { m.currentState = newState m.recheck.Broadcast() } m.stateLock.Unlock() case <-ticker.C: m.recheck.Broadcast() case <-ctx.Done(): m.recheck.Broadcast() wg.Wait() m.configurer.cleanupArp() return } } } vip-manager-1.0.1/main.go000066400000000000000000000040341375274636300152130ustar00rootroot00000000000000package main import ( "context" "fmt" // "flag" "log" "net" "os" "os/signal" "sync" "github.com/cybertec-postgresql/vip-manager/checker" "github.com/cybertec-postgresql/vip-manager/ipmanager" "github.com/cybertec-postgresql/vip-manager/vipconfig" ) var ( // vip-manager version definition version string = "1.0.1" ) func getMask(vip net.IP, mask int) net.IPMask { if mask > 0 || mask < 33 { return net.CIDRMask(mask, 32) } return vip.DefaultMask() } func getNetIface(iface string) *net.Interface { netIface, err := net.InterfaceByName(iface) if err != nil { log.Fatalf("Obtaining the interface raised an error: %s", err) } return netIface } func main() { if (len(os.Args) > 1) && (os.Args[1] == "--version") { // log.Print("version " + version) // return nil, nil // } fmt.Printf("version: %s\n", version) return } conf, err := vipconfig.NewConfig() if err != nil { log.Fatal(err) } lc, err := checker.NewLeaderChecker(conf) if err != nil { log.Fatalf("Failed to initialize leader checker: %s", err) } vip := net.ParseIP(conf.IP) vipMask := getMask(vip, conf.Mask) netIface := getNetIface(conf.Iface) states := make(chan bool) manager, err := ipmanager.NewIPManager( conf.HostingType, &ipmanager.IPConfiguration{ VIP: vip, Netmask: vipMask, Iface: *netIface, RetryNum: conf.RetryNum, RetryAfter: conf.RetryAfter, }, states, conf.Verbose, ) if err != nil { log.Fatalf("Problems with generating the virtual ip manager: %s", err) } mainCtx, cancel := context.WithCancel(context.Background()) go func() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c log.Print("Received exit signal") cancel() }() var wg sync.WaitGroup wg.Add(1) go func() { err := lc.GetChangeNotificationStream(mainCtx, states) if err != nil && err != context.Canceled { log.Fatalf("Leader checker returned the following error: %s", err) } wg.Done() }() wg.Add(1) go func() { manager.SyncStates(mainCtx, states) wg.Done() }() wg.Wait() } vip-manager-1.0.1/package/000077500000000000000000000000001375274636300153325ustar00rootroot00000000000000vip-manager-1.0.1/package/DEBIAN/000077500000000000000000000000001375274636300162545ustar00rootroot00000000000000vip-manager-1.0.1/package/DEBIAN/changelog000066400000000000000000000044301375274636300201270ustar00rootroot00000000000000vip-manager (1.0-1) unstable; urgency=low * support managing VIP on windows (pavlo.golub@cybertec.at) * add TLS support for etcd connections (gandalfmagic@libero.it) * enable TLS configuration from cli flags and from yaml file (gandalfmagic@libero.it) * improved gratuitous arp request and reply sending (julian.markwort@cybertec.at) * unified config handling - flags, YAML file, environment variables (julian.markwort@cybertec.at) * add verbose logging to hetzner manager code (tpo@sourcepole.ch) * switched to go modules for dependency management (pavlo.golub@cybertec.at) * fix main loop refusing to exit (igor.yanchenko@cybertec.at) -- Julian Markwort Sun, 17 Nov 2019 23:52:12 +0100 vip-manager (0.6-1) unstable; urgency=low * switched to yaml file for config, still supporting all command line options from previous releases. * Introduced unified config management in the vipconfig source directory * Enabled consul_leader_checker to use tokens -- Julian Markwort Tue, 04 Sep 2018 14:40:00 +0300 vip-manager (0.5-1) unstable; urgency=low * Support Hetzner API for failover-ip configuration. * Enable etcd-checker to use username and password. -- Julian Markwort Tue, 04 Sep 2018 14:40:00 +0300 vip-manager (0.4-1) unstable; urgency=low * Fix SystemD service file and add netmask parameter to it. * Remove SystemV init script from packaging. Apparently debians systemd does not like it. -- Ants Aasma Thu, 19 Apr 2018 01:18:00 +0300 vip-manager (0.3-0) unstable; urgency=low * The vip-manager is sending a gratuitous arp if it takes over the vip * It will be no longer checked if another server has the vip because it makes no difference as long as the new server sends a gratuitous arp message * Fixed the vip-manager packaging -- David Leib Wed, 25 Oct 2017 14:25:44 +0300 vip-manager (0.2-0) unstable; urgency=low * The vip-manager is not failing anymore if the keystorage is not available -- David Leib Thu, 21 Sep 2017 12:23:34 +0300 vip-manager (0.1-1) unstable; urgency=low * Initial release -- Ants Aasma Tue, 25 Apr 2017 15:58:29 +0300 vip-manager-1.0.1/package/DEBIAN/compat000066400000000000000000000000021375274636300174520ustar00rootroot000000000000009 vip-manager-1.0.1/package/DEBIAN/copyright000066400000000000000000000030001375274636300202000ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: vip-manager Source: Files: * Copyright: 2017-2019 Ants Aasma License: BSD-2-clause All rights reserved. . Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. vip-manager-1.0.1/package/DEBIAN/rules000077500000000000000000000000371375274636300173340ustar00rootroot00000000000000#!/usr/bin/make -f %: dh $@ vip-manager-1.0.1/package/DEBIAN/source/000077500000000000000000000000001375274636300175545ustar00rootroot00000000000000vip-manager-1.0.1/package/DEBIAN/source/format000066400000000000000000000000141375274636300207620ustar00rootroot000000000000003.0 (quilt) vip-manager-1.0.1/package/config/000077500000000000000000000000001375274636300165775ustar00rootroot00000000000000vip-manager-1.0.1/package/config/vip-manager.default000066400000000000000000000013361375274636300223560ustar00rootroot00000000000000# The keys below are mandatory #VIP_IP="10.1.2.3" # Netmask for IP address #VIP_MASK=24 # Just use the normal interface name of the primary network interface #VIP_IFACE="eth0" # This must match scope from Patroni postgres.yml #VIP_KEY="/service/batman/leader" # This value must match the value used in Patroni postgres.yml #VIP_HOST="serverX" # Specify the type of endpoint (etcd|consul) #VIP_TYPE="etcd" #VIP_ENDPOINT="http://10.1.2.3:2379" # The keys below are optional #VIP_HOSTINGTYPE="basic" #VIP_ETCD_USER #VIP_ETCD_PASSWORD # These keys are optional if VIP_TYPE="etcd" is chosen # # #VIP_ETCD_CA_FILE="/etc/etcd/ca.cert.pem" #VIP_ETCD_CERT_FILE="/etc/etcd/host.cert.pem" #VIP_ETCD_KEY_FILE="/etc/etcd/host.key.pem" vip-manager-1.0.1/package/scripts/000077500000000000000000000000001375274636300170215ustar00rootroot00000000000000vip-manager-1.0.1/package/scripts/vip-manager000066400000000000000000000116361375274636300211610ustar00rootroot00000000000000#! /usr/bin/env bash # chkconfig: 2345 99 01 # description: Vip-manager daemon ### BEGIN INIT INFO # Provides: vip-manager # Required-Start: $network $local_fs $remote_fs # Required-Stop: $network $local_fs $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start vip-manager at boot time ### END INIT INFO # Command-line options that will be set with /etc/default/vip-manager. VIP_OPTS="" USER=root GROUP=root if [ -r /lib/lsb/init-functions ]; then source /lib/lsb/init-functions fi DEFAULT=/etc/default/vip-manager if [ -r $DEFAULT ]; then source $DEFAULT fi if [ -z "$VIP_IP" ]; then VIP_OPTS="$VIP_OPTS -ip=$VIP_IP" fi if [ -z "$VIP_IFACE" ]; then VIP_OPTS="$VIP_OPTS -iface=$VIP_IFACE" fi if [ -z "$VIP_KEY" ]; then VIP_OPTS="$VIP_OPTS -key=$VIP_KEY" fi if [ -z "$VIP_ETCD_USER" ]; then VIP_OPTS="$VIP_OPTS -etcd_user=$VIP_ETCD_USER" fi if [ -z "$VIP_ETCD_PASSWORD" ]; then VIP_OPTS="$VIP_OPTS -etcd_password=$VIP_ETCD_PASSWORD" fi if [ -z "$VIP_HOST" ]; then VIP_OPTS="$VIP_OPTS -host=$VIP_HOST" fi if [ -z "$VIP_TYPE" ]; then VIP_OPTS="$VIP_OPTS -type=$VIP_TYPE" fi if [ -z "$VIP_ENDPOINT" ]; then VIP_OPTS="$VIP_OPTS -endpoint=$VIP_ENDPOINT" fi if [ -z "$VIP_MASK" ]; then VIP_OPTS="$VIP_OPTS -mask=$VIP_MASK" fi if [ -z "$VIP_HOSTINGTYPE" ]; then VIP_OPTS="$VIP_OPTS -hostingtype=$VIP_HOSTINGTYPE" fi if [ -z "$STDOUT" ]; then STDOUT=/dev/null fi if [ ! -f "$STDOUT" ]; then mkdir -p `dirname $STDOUT` fi if [ -z "$STDERR" ]; then STDERR=/var/log/vipmanager/vipmanager.log fi if [ ! -f "$STDERR" ]; then mkdir -p `dirname $STDERR` fi function pidofproc { if [ $# -ne 3 ]; then echo "Expected three arguments, e.g. $0 -p pidfile daemon-name" fi if [ ! -f "$2" ]; then return 1 fi local pidfile=`cat $2` if [ "x$pidfile" == "x" ]; then return 1 fi if ps --pid "$pidfile" | grep -q $(basename $3); then return 0 fi return 1 } function killproc { if [ $# -ne 3 ]; then echo "Expected three arguments, e.g. $0 -p pidfile signal" fi pid=`cat $2` kill -s $3 $pid } function log_failure_msg { echo "$@" "[ FAILED ]" } function log_success_msg { echo "$@" "[ OK ]" } # Process name ( For display ) name=vipmanager # Daemon name, where is the actual executable daemon=/usr/bin/vip-manager # pid file for the daemon pidfile=/var/run/vip-manager.pid piddir=`dirname $pidfile` if [ ! -d "$piddir" ]; then mkdir -p $piddir chown $USER:$GROUP $piddir fi # If the daemon is not there, then exit. [ -x $daemon ] || exit 5 case $1 in start) # Checked the PID file exists and check the actual status of process if [ -e $pidfile ]; then pidofproc -p $pidfile $daemon > /dev/null 2>&1 && status="0" || status="$?" # If the status is SUCCESS then don't need to start again. if [ "x$status" = "x0" ]; then log_failure_msg "$name process is running" exit 0 # Exit fi fi log_success_msg "Starting the process" "$name" if command -v startproc >/dev/null; then startproc -u "$USER" -g "$GROUP" -p "$pidfile" -q -- "$daemon" $VIP_OPTS elif which start-stop-daemon > /dev/null 2>&1; then start-stop-daemon --chuid $USER:$GROUP --start --quiet --pidfile $pidfile --exec $daemon -- $VIP_OPTS >>$STDOUT 2>>$STDERR & else su -s /bin/sh -c "nohup $daemon $VIP_OPTS >>$STDOUT 2>>$STDERR &" $USER fi log_success_msg "$name process was started" ;; stop) # Stop the daemon. if [ -e $pidfile ]; then pidofproc -p $pidfile $daemon > /dev/null 2>&1 && status="0" || status="$?" if [ "$status" = 0 ]; then if killproc -p $pidfile SIGTERM && /bin/rm -rf $pidfile; then log_success_msg "$name process was stopped" else log_failure_msg "$name failed to stop service" fi fi else log_failure_msg "$name process is not running" fi ;; restart|force-reload) # Restart the daemon. $0 stop && sleep 2 && $0 start ;; status) # Check the status of the process. if [ -e $pidfile ]; then if pidofproc -p $pidfile $daemon > /dev/null; then log_success_msg "$name Process is running" exit 0 else log_failure_msg "$name Process is not running" exit 1 fi else log_failure_msg "$name Process is not running" exit 3 fi ;; version) $daemon version ;; *) # For invalid arguments, print the usage message. echo "Usage: $0 {start|stop|restart|status|version}" exit 2 ;; esac vip-manager-1.0.1/package/scripts/vip-manager.service000066400000000000000000000010531375274636300226100ustar00rootroot00000000000000# This is an example of a systemD config file for vip-manager. # You can copy it to "/etc/systemd/system/vip-manager.service", adjust as necessary and then call # systemctl daemon-reload && systemctl start vip-manager && systemctl enable vip-manager # to start and also enable auto-start after reboot. [Unit] Description=Manages Virtual IP for Patroni After=network-online.target Before=patroni.service [Service] Type=simple ExecStart=/usr/bin/vip-manager --config=/etc/default/vip-manager.yml Restart=on-failure [Install] WantedBy=multi-user.target vip-manager-1.0.1/test/000077500000000000000000000000001375274636300147165ustar00rootroot00000000000000vip-manager-1.0.1/test/behaviour_test.sh000077500000000000000000000040661375274636300203060ustar00rootroot00000000000000#!/bin/sh set -eu -o pipefail RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' # No Color # testing parameters dev=`ip link show | grep -B1 ether | cut -d ":" -f2 | head -n1 | cut -d " " -f2` vip=10.0.2.123 #cleanup function cleanup { if test -f .ncatPid then kill `cat .ncatPid` 2> /dev/null || true rm .ncatPid fi if test -f .vipPid then kill `cat .vipPid` 2> /dev/null || true rm .vipPid fi if test -f .etcdPid then kill `cat .etcdPid` 2> /dev/null || true rm .etcdPid fi if test -f .failed then echo -e "${RED}### Some tests failed! ###${NC}" rm .failed fi } trap cleanup EXIT # prerequisite test 0: vip should not yet be registered ! ip address show dev $dev | grep $vip # run etcd with podman/docker maybe? # podman rm etcd || true # podman run -d --name etcd -p 2379:2379 -e "ETCD_ENABLE_V2=true" -e "ALLOW_NONE_AUTHENTICATION=yes" bitnami/etcd # run etcd locally maybe? #etcd --enable-v2 & #echo $! > .etcdPid sleep 2 # simulate server, e.g. postgres ncat -vlk 0.0.0.0 12345 -e "/bin/echo $HOSTNAME" & echo $! > .ncatPid curl -s -XDELETE http://127.0.0.1:2379/v2/keys/service/pgcluster/leader ||true touch .failed ./vip-manager --interface $dev --ip $vip --netmask 32 --trigger-key service/pgcluster/leader --trigger-value $HOSTNAME & #2>&1 & echo $! > .vipPid sleep 2 # test 1: vip should still not be registered ! ip address show dev $dev | grep $vip # simulate patroni member promoting to leader curl -s -XPUT http://127.0.0.1:2379/v2/keys/service/pgcluster/leader -d value=$HOSTNAME | jq . sleep 2 # test 2: vip should now be registered ip address show dev $dev | grep $vip ncat -vzw 1 $vip 12345 # simulate leader change curl -s -XPUT http://127.0.0.1:2379/v2/keys/service/pgcluster/leader -d value=0xGARBAGE | jq . sleep 2 # test 3: vip should be deregistered again ! ip address show dev $dev | grep $vip ! ncat -vzw 1 $vip 12345 rm .failed echo -e "${GREEN}### You've reached the end of the script, all \"tests\" have successfully been passed! ###${NC}" vip-manager-1.0.1/test/cacert_test.sh000077500000000000000000000044031375274636300175560ustar00rootroot00000000000000#!/bin/sh set -eu -o pipefail RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' # No Color # testing parameters dev=`ip link show | grep -B1 ether | cut -d ":" -f2 | head -n1 | cut -d " " -f2` vip=10.0.2.123 #cleanup function cleanup { if test -f .ncatPid then kill `cat .ncatPid` 2> /dev/null || true rm .ncatPid fi if test -f .vipPid then kill `cat .vipPid` 2> /dev/null || true rm .vipPid #rm vip-manager.log fi if test -f .etcdPid then kill `cat .etcdPid` 2> /dev/null || true rm .etcdPid fi if test -f .failed then echo -e "${RED}### Some tests failed! ###${NC}" rm .failed fi podman stop etcd } trap cleanup EXIT # prerequisite test 0: vip should not yet be registered ! ip address show dev $dev | grep $vip # run etcd with podman/docker maybe? podman run --rm -d --name etcd -p 2379:2379 -e "ETCD_ENABLE_V2=true" -e "ALLOW_NONE_AUTHENTICATION=yes" -v `pwd`/test/certs/:/certs:Z quay.io/coreos/etcd /usr/local/bin/etcd --cert-file=/certs/etcd_server.crt --key-file=/certs/etcd_server.key --listen-client-urls https://127.0.0.1:2379 --advertise-client-urls https://127.0.0.1:2379 sleep 2 # simulate server, e.g. postgres ncat -vlk 0.0.0.0 12345 -e "/bin/echo $HOSTNAME" & echo $! > .ncatPid curl -s --cacert test/certs/etcd_server_ca.crt -XDELETE https://127.0.0.1:2379/v2/keys/service/pgcluster/leader ||true touch .failed ./vip-manager --etcd-ca-file test/certs/etcd_server_ca.crt --dcs-endpoints https://127.0.0.1:2379 --interface $dev --ip $vip --netmask 32 --trigger-key service/pgcluster/leader --trigger-value $HOSTNAME &> vip-manager.log & echo $! > .vipPid sleep 2 # test 1: vip should still not be registered ! ip address show dev $dev | grep $vip # simulate patroni member promoting to leader curl -s --cacert test/certs/etcd_server_ca.crt -XPUT https://127.0.0.1:2379/v2/keys/service/pgcluster/leader -d value=$HOSTNAME | jq . sleep 2 # we're just checking whether vip-manager picked up the change, for some reason, we can't run an elevated container of quay.io/coreos/etcd grep 'state is false, desired true' vip-manager.log rm .failed echo -e "${GREEN}### You've reached the end of the script, all \"tests\" have successfully been passed! ###${NC}" vip-manager-1.0.1/test/certs/000077500000000000000000000000001375274636300160365ustar00rootroot00000000000000vip-manager-1.0.1/test/certs/etcd_client.crt000066400000000000000000000037751375274636300210410ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFujCCA6KgAwIBAgIUSVGhOPULK6WMRVVq9zQ5Dm4bBv0wDQYJKoZIhvcNAQEL BQAwIjEgMB4GA1UEAwwXaS5hbS50aGUuZXRjZC5zZXJ2ZXIuY2EwHhcNMjAwNDAz MTc0NTI1WhcNMzAwNDAxMTc0NTI1WjAeMRwwGgYDVQQDDBNjZW50b3Mtc2VydmVy LTctMTAxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtUEiDqKo5UPq VvrJQSB8lYgYw371/wJos+EHsI1H6nmnzFtMYOpwr9OBsu5VHThyNd+hAd7ktLaB wR3NeRUsCljiVcle41k6chlTDEy82UUbuL3wQHh52lMqb4NSC8NnGyEp8rp76Db/ +WiFaQrn00er1buW872WV4gsKBiXlJZoucS7EkPOoLwQ9AmYKv4kBIhU9r1rvMn8 PQDhHRHzHlnysk9DLdpi+HmsBS0/qYQxPh4Chq9fSnU8eyXxR2HVPwuGBs2kaTcb VYsFGOjn+86PgiXBvw2W4ogaDoDaxt91TzsCrU4dUCnfKSo8Qb5TpuZDRfLxdhZc jfYOwqkoVUAmPUtSdyDk1u5fTM4Po5RZlyVf9Ucm7M/uK5AEUqILNpsPtj2+Wjjw TPnltYCSMGi9dHNqSpWXQUmIO7Mu1Ez9jF5WBmUlEmRQ4eGSEdl+EA6jpkgkIi6y 89q80wHW8c4WteUmJuAn7jGS/9E63uug/NeBYBY8B5azfZ3Yn4y3K7APA7/0mBFL slZBHZzngfw4Xy0RgEzMzybnyAIK7oVj675KaLvarQxzg5B4OD7L8mR0Hd706IEY eQNq/y8V53JlycOyG6dJTq75CPN5a/VEbjCEHxBdam1oWDwHaUhWPhepnGqi/i43 9uJp+/JdmG92S4RSGUCDRXGZ7E/p/lECAwEAAaOB6zCB6DBpBgNVHREEYjBghwR/ AAABhwTAqLJIhwR/AAABhwTAqLJIhxAAAAAAAAAAAAAAAAAAAAABhxAgAwDd/wrs AFCjhjpN1z7KhxAAAAAAAAAAAAAAAAAAAAABhxAgAwDd/wrsAFCjhjpN1z7KMA4G A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYD VR0TAQH/BAIwADAdBgNVHQ4EFgQUk/gJR3Fn1QVISRqaecmdex/FC+QwHwYDVR0j BBgwFoAU4Ofj09tTKVnH+KHTVZUXX1TsceQwDQYJKoZIhvcNAQELBQADggIBACUr q6T6Pjx8GZiV2c+qozqBoDGqPGQ94iPwXEOA32t1VT/VBqpEv5BNpwRV2y8RtjJQ hKCtk3DnsmxSJuMOB4HLlE/C9xuyyxVyvfP+LjExGi55/ITQOUXwtQRt2wAu2PSy ZnwFfTHfR2/7yVkq0scRJ5AXPe/uvAfJY5TNQl7rBrXwDKvmnvGzQOH9o/dKGui3 Sz7r0t2RXd2WGL0x+IiVrOXwti9ktp0s58Vo9JYpmimTVkPjWV9YdzhkIJRj7hxf J68PsmTE+UHkCkv3lqgpK1DDKfa23a4XVYR22Ccc3VqriVd27RTpr++3ZH2cNX6Y UJQtJFLkkcrSZlKJFlT+9heg7qn8IGA8BL+xXeCw9BDpq5TlEJ01BPGeKuJ1Ks1X YyYA8EItcOjQXQzzyIZculQzIEC9+3ZjHxIUKjKN5LeR+2fbD8dWRK8w9ylRMJoC kUlniZsi/weCxGPu5cg9mLSjGcPTNbDxb6WvCRb+8gG880f3deqHq0UIep6NG3jv pdUIwNKB1BuFvVMPHl1Oqqdm35AAsgv55WQgd3xk9i0ltJza+SPQlp/TqxDbZEFm rPnLHG9oSlzWVrO++/D+xc2qsLjTq8ApqyV9J4w63ksX6q/Qkr/MZJncNs+WKIx5 J5DXiD3fK3F91+BHftaJpiHpe1Nx/DloAmPweC2u -----END CERTIFICATE----- vip-manager-1.0.1/test/certs/etcd_client.key000066400000000000000000000062531375274636300210330ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIJKQIBAAKCAgEAtUEiDqKo5UPqVvrJQSB8lYgYw371/wJos+EHsI1H6nmnzFtM YOpwr9OBsu5VHThyNd+hAd7ktLaBwR3NeRUsCljiVcle41k6chlTDEy82UUbuL3w QHh52lMqb4NSC8NnGyEp8rp76Db/+WiFaQrn00er1buW872WV4gsKBiXlJZoucS7 EkPOoLwQ9AmYKv4kBIhU9r1rvMn8PQDhHRHzHlnysk9DLdpi+HmsBS0/qYQxPh4C hq9fSnU8eyXxR2HVPwuGBs2kaTcbVYsFGOjn+86PgiXBvw2W4ogaDoDaxt91TzsC rU4dUCnfKSo8Qb5TpuZDRfLxdhZcjfYOwqkoVUAmPUtSdyDk1u5fTM4Po5RZlyVf 9Ucm7M/uK5AEUqILNpsPtj2+WjjwTPnltYCSMGi9dHNqSpWXQUmIO7Mu1Ez9jF5W BmUlEmRQ4eGSEdl+EA6jpkgkIi6y89q80wHW8c4WteUmJuAn7jGS/9E63uug/NeB YBY8B5azfZ3Yn4y3K7APA7/0mBFLslZBHZzngfw4Xy0RgEzMzybnyAIK7oVj675K aLvarQxzg5B4OD7L8mR0Hd706IEYeQNq/y8V53JlycOyG6dJTq75CPN5a/VEbjCE HxBdam1oWDwHaUhWPhepnGqi/i439uJp+/JdmG92S4RSGUCDRXGZ7E/p/lECAwEA AQKCAgBZKmGGsZ5EqtRtVZIL599h8EG+aoa5nIXFd75ArD/kqVRSw9cfFjW5SWNU kspsRYhp2ElskioQfHf4eKDMIA46SN+PNDDpxstpteuU8Ws1tzmb+FRoYtwO+zq1 APUrtETUo8vvDK5H3kaueyymMCc7WNa/njj0Tx/Wj7apQu/OuO9r88vTgGf1yo7M fqvM+pjdYfPqLeUCSps7p2MW2e2v0LAD59o0hGqLsc9d/JSE3/MZi8nSWBOYnXPT YScA5q/xA8o7Lo7i86kOvAUV6/2zcjpG3CwjwJupdSrcV6dIjkU3ZOA8QWFKPk76 AT0DWo3sXPpbPthqLzX44EMCoBSmh6BQ9gPUZDhdK7swwnkksXs84G5LODPwiNPg ZF7SgRbjcTQmzOXFqmrZcMR2itUcttpFkUnN6fH615DDunk8FCnOxUXdplenliN4 7MHL4NSr6KOclmRDpew5Wl++/wxrM/V7Y7LBk/OuzcR2BDyG1JkzaOUlUjpXsuy5 l2rzIT9i7WQ8IKyEO/HGv9l8nXRlZPWVJOglL0UT0PWfOeY9Cd/zBt63SMjYnXSP HJzv/ooT/cNfnQwWmUzB/Z9bK2sutOuwBayYGnqt+j2QT0x2hq+p4v6P56A82WZ+ ubjYhSnJzyqeDx7Xf6zg2PZ/kIFFDI6UnlnTDAurE4BdVxdhwQKCAQEA2xXRfAWs eSehfpLAVoETTu21AOOi6mgLC8h4XrpSbKUDFIDW/fVxzZ3sSELgLu715GKl+6qs NqW2zN9K3cSiFZ1oEaj5v2gZhSxF0A/36mD586QwAkFXDE6EYAVwttcogSKC2VB5 IYEEiR/aAGo8FxN5LHXiotnlqJm0fUebwzlcX8zr+YLo8f4lnyDOLGmOTEWdGHTb 3P+fV/lJSu2B2sQow92AXJKOlRHr9YfScgu/pXMOaB6yv3pcnQHr/VzIIC2pY9pR ye1Wjvxwg+StFMSN9SgaUHUGgkq9WC4Z1IgYNJBFyc0k7MS9GkLVt3TFdjZEOa8/ rORu1D4q8eSPpwKCAQEA08t+TvG+RIqBgWRl3P0qKv8EWkPR/jqu+RdFOzR5qpbT CFcI6YwILfQ0hCt6W9X8zdwKuS7KjbKdV1fLhQSiKl64kiJKbCTJPxpThcxa9hjK 6jcxtxQCEJtTWXwbB3JNLCuY3XopsIIME4ZNQh7vr1Pl/GoG+/Yw0PjH8229Lda1 rPIayQseOKolSTqClZTswKGfL7HHcV8jsN+hBZYw9BTL8cW6ctiPfP5lSQZnXDvs C66grP4zbTtOP0TsfIDjGZnJVhZQOXJDK7Xfm3AcGsPXccurg26kw1LF9RExfCwL Q12VsYDNMq2JRFjBzqifB1LSdtxkI3KySSmQpiWBRwKCAQEApL/Uh2Iw3+7Yd7ld n+9ymKESwzdrdMCGxfab5ghRIVg5Z7q3ccSYLtp6K6D2uvSBvpwcW5Nt665UN94W i4xpor163ATownJC9q1jVmIbuYnxjLFEVP3TuvJ0g0y0BRrpX6qXIVptrK87vO5R 3owE4gmHztJbesFG/bGQU8F8taM1/ui37yrth8Tpf6+Iu0cpddvHlfOSvq8PoXVa E1lllCB800WHWJXxWNJgUYQw0ghZts494DhtjKY0bPFcCGw0JlaQEgHEDYhH1kCp T9Wv/nUMl3Xvy98k5OfVWTFZxUQOh8CSan21LcOIvO3TjyDluM54IbTSum4RldOm Mb8B8wKCAQEAoWZnGBQjkioW7RssgU8wjlmO4JbkdaAU7WAtcyPXQAf2RFnHQetj 5FlAmCRl94xIPjzcsyiUVY1zWDdgsjrItg8/CqY1Htqdvof6dHE6NGbKY9ix+zm1 JSCpUP1Bv9f8NZf3w3gwQwGn4E0tnSDkOTFvh37pWaPQqb+c6MaNL1x7UJOjk+f0 HOyUw4xiLUmzbkz8eaU6Pwxor4aMOCyvm6IplVLAdnrQRkm7t/24UNKdXH20loCV gj16sL5+lZbG+iB3DTKt6klIJQxRniu+TytFiMPULbHov6zZjJuQoXcTEkBELmPg fClA/SPCdhGMN1GHb+seKOFkOlsBj4vvSwKCAQBjzuiiUf/eaciq0iDGBnH8vQSy fPN4bn1NWbgShnPCLLXZV95PqBwEwUf6rfB2mWlAvt48jT7wvszf9iV+7FJfstjl vzjqx5KOfPP38lhVZ/h1XfID3n/RHhcwMlXlM07zvyo2SqaAr4tNxPVxoFyHBb+N vOYEEVqlXkgLU7XJsYWAh9d1vuJhsrX+p85vj3aPmwX/sKY6jtRILOAglWkp/6T/ 1CgWcziSofxKqkON5OSpP4kghzYBPbW7O7X1r4NNg7LsAltf7YTDmltVAV6AOdlB UVbnClsnX2fpacfcUTB0rpzzTav1+uro4UjTRiNabjpFeF+HiuaDXMScVeio -----END RSA PRIVATE KEY----- vip-manager-1.0.1/test/certs/etcd_server.crt000066400000000000000000000036741375274636300210670ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFijCCA3KgAwIBAgIULNyH/+7i9YuYR1/hm0pZ/MqKOzMwDQYJKoZIhvcNAQEL BQAwIjEgMB4GA1UEAwwXaS5hbS50aGUuZXRjZC5zZXJ2ZXIuY2EwHhcNMjAwNDAz MTc0NTE3WhcNMzAwNDAxMTc0NTE3WjAeMRwwGgYDVQQDDBNjZW50b3Mtc2VydmVy LTctMTAxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoCHqFpy9G3MF lYNejKpfLQqQvHEW612+GtRIjUATYuuR49rrXFpKids+jNaDDMwx2wObaO4ECP5E UP7OkAcn0EaZ6IdImFPnpRZ0T6CKevPC+v2OPP2iN57mJsXI2Qve7XrEez3C/qdv jWZK+PIp77OCry5jblLxQgsNmwmNb6gUBGiqqWDBif3mZQZAMJtnRbnVWnHweA2A 4m3Z2j7DCFQoZXBEUEdp4GhFwz8/OWjK52GonA1bJS7MYPPUyU9wZthgCoI3scm7 hIEBiole6kJcLKEcHE/CTSUdqgZQUhOpLTTnXlhnDhyRngbdc/hmJhKmca9nh9Sz IRJeU1rCnZVi1rjKjdAT2ta7nJXAO+fk+bSYL+XbN4iRlIXrb9JiQehkFIhu1vQp VpLZT430nymBtG34ldHRZmIEEYA0LMHWWIs0dc2jn4SWFpXOdgOvKWdK5mMFT+gN PqHrJ/goy+214+5uikpdNlj8J2GT038duM+OB7Or+mh2XAI94RbV86Mr1kKzgMCT GdMV0WOvhkfRKSNx4WuQa7A8ToCuNJLYSbgaRAIaTraY0NQz4MpMuM+Skp8j52bn FedwmxkclnBpsNEN43mRogc5eDyZqZdgsf0WZq/ZzayQmZiGR+whNXg5Y/fZ19Up uqGyofEWpCivvC/XsGcbXe5dUBynkQcCAwEAAaOBuzCBuDA5BgNVHREEMjAwhwR/ AAABhwTAqLJIhxAAAAAAAAAAAAAAAAAAAAABhxAgAwDd/wrsAFCjhjpN1z7KMA4G A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYD VR0TAQH/BAIwADAdBgNVHQ4EFgQU6yz/154zOfY7a15DgNWuSvmL3BowHwYDVR0j BBgwFoAU4Ofj09tTKVnH+KHTVZUXX1TsceQwDQYJKoZIhvcNAQELBQADggIBAK/g eJzkNrGT29aUI2zSM3UgKLet/eov/EUfjWB/2wi028mEREsNKNRRwDH1gC/h5kkh qryrf4rDb3WiaqCiM+jxhxhJY491NAbTXs1HFNW0a2YxHOqgceBB4uOWzM8BbKOg hYtxEGMAUR0kIfRTJO/t5WD2wFL75pdgEOdIQoabN2Yiur3+frrZhx/tjaLf6nnr YD8lWFt1+wfW2QfmYLwXYzdR/F39WAVbNwWzimYthow7EekiBEoH57Yv/4hFoR4Z FoHC8cRudbkiVNDXVN9hNqfMCmxeq0HrQOaa4gHnw85C4gSZdR7Q2hwDHF3Ujbqf /mS94eGa2qLIFQW7gvvQf4mTRTjO51owZ6Yt6NBS9GzMGUsKdlC0ylbBiuK/7wBh iB8rQdayLp/+6xFhGD8D8G+Xj0VvVlQ1tos2JdvJ1LhoTXWG2FjZ9ZpIXOU6Xjvj N9MufxgDidcXJOdA3lTKVBhmLobjvTcxepkmSEP5sS/Yi2DuFSohpcjS1GLhGrMu 9Q6rpVPjRauZAbYigjI3Rt1lryjgwTr+Rlyx0okmH2A7jNCOXN8EI7sZFHeKgpgq 3nPwEnvOem0v1joTyuIwcndWQyegwa69K+lowcb43C5akBzDZs7myPvU6MzYhoN7 I5PoSbsOrshivg/+KnCBxoPswpvJN/l26o+8UGb1 -----END CERTIFICATE----- vip-manager-1.0.1/test/certs/etcd_server.key000066400000000000000000000062531375274636300210630ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- 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 -----END RSA PRIVATE KEY----- vip-manager-1.0.1/test/certs/etcd_server_ca.crt000066400000000000000000000035531375274636300215260ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFTTCCAzWgAwIBAgIUEvgw+Vm0JRrpoyccisxPPIxVLgowDQYJKoZIhvcNAQEL BQAwFDESMBAGA1UEAwwJaS5hbS5hLmNhMB4XDTIwMDQwMzE3NDUwOVoXDTMwMDQw MTE3NDUwOVowIjEgMB4GA1UEAwwXaS5hbS50aGUuZXRjZC5zZXJ2ZXIuY2EwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC6+76EfGNVBlkzH8eWCsYSWok9 lE3BstEvNpFpaZyEjW4/Q3TVZsvhl70e4Ko8SYIdc7ZI2FHPlhJpqqxjldfAx2O6 V41qgutt6K0X5/2yY0XGlIrGOCVLVKfYZmCgheQ7pwwdh6+PBBCsJd34LE0tZx4/ rgVw1Z+pcqP+9UBx/8pGgi7O7e+ENxKuZNcgjG9LKC3k558fKpdeg5UyuBPLu49C xhM1h8zNk7ExyXlplIOmGr1y1MDBAkqikl9cxs10G66Nzvtar08sSP3UMlVM1fqq vAVFARAyqbAPxa3Mrv712/AywQV7Lr6n3LJFPlteJ7MIs/+1xAAAmLH/By8O8saH 0nyoR+498VPbQPD1RxxBeia1bNE/zrmAO9UIdUg3uvUuZoIdQpypV1t9zNsd1duQ aMWLjPNl1LtYJWg2UCS/yrSsSnqPZhYECL9a9zBKjqMcya6B4ZRAbWY6Kj6sUvpu GZuA+i2ICpmg9f3gypOt+nrrN2wUJ7qFFy0CHQaHNn8SumuHWTmoK1XIuSIPVKfO tqxJu7b22T9IWOrR0m+M6hWs1IYEzv4dCKRLHzbI0p+PYCKTfYqNZw0GOSS1Zf2/ Qs8yrJDNes7qqAtE7Js9LOxIz1U18sLFwQU4JCo8J0+EeX4MNvpDoP/Hfgo4C8gp ZeuaBJfxfJa13QRBfwIDAQABo4GIMIGFMCIGA1UdEQQbMBmCF2kuYW0udGhlLmV0 Y2Quc2VydmVyLmNhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0G A1UdDgQWBBTg5+PT21MpWcf4odNVlRdfVOxx5DAfBgNVHSMEGDAWgBTjygVZXrWb FUh/t4mYQWLPSQTOozANBgkqhkiG9w0BAQsFAAOCAgEAOBdbChFLkEwT+X+CyYy8 P9AD/U5MBhanjArZjKnmDNtWbaFujU9P4MhVZSRQybtCFixujy4svC2yww/y0VbC AY80U5KELfR4eW39wyASSmlL3F3AhciyTjDlwrnGvt32uahwHeIvq4a43g5DZQi0 RJnyj6zErhUpeEYOD5xvmnK+e6bDdkH4QLkOAcEFHa4fU4QrghHHjfi8AAuZyTk8 GA+ViodUMDI8APvXaXpljQarttnmFz76pwYhDyteKQzhJTIKO2lxdDn1Iutx5GH+ VO4yl5nhaPKrHscuouralCtZDKUMMz2Le40oSnC72qGS4KimOD52O4XrqISO5DtD HtXh1P5xcpnCXlnbClcwoI/46rGNcXAwlXy01iPeH2bxrl0xhdJpKpVmWI11LuYe vzk3gmttGdCvbYE496hiEGX8yVW43LC6tcXUOGrPKODIj5iQCPBHc5/Xo7OvKkTr 9jnZAyKogb21VoJbECcXrOCnwnfkKhfajbFYOnJkntR0Z6Eae8d3yj7oA5EgPgEa 2ucBelrMbBrgHV2vkvkQ7/wxhYfMn9Rhw+UGaem99hN9+1eBEm/azP5fT/Kt7oN7 KNl9c5Wgto7rzy49bmgb7xWqGtHkQ7v1SUTErGc+IiMU/5oDQzcf3EpfIW3TDX9P 1qq8yN3Rs4zlP7uBE7Kaj8c= -----END CERTIFICATE----- vip-manager-1.0.1/test/clientcert_test.sh000077500000000000000000000050371375274636300204550ustar00rootroot00000000000000#!/bin/sh set -eu -o pipefail RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' # No Color # testing parameters dev=`ip link show | grep -B1 ether | cut -d ":" -f2 | head -n1 | cut -d " " -f2` vip=10.0.2.123 #cleanup function cleanup { if test -f .ncatPid then kill `cat .ncatPid` 2> /dev/null || true rm .ncatPid fi if test -f .vipPid then kill `cat .vipPid` 2> /dev/null || true rm .vipPid #rm vip-manager.log fi if test -f .etcdPid then kill `cat .etcdPid` 2> /dev/null || true rm .etcdPid fi if test -f .failed then echo -e "${RED}### Some tests failed! ###${NC}" rm .failed fi podman stop etcd } trap cleanup EXIT # prerequisite test 0: vip should not yet be registered ! ip address show dev $dev | grep $vip # run etcd with podman/docker maybe? podman run --rm -d --name etcd -p 2379:2379 -e "ETCD_ENABLE_V2=true" -e "ALLOW_NONE_AUTHENTICATION=yes" -v `pwd`/test/certs/:/certs:Z quay.io/coreos/etcd /usr/local/bin/etcd --trusted-ca-file=/certs/etcd_server_ca.crt --client-cert-auth --cert-file=/certs/etcd_server.crt --key-file=/certs/etcd_server.key --listen-client-urls https://127.0.0.1:2379 --advertise-client-urls https://127.0.0.1:2379 sleep 2 # simulate server, e.g. postgres ncat -vlk 0.0.0.0 12345 -e "/bin/echo $HOSTNAME" & echo $! > .ncatPid curl -s --cert test/certs/etcd_client.crt --key test/certs/etcd_client.key --cacert test/certs/etcd_server_ca.crt -XDELETE https://127.0.0.1:2379/v2/keys/service/pgcluster/leader ||true touch .failed ./vip-manager --etcd-cert-file test/certs/etcd_client.crt --etcd-key-file test/certs/etcd_client.key --etcd-ca-file test/certs/etcd_server_ca.crt --dcs-endpoints https://127.0.0.1:2379 --interface $dev --ip $vip --netmask 32 --trigger-key service/pgcluster/leader --trigger-value $HOSTNAME &> vip-manager.log & echo $! > .vipPid sleep 2 # test 1: vip should still not be registered ! ip address show dev $dev | grep $vip # simulate patroni member promoting to leader curl -s --cert test/certs/etcd_client.crt --key test/certs/etcd_client.key --cacert test/certs/etcd_server_ca.crt -XPUT https://127.0.0.1:2379/v2/keys/service/pgcluster/leader -d value=$HOSTNAME | jq . sleep 2 # we're just checking whether vip-manager picked up the change, for some reason, we can't run an elevated container of quay.io/coreos/etcd grep 'state is false, desired true' vip-manager.log rm .failed echo -e "${GREEN}### You've reached the end of the script, all \"tests\" have successfully been passed! ###${NC}" vip-manager-1.0.1/vipconfig/000077500000000000000000000000001375274636300157235ustar00rootroot00000000000000vip-manager-1.0.1/vipconfig/config.go000066400000000000000000000246011375274636300175220ustar00rootroot00000000000000package vipconfig import ( "errors" "fmt" "log" "os" "sort" "strings" "github.com/spf13/pflag" "github.com/spf13/viper" ) // Config represents the configuration of the VIP manager type Config struct { IP string `mapstructure:"ip"` Mask int `mapstructure:"netmask"` Iface string `mapstructure:"interface"` HostingType string `mapstructure:"manager-type"` Key string `mapstructure:"trigger-key"` Nodename string `mapstructure:"trigger-value"` //hostname to trigger on. usually the name of the host where this vip-manager runs. EndpointType string `mapstructure:"dcs-type"` Endpoints []string `mapstructure:"dcs-endpoints"` EtcdUser string `mapstructure:"etcd-user"` EtcdPassword string `mapstructure:"etcd-password"` EtcdCAFile string `mapstructure:"etcd-ca-file"` EtcdCertFile string `mapstructure:"etcd-cert-file"` EtcdKeyFile string `mapstructure:"etcd-key-file"` ConsulToken string `mapstructure:"consul-token"` Interval int `mapstructure:"interval"` //milliseconds RetryAfter int `mapstructure:"retry-after"` //milliseconds RetryNum int `mapstructure:"retry-num"` Verbose bool `mapstructure:"verbose"` } func defineFlags() { // When adding new flags here, consider adding them to the Config struct above // and then make sure to insert them into the conf instance in NewConfig down below. pflag.String("config", "", "Location of the configuration file.") pflag.Bool("version", false, "Show the version number.") pflag.String("ip", "", "Virtual IP address to configure.") pflag.String("netmask", "", "The netmask used for the IP address. Defaults to -1 which assigns ipv4 default mask.") pflag.String("interface", "", "Network interface to configure on .") pflag.String("trigger-key", "", "Key in the DCS to monitor, e.g. \"/service/batman/leader\".") pflag.String("trigger-value", "", "Value to monitor for.") pflag.String("dcs-type", "etcd", "Type of endpoint used for key storage. Supported values: etcd, consul.") // note: can't put a default value into dcs-endpoints as that would mess with applying default localhost when using consul pflag.String("dcs-endpoints", "", "DCS endpoint(s), separate multiple endpoints using commas. (default \"http://127.0.0.1:2379\" or \"http://127.0.0.1:8500\" depending on dcs-type.)") pflag.String("etcd-user", "", "Username for etcd DCS endpoints.") pflag.String("etcd-password", "", "Password for etcd DCS endpoints.") pflag.String("etcd-ca-file", "", "Trusted CA certificate for the etcd server.") pflag.String("etcd-cert-file", "", "Client certificate used for authentiaction with etcd.") pflag.String("etcd-key-file", "", "Private key matching etcd-cert-file to decrypt messages sent from etcd.") pflag.String("consul-token", "", "Token for consul DCS endpoints.") pflag.String("interval", "1000", "DCS scan interval in milliseconds.") pflag.String("manager-type", "basic", "Type of VIP-management to be used. Supported values: basic, hetzner.") pflag.Bool("verbose", false, "Be verbose. Currently only implemented for manager-type=hetzner .") pflag.CommandLine.SortFlags = false } func mapDeprecated() error { deprecated := map[string]string{ // "deprecated" : "new", "mask": "netmask", "iface": "interface", "key": "trigger-key", "nodename": "trigger-value", "etcd_user": "etcd-user", "etcd_password": "etcd-password", "type": "dcs-type", "endpoint": "dcs-endpoints", "endpoints": "dcs-endpoints", "hostingtype": "manager-type", "hosting_type": "manager-type", "endpoint_type": "dcs-type", "retry_num": "retry-num", "retry_after": "retry-after", "consul_token": "consul-token", "host": "trigger-value", } complaints := []string{} errors := false for k, v := range deprecated { if viper.IsSet(k) { if _, exists := os.LookupEnv("VIP_" + strings.ToUpper(k)); !exists { // using deprecated key in config file (as not exists in ENV) complaints = append(complaints, fmt.Sprintf("Parameter \"%s\" has been deprecated, please use \"%s\" instead", k, v)) } else { if strings.ReplaceAll(k, "_", "-") != v { // this string is not a direct replacement (e.g. etcd-user replaces etcd-user, i.e. in both cases VIP_ETCD_USER is the valid env key) // for example, complain about VIP_IFACE, but not VIP_CONSUL_TOKEN or VIP_ETCD_USER... complaints = append(complaints, fmt.Sprintf("Parameter \"%s\" has been deprecated, please use \"%s\" instead", "VIP_"+strings.ToUpper(k), "VIP_"+strings.ReplaceAll(strings.ToUpper(v), "-", "_"))) } else { continue } } if viper.IsSet(v) { // don't forget to reset the desired replacer when exiting replacer := strings.NewReplacer("-", "_") defer viper.SetEnvKeyReplacer(replacer) // Check if there is only a collision because ENV vars always use _ instead of - and the deprecated mapping only maps from *_* to *-*. testReplacer := strings.NewReplacer("", "") // just don't replace anything viper.SetEnvKeyReplacer(testReplacer) if viper.IsSet(v) { complaints = append(complaints, fmt.Sprintf("Conflicting settings: %s or %s and %s or %s are both specified…", k, "VIP_"+strings.ToUpper(k), v, "VIP_"+strings.ReplaceAll(strings.ToUpper(v), "-", "_"))) if viper.Get(k) == viper.Get(v) { complaints = append(complaints, fmt.Sprintf("… But no conflicting values: %s and %s are equal…ignoring.", viper.GetString(k), viper.GetString(v))) continue } else { complaints = append(complaints, fmt.Sprintf("…conflicting values: %s and %s", viper.GetString(k), viper.GetString(v))) errors = true continue } } } // if this is a valid mapping due to deprecation, set the new key explicitly to the value of the deprecated key. viper.Set(v, viper.Get(k)) // "unset" the deprecated setting so it will not show up in our config later viper.Set(k, "") } } for c := range complaints { log.Println(complaints[c]) } if errors { log.Fatal("Cannot continue due to conflicts.") } return nil } func setDefaults() { defaults := map[string]string{ "dcs-type": "etcd", "interval": "1000", "hostingtype": "basic", "retry-num": "3", "retry-after": "250", } for k, v := range defaults { if !viper.IsSet(k) { viper.SetDefault(k, v) } } } func checkSetting(name string) bool { if !viper.IsSet(name) { log.Printf("Setting %s is mandatory", name) return false } return true } func checkMandatory() error { mandatory := []string{ "ip", "netmask", "interface", "trigger-key", "trigger-value", "dcs-endpoints", } success := true for _, v := range mandatory { success = checkSetting(v) && success } if !success { return errors.New("one or more mandatory settings were not set") } return nil } // if reason is set, but implied is not set, return false. func checkImpliedSetting(implied string, reason string) bool { if viper.IsSet(reason) && !viper.IsSet(implied) { log.Printf("Setting %s is mandatory when setting %s is specified.", implied, reason) return false } return true } // Some settings imply that another setting must be set as well. func checkImpliedMandatory() error { mandatory := map[string]string{ // "implied" : "reason" "etcd-user": "etcd-password", "etcd-key-file": "etcd-cert-file", "etcd-ca-file": "etcd-cert-file", } success := true for k, v := range mandatory { success = checkImpliedSetting(k, v) && success } if !success { return errors.New("one or more implied mandatory settings were not set") } return nil } func printSettings() { s := []string{} for k, v := range viper.AllSettings() { if v != "" { switch k { case "etcd-password": fallthrough case "consul-token": s = append(s, fmt.Sprintf("\t%s : *****\n", k)) default: s = append(s, fmt.Sprintf("\t%s : %v\n", k, v)) } } } sort.Strings(s) log.Println("This is the config that will be used:") for k := range s { fmt.Print(s[k]) } } // NewConfig returns a new Config instance func NewConfig() (*Config, error) { var err error defineFlags() pflag.Parse() // import pflags into viper _ = viper.BindPFlags(pflag.CommandLine) // make viper look for env variables that are prefixed VIP_... // e.g.: viper.getString("ip") will return the value of env variable VIP_IP viper.SetEnvPrefix("vip") viper.AutomaticEnv() //replace dashes (in flags) with underscores (in ENV vars) // so that e.g. viper.GetString("dcs-endpoints") will return value of VIP_DCS_ENDPOINTS replacer := strings.NewReplacer("-", "_") viper.SetEnvKeyReplacer(replacer) // viper precedence order // - explicit call to Set // - flag // - env // - config // - key/value store // - default // if a configfile has been passed, make viper read it if viper.IsSet("config") { viper.SetConfigFile(viper.GetString("config")) err := viper.ReadInConfig() // Find and read the config file if err != nil { // Handle errors reading the config file return nil, fmt.Errorf("Fatal error reading config file: %w", err) } log.Printf("Using config from file: %s\n", viper.ConfigFileUsed()) } if err = mapDeprecated(); err != nil { return nil, err } setDefaults() // convert string of csv to String Slice if viper.IsSet("dcs-endpoints") { endpointsString := viper.GetString("dcs-endpoints") if strings.Contains(endpointsString, ",") { viper.Set("dcs-endpoints", strings.Split(endpointsString, ",")) } } // apply defaults for endpoints if !viper.IsSet("dcs-endpoints") { log.Println("No dcs-endpoints specified, trying to use localhost with standard ports!") switch viper.GetString("dcs-type") { case "consul": viper.Set("dcs-endpoints", []string{"http://127.0.0.1:8500"}) case "etcd": viper.Set("dcs-endpoints", []string{"http://127.0.0.1:2379"}) } } // set trigger-value to hostname if nothing is specified if len(viper.GetString("trigger-value")) == 0 { triggerValue, err := os.Hostname() if err != nil { log.Printf("No trigger-value specified, hostname could not be retrieved: %s", err) } else { log.Printf("No trigger-value specified, instead using hostname: %v", triggerValue) viper.Set("trigger-value", triggerValue) } } if err = checkMandatory(); err != nil { return nil, err } if err = checkImpliedMandatory(); err != nil { return nil, err } conf := &Config{} err = viper.Unmarshal(conf) if err != nil { log.Fatalf("unable to decode viper config into config struct, %v", err) } printSettings() return conf, nil } vip-manager-1.0.1/vipconfig/vip-manager.yml000066400000000000000000000042161375274636300206570ustar00rootroot00000000000000# config for vip-manager by Cybertec Schönig & Schönig GmbH # time (in milliseconds) after which vip-manager wakes up and checks if it needs to register or release ip addresses. interval: 1000 # the etcd or consul key which vip-manager will regularly poll. trigger-key: "/service/pgcluster/leader" # if the value of the above key matches the trigger-value (often the hostname of this host), vip-manager will try to add the virtual ip address to the interface specified in Iface trigger-value: "pgcluster_member1" ip: 192.168.0.123 # the virtual ip address to manage netmask: 24 # netmask for the virtual ip interface: enp0s3 #interface to which the virtual ip will be added # how the virtual ip should be managed. we currently support "ip addr add/remove" through shell commands or the Hetzner api hosting-type: basic # possible values: basic, or hetzner. dcs-type: etcd # etcd or consul # a list that contains all DCS endpoints to which vip-manager could talk. dcs-endpoints: - http://127.0.0.1:2379 - https://192.168.0.42:2379 # A single list-item is also fine. # consul will always only use the first entry from this list. # For consul, you'll obviously need to change the port to 8500. Unless you're using a different one. Maybe you're a rebel and are running consul on port 2379? Just to confuse people? Why would you do that? Oh, I get it. etcd-user: "patroni" etcd-password: "Julian's secret password" # when etcd-ca-file is specified, TLS connections to the etcd endpoints will be used. etcd-ca-file: "/path/to/etcd/trusted/ca/file" # when etcd-cert-file and etcd-key-file are specified, we will authenticate at the etcd endpoints using this certificate and key. etcd-cert-file: "/path/to/etcd/client/cert/file" etcd-key-file: "/path/to/etcd/client/key/file" # don't worry about parameter with a prefix that doesn't match the endpoint_type. You can write anything there, I won't even look at it. consul-token: "Julian's secret token" # how often things should be retried and how long to wait between retries. (currently only affects arpClient) retry-num: 2 retry-after: 250 #in milliseconds # verbose logs (currently only supported for hetzner) verbose: false