pax_global_header00006660000000000000000000000064140320615200014503gustar00rootroot0000000000000052 comment=f85d38745684e0082650118bff4ee18f5f6b2592 prometheus-nextcloud-exporter-0.4.0/000077500000000000000000000000001403206152000175505ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/.dockerignore000077700000000000000000000000001403206152000242072.gitignoreustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/.editorconfig000066400000000000000000000003661403206152000222320ustar00rootroot00000000000000root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false [*.go] indent_style = tab [Makefile] indent_style = tab prometheus-nextcloud-exporter-0.4.0/.github/000077500000000000000000000000001403206152000211105ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/.github/workflows/000077500000000000000000000000001403206152000231455ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/.github/workflows/pull-request.yml000066400000000000000000000007661403206152000263430ustar00rootroot00000000000000name: Pull-Request on: pull_request: branches: [ master ] jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: ^1.15 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Get dependencies run: | go get -v -t -d ./... - name: Build run: go build -v . - name: Test run: go test -cover ./... prometheus-nextcloud-exporter-0.4.0/.gitignore000066400000000000000000000001021403206152000215310ustar00rootroot00000000000000run.sh nextcloud-exporter nextcloud-exporter.yml config.yml dist/ prometheus-nextcloud-exporter-0.4.0/Dockerfile000066400000000000000000000006671403206152000215530ustar00rootroot00000000000000FROM golang:1.15.7 AS builder WORKDIR /build COPY go.mod go.sum /build/ RUN go mod download RUN go mod verify COPY . /build/ RUN make FROM busybox LABEL maintainer="Robert Jacob " COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /build/nextcloud-exporter /bin/nextcloud-exporter USER nobody EXPOSE 9205 ENTRYPOINT ["/bin/nextcloud-exporter"] prometheus-nextcloud-exporter-0.4.0/LICENSE000066400000000000000000000020671403206152000205620ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017 Robert Jacob Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. prometheus-nextcloud-exporter-0.4.0/Makefile000066400000000000000000000016101403206152000212060ustar00rootroot00000000000000.PHONY: all test build-binary install clean GO ?= go GO_CMD := CGO_ENABLED=0 $(GO) GIT_VERSION := $(shell git describe --tags --dirty) VERSION := $(GIT_VERSION:v%=%) GIT_COMMIT := $(shell git rev-parse HEAD) all: test build-binary test: $(GO_CMD) test -cover ./... build-binary: $(GO_CMD) build -tags netgo -ldflags "-w -X main.Version=$(VERSION) -X main.GitCommit=$(GIT_COMMIT)" -o nextcloud-exporter . deb: build-binary mkdir -p dist/deb/DEBIAN dist/deb/usr/bin sed 's/%VERSION%/$(VERSION)/' contrib/debian/control > dist/deb/DEBIAN/control cp nextcloud-exporter dist/deb/usr/bin/ fakeroot dpkg-deb --build dist/deb dist install: install -D -t $(DESTDIR)/usr/bin/ nextcloud-exporter install -D -m 0644 -t $(DESTDIR)/lib/systemd/system/ contrib/nextcloud-exporter.service image: docker build -t "xperimental/nextcloud-exporter:$(VERSION)" . clean: rm -f nextcloud-exporter rm -r dist prometheus-nextcloud-exporter-0.4.0/README.md000066400000000000000000000111541403206152000210310ustar00rootroot00000000000000# nextcloud-exporter [![Docker Build Status](https://img.shields.io/docker/build/xperimental/nextcloud-exporter.svg?style=flat-square)](https://hub.docker.com/r/xperimental/nextcloud-exporter/) A [prometheus](https://prometheus.io) exporter for getting some metrics of a nextcloud server instance. ## Installation If you have a working Go installation, getting the binary should be as simple as ```bash go get github.com/xperimental/nextcloud-exporter ``` ## Client credentials To access the serverinfo API you will need the credentials of an admin user. It is recommended to create a separate user for that purpose. It's also possible for the exporter to generate an "app password", so that the real user password is never saved to the configuration. This also makes the exporter show up in the security panel of the user as a connected application. To let the nextcloud-exporter create an app password, start it with the `--login` parameter: ```bash nextcloud-exporter --login --server https://nextcloud.example.com ``` The exporter will generate a login URL that you need to open in your browser. Be sure to login with the correct user if you created a special user for the exporter as the app password will be bound to the logged-in user. Once the access has been granted using the browser the exporter will output the username and password that need to be entered into the configuration. When the login process is done, it is possible to disable filesystem access for the generated token in the user's settings: ![Allow filesystem access checkbox](contrib/allow-filesystem.png) --- The interactive login can also be done using a Docker container: ```bash docker run --rm -it xperimental/nextcloud-exporter --login --server https://nextcloud.example.com ``` The login flow needs at least Nextcloud 16 to work. ## Usage ```plain $ nextcloud-exporter --help Usage of nextcloud-exporter: -a, --addr string Address to listen on for connections. (default ":9205") -c, --config-file string Path to YAML configuration file. --login Use interactive login to create app password. -p, --password string Password for connecting to Nextcloud. -s, --server string URL to Nextcloud server. -t, --timeout duration Timeout for getting server info document. (default 5s) -u, --username string Username for connecting to Nextcloud. ``` After starting the server will offer the metrics on the `/metrics` endpoint, which can be used as a target for prometheus. ### Configuration methods There are three methods of configuring the nextcloud-exporter (higher methods take precedence over lower ones): - Environment variables - Configuration file - Command-line parameters #### Environment variables All settings can also be specified through environment variables: | Environment variable | Flag equivalent | | -------------------------: | :-------------- | | `NEXTCLOUD_SERVER` | --server | | `NEXTCLOUD_USERNAME` | --username | | `NEXTCLOUD_PASSWORD` | --password | | `NEXTCLOUD_LISTEN_ADDRESS` | --addr | | `NEXTCLOUD_TIMEOUT` | --timeout | #### Configuration file The `--config-file` option can be used to read the configuration options from a YAML file: ```yaml # required server: "https://example.com" username: "example" password: "example" # optional listenAddress: ":9205" timeout: "5s" ``` ### Password file Optionally the password can be read from a separate file instead of directly from the input methods above. This can be achieved by setting the password to the path of the password file prefixed with an "@", for example: ```bash nextcloud-exporter -c config-without-password.yml -p @/path/to/passwordfile ``` ## Other information ### Info URL The exporter reads the metrics from the Nextcloud server using its "serverinfo" API. You can find the URL of this API in the administrator settings in the "Monitoring" section. It should look something like this: ```plain https://example.com/ocs/v2.php/apps/serverinfo/api/v1/info ``` The path will be automatically added to the server URL you provide, so in the above example setting `--server https://example.com` would be sufficient. If you open this URL in a browser you should see an XML structure with the information that will be used by the exporter. ### Scrape configuration The exporter will query the nextcloud server every time it is scraped by prometheus. If you want to reduce load on the nextcloud server you need to change the scrape interval accordingly: ```yml scrape_configs: - job_name: 'nextcloud' scrape_interval: 90s static_configs: - targets: ['localhost:9205'] ``` prometheus-nextcloud-exporter-0.4.0/collector.go000066400000000000000000000170241403206152000220710ustar00rootroot00000000000000package main import ( "fmt" "net/http" "time" "github.com/prometheus/client_golang/prometheus" "github.com/xperimental/nextcloud-exporter/serverinfo" ) const ( metricPrefix = "nextcloud_" ) var ( systemInfoDesc = prometheus.NewDesc( metricPrefix+"system_info", "Contains meta information about Nextcloud as labels. Value is always 1.", []string{"version"}, nil) appsInstalledDesc = prometheus.NewDesc( metricPrefix+"apps_installed_total", "Number of currently installed apps", nil, nil) appsUpdatesDesc = prometheus.NewDesc( metricPrefix+"apps_updates_available_total", "Number of apps that have available updates", nil, nil) usersDesc = prometheus.NewDesc( metricPrefix+"users_total", "Number of users of the instance.", nil, nil) filesDesc = prometheus.NewDesc( metricPrefix+"files_total", "Number of files served by the instance.", nil, nil) freeSpaceDesc = prometheus.NewDesc( metricPrefix+"free_space_bytes", "Number of bytes of free space on the instance.", nil, nil) sharesDesc = prometheus.NewDesc( metricPrefix+"shares_total", "Number of shares by type.", []string{"type"}, nil) federationsDesc = prometheus.NewDesc( metricPrefix+"shares_federated_total", "Number of federated shares by direction.", []string{"direction"}, nil) activeUsersDesc = prometheus.NewDesc( metricPrefix+"active_users_total", "Number of active users for the last five minutes.", nil, nil) phpInfoDesc = prometheus.NewDesc( metricPrefix+"php_info", "Contains meta information about PHP as labels. Value is always 1.", []string{"version"}, nil) phpMemoryLimitDesc = prometheus.NewDesc( metricPrefix+"php_memory_limit_bytes", "Configured PHP memory limit in bytes.", nil, nil) phpMaxUploadSizeDesc = prometheus.NewDesc( metricPrefix+"php_upload_max_size_bytes", "Configured maximum upload size in bytes.", nil, nil) databaseSizeDesc = prometheus.NewDesc( metricPrefix+"database_size_bytes", "Size of database in bytes as reported from engine.", nil, nil) ) type nextcloudCollector struct { infoURL string username string password string client *http.Client userAgent string upMetric prometheus.Gauge authErrorsMetric prometheus.Counter scrapeErrorsMetric prometheus.Counter } func newCollector(infoURL, username, password string, timeout time.Duration, userAgent string) *nextcloudCollector { return &nextcloudCollector{ infoURL: infoURL, username: username, password: password, client: &http.Client{ Timeout: timeout, }, userAgent: userAgent, upMetric: prometheus.NewGauge(prometheus.GaugeOpts{ Name: metricPrefix + "up", Help: "Shows if nextcloud is deemed up by the collector.", }), authErrorsMetric: prometheus.NewCounter(prometheus.CounterOpts{ Name: metricPrefix + "auth_errors_total", Help: "Counts number of authentication errors encountered by the collector.", }), scrapeErrorsMetric: prometheus.NewCounter(prometheus.CounterOpts{ Name: metricPrefix + "scrape_errors_total", Help: "Counts the number of scrape errors by this collector.", }), } } func (c *nextcloudCollector) Describe(ch chan<- *prometheus.Desc) { c.upMetric.Describe(ch) c.authErrorsMetric.Describe(ch) c.scrapeErrorsMetric.Describe(ch) ch <- usersDesc ch <- filesDesc ch <- freeSpaceDesc ch <- sharesDesc ch <- federationsDesc ch <- activeUsersDesc } func (c *nextcloudCollector) Collect(ch chan<- prometheus.Metric) { if err := c.collectNextcloud(ch); err != nil { log.Errorf("Error during scrape: %s", err) c.scrapeErrorsMetric.Inc() c.upMetric.Set(0) } else { c.upMetric.Set(1) } c.upMetric.Collect(ch) c.authErrorsMetric.Collect(ch) c.scrapeErrorsMetric.Collect(ch) } func (c *nextcloudCollector) collectNextcloud(ch chan<- prometheus.Metric) error { req, err := http.NewRequest(http.MethodGet, c.infoURL, nil) if err != nil { return err } req.SetBasicAuth(c.username, c.password) req.Header.Set("User-Agent", c.userAgent) res, err := c.client.Do(req) if err != nil { return err } defer res.Body.Close() if res.StatusCode == http.StatusUnauthorized { c.authErrorsMetric.Inc() return fmt.Errorf("wrong credentials") } if res.StatusCode != http.StatusOK { return fmt.Errorf("unexpected status code: %d", res.StatusCode) } status, err := serverinfo.Parse(res.Body) if err != nil { return fmt.Errorf("Error parsing server info: %s", err) } return readMetrics(ch, status) } func readMetrics(ch chan<- prometheus.Metric, status serverinfo.ServerInfo) error { if err := collectSimpleMetrics(ch, status); err != nil { return err } if err := collectShares(ch, status.Data.Nextcloud.Shares); err != nil { return err } if err := collectFederatedShares(ch, status.Data.Nextcloud.Shares); err != nil { return err } systemInfo := []string{ status.Data.Nextcloud.System.Version, } if err := collectInfoMetric(ch, systemInfoDesc, systemInfo); err != nil { return err } phpInfo := []string{ status.Data.Server.PHP.Version, } if err := collectInfoMetric(ch, phpInfoDesc, phpInfo); err != nil { return err } return nil } func collectSimpleMetrics(ch chan<- prometheus.Metric, status serverinfo.ServerInfo) error { metrics := []struct { desc *prometheus.Desc value float64 }{ { desc: appsInstalledDesc, value: float64(status.Data.Nextcloud.System.Apps.Installed), }, { desc: appsUpdatesDesc, value: float64(status.Data.Nextcloud.System.Apps.AvailableUpdates), }, { desc: usersDesc, value: float64(status.Data.Nextcloud.Storage.Users), }, { desc: filesDesc, value: float64(status.Data.Nextcloud.Storage.Files), }, { desc: freeSpaceDesc, value: float64(status.Data.Nextcloud.System.FreeSpace), }, { desc: activeUsersDesc, value: float64(status.Data.ActiveUsers.Last5Minutes), }, { desc: phpMemoryLimitDesc, value: float64(status.Data.Server.PHP.MemoryLimit), }, { desc: phpMaxUploadSizeDesc, value: float64(status.Data.Server.PHP.UploadMaxFilesize), }, { desc: databaseSizeDesc, value: float64(status.Data.Server.Database.Size), }, } for _, m := range metrics { metric, err := prometheus.NewConstMetric(m.desc, prometheus.GaugeValue, m.value) if err != nil { return fmt.Errorf("error creating metric for %s: %s", m.desc, err) } ch <- metric } return nil } func collectShares(ch chan<- prometheus.Metric, shares serverinfo.Shares) error { values := make(map[string]float64) values["user"] = float64(shares.SharesUser) values["group"] = float64(shares.SharesGroups) values["authlink"] = float64(shares.SharesLink - shares.SharesLinkNoPassword) values["link"] = float64(shares.SharesLink) return collectMap(ch, sharesDesc, values) } func collectFederatedShares(ch chan<- prometheus.Metric, shares serverinfo.Shares) error { values := make(map[string]float64) values["sent"] = float64(shares.FedSent) values["received"] = float64(shares.FedReceived) return collectMap(ch, federationsDesc, values) } func collectMap(ch chan<- prometheus.Metric, desc *prometheus.Desc, labelValueMap map[string]float64) error { for k, v := range labelValueMap { metric, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, v, k) if err != nil { return fmt.Errorf("error creating shares metric for %s: %s", k, err) } ch <- metric } return nil } func collectInfoMetric(ch chan<- prometheus.Metric, desc *prometheus.Desc, labelValues []string) error { metric, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, 1, labelValues...) if err != nil { return err } ch <- metric return nil } prometheus-nextcloud-exporter-0.4.0/contrib/000077500000000000000000000000001403206152000212105ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/contrib/allow-filesystem.png000066400000000000000000000314111403206152000252160ustar00rootroot00000000000000PNG  IHDR,," pHYs.#.#x?vtIME ")bKGD2IDATxyt3xf_{vδx:=vde;ZAˮlɎ(@d}NBBH–$H4{y?M$77 }rr}~^<{7/$5$%IJ$%IJ$%ϒOh ,7{駟E>5XM.]$\/+**JKKϝ;w3gdgggff"'''??ϟ?_A(R~>eL }zOJ8 ]VeW0S)WRRR?s ./8wUHϟϪg+++ՂPF,VE'|`2 V~~~VcD/P\*ovg5KuQ޼p d5Μ9 ›Q_~e?V:wFDt*((Ⱥfš T ɹ|F1 ›HYת4:Pcݚ1Ax/V^h >,VvvHOe3335L1e>[̙38HI&Y&$y7 EEE҂Pa&M_~u/5jCS3n>|c}aVb ޭ[7=**مw}`]XXx]@GB5ѣ@8~jHOO焸Ç%b)'N~)'{}% {'o`uDk_GZRB*Y~w0''ڀ:SLVh~'TTT$%%)!!~nqvw!Dqnpܸq4a„_!@H @1ڵ+@Ν;u3f 4ȌtۤU !.͊!˗/pU5kiBMzpx[nޛՠ>(mQ8z@_S/&M>}zM ܿ?^ aΝk!g?{'XF垒„3XIY9۶mЃ>pYpox!z9s77N&qΜ95n8  6|dcƌa!Nv3W&2ha# kĉC! @V8~[/19c_UmF=z֭H\MRRҕQ^1›ۖ-[H$̮aw1i'V{ZmG?5hcAغukod{y/棼5!HQ(3js~i? B~={!4ͻ1s%¿MMS[۬B~0ӧ7(U{ dsw[jՎ;.\{;t}ŋg븖D+:Y~ Ck׮6mqfՇiӆ nMvo[">,.~ߖ8.j 5r8z3b~|X:h6ֽ-R BWԓf$<ʕ+RSS17ꩧBiXӧ~Z~T{h3P l4^~߇@,;6 GŜ1SY?ޒn(:X[k% s}G j~K9@03C /FCX CVAx㫼&Tx t\_RRRbbѣGۈBޒ%֊CяR›B, (.`#GyCG8LHH(x]b 9~PD" w'Ə@Hسg B=IU084L7:V{h!}9&&oihhB5.uІЊ/yYhjKDǡjq999Ase[UAAcλ9@GZU3;C>r۪q$3#92Ap4*}£ϥ+;;{n~8@ g;xj!QGj(JmaaV?&kbXm 4r>]x7:/F qt>8$|lWUVs9;?qC1k emأԩ 3̧>j"H)ѧHo߾@?K4n'ZYD2AAPtih t> ia@3Ν+o'6:{,xS=߿޽ub(^h4󻤚ٶ:6c0 @I0&[R|uŅ >S~~~^ٳg_YƟ<$|={ڵ+22?܉Xnw&>A'$@ơ Mk 5>+YF|7("rn@!ZB&%_W뽢$I &U"A9@h1:}% BI R-.uC)+L^ ~;PnG'HP3t;4unۦ(Nܿn0AJٳ8CbƋ/0"'$5Esdݿ@J$''hn^«Go9s&--/$m2 $?!^U P*&wӧO<~ɼ8A(I~fffB q)AXpݟpfn6 $?3!a#NnTBBBތf$U0==Yǹ ҐAOTD6H B@´4ŋkrڍȯHSS$ BI!<}4;w BI$$ ¿n'^$CÇ{EWDD}_IC} _K 5T1k%AxB֭[H{{;ٳ^{ռy{۹_w&i߾=.7/_ƬxAC'Nxko9ۺukݧNjئoO:եKR8aӦM*EEEELCJBz^ܹi.\a=BsҤIcǎ.gqqʕ+m7 ̔)S^z%KP Cx!b#Fq!'1Fy7 14zWhѢB{m۶߿߹/o4*66^rtm޼~J:wQ^^ȑ#_y>0l}0%%(qfgVV͙ ;'//o˖-'O'C;vܸpvv6)֭['Jt3|AqqqwN!h8J4kmK12w ]?a'U5 vYc1YZS1k8,ݯ!ZM?[ߏ3ܖtAJ$iܹs 2vzg3dd&8qocmEEŸq^ug"^pOcƌpС/B7o^L5vJ\mF-_C ˺'4zϕ5ӴdlٲN: k֬V B@ӄE>KT  vΜ9PaG'Ont~ZZZq98|_|Kx Zwy#ydE>a{I:Gg̘aWa`E:(2tͧ']J4QZa7jŘZ:Ww#M]~㖹!&^CyU͞=n^~e {*((񱄻G*~@:C`+HI8Q+<2tRF~˦JLlrGCpeo<,ؐ=hϮe4|..Q U+G<$%%\l&UVVTTaP( idfRTTDzOĬAC!l-_JP(.C !ɼ>k,hC !dD.ƬERo9-3Fl6 yQ*!_TBK7`PH(ʁ/ԩSG]\\l`[ 5!ELb%R]>|V[͘ɓ'9j4#`YTiiW,m3?)I`TVXS&GYK`J%fȔ̪ bvXG̸ :oxT"bRı4!‡j{?gsNxBL|(~.bR}|+ !NAAo3s}]&/&3]Ʃ-|uv7r N 4'n6`3|hG0CJҊ`7=XXX !@bXƍ7ll4rCoǛ'R[xY?N/S8,rPB{bӮ];L[bf^S%Lb O]BH& ''fzBԖ:la[9q&9‚y 9hdBw ˴8sLĽ#᷷,#2F#0-+Ff2}G.\oqv E8@bK@hqkɜ`(KӦM&lng] ʂjUNnff&~{64,YB P=l׻i̡>1UZ)^FɐxJB BRggĄ 4ʕ+Ϊ޶T &ԩӲe˼xз99m 5k}/<$ H?bBqk6i7# t[@%?vDܵ`q7* hVȒ f09LK1~ve B386z!@zO xK!s;1`&Ld}رcG,Ս4̡Ν;@p!7NcNv7&T(x`W$ vԐIt1k,Kg{6x6f? Nyvˁ6iRk֬!E)H&o>Dt/ B&A?ڿ?S} h\\o9b\ɬ#~L+4~!ƵE-7y,C["pHBn<ի iITa>}:3w}w~[w]@ȸ>LU`o߾=/6aIh0GPYyH隃hBJz BzL $A(%IJ FիW>pƌ۷k+%)TvD*++gGsss3l=_ B$ BIP$ BA(IP $A(IPJ $A(%Q!L?6+6lh,N8ɓ:IjT*I $A(IPJ $A(%IJ WAivvvZZ%&&FGGW{$C ,WRRRPPs)yBIR8*IP$A(IP$A(IP$A(IP$A(I0Ģ qa@qƟg?OFUTTѕaCCXXX8`֭[ϙ3G+ b…|˗iȑ#[j5l0JK!={v˖-qpHJAAA߾}MVG:~nݺuڵgϞcƌپ}{yy,F>p޼yORhIII]rGlLo߾Euqiii2IΝ;|#8ܰaC3G^|իWΚ5>hgugwi83Qt8ͥs]j|W5䨕!o l |Μ9ڴiCIY!Dż֮]ۻwx>377NKLLċvЁ "<ׯwlڴ驧jѢ4hPLL!?a„.]pkZb L8yK%KӲe˺w~SSn*Ο?umbQB/H7ށΝ;uʒ8y̙&skXJwI /,\o%AX-@L6$> !OCĉʲp]Çv؁W.W^ǏC[fMR0}v;Db~ۼaÆ٤DCQԇ:ˠaCHig~Q(>0~W>޻w}ZZ·[ι;? _ǎxb{xG}Yf8}rp۶mv<1¡K&8aL)))BSJ Z/7#EW׮]CyUVjs5NM0tx?p$0 HڵBH.ndkO>w ̒ G0'|һV&9s5<֭[60ƅ‰Ϛ@H&cǎA2nY+E<{<!URucr;u6ifΜ9dzO. add$3))=zn$=ztAA. 055$4W͞={РA4Ƕ[8:̘1wށ oOBzyС`͛{M<̚Ch6ıCs"w$0--?:::{YԆ̾A/\O>Є;wMٳVTTl߾ӀRRRln9i&[noUK,!oVTTFcAdƍ2eee%Q!]R>SlllЛdwܹj*! IF2oذc(#ZBkFe%IzMXߢO-I!&'' /^y$!o_?O92,,?CN8/JRCCXQQ, 8y~ٳmVoK fM١C|;DR$5(ϟ榤7% c%IJ $%I BI$$ BI$ BI$ BI$ BI$ BI$$ BI y(I,JJJ`srr222p񥥥qI W@`qq19N8'%QbIaքژ$A(IPJ $A(I $A(Iu˗zOYYYKI6(]tiժU6mZl9i$$\n鮻 C;|gHPἼ<@F)Qcbb6mPԾ}.@뗖&(/>>>f:z(ץ_jTTN"##8!}MxebWSEEE0`@>}F1|wq̙+uaYYYN~P!٤G^|>״۷{w+++tBC i۶mCիo/L-7u]tپ};G:p6mv vՑ#G8СCք7v9<<^9wBKQ*& rf3eq $)S`Ё!\~}=A8cƌW^yױݻwu+p4uփ0vZ*--wi&?~|׮]YyB`ܹ6ӧO?Ǐ=8d fVZp֭pժU($X8ʲ Э?W\K,9r5k9s޼yؽ{7ŹL6 L dI/EDDť111ݺu㿽χ?йPI6d <@~a|TH 8p`=p;/B޽{Lv/;;O>۶msFÆ KyM5mر eϞ=O:}ݬYO!IXe999!_&$$X[dx+]|_~E ѩ OHz%OH@$Ffffi\K/BT5, !`dDz ҥ(BzUibb"oY J ?ŋ؉',qϞ=$kѣG5!ޕ^ƍ7tP'\\a ;p ::Cu.9;qIJ2C1*+U;kJg`p9Wq￟DdnLz[n?!.A~_'p;w[&r Mq-[)b˖-곳!' w8::Νk׮|p_.(j 4l۶CjjI. W^Zb9}L4ҍx!b[u@^^JbX ݤ# z^C[TŒKL^޽t qBxɐ|/77wΝU'BڠAaRhW$I^%$IJ aNN FY>$ ZCXRR֮];=Acǎ\ttyyyfjڴ}gG- 8 ɷ($IB $ BIP$ BA(IP $A(IPJ $A(%IJ $%IJ$%IJ$%IJTr1YԕW$I~ aa@ȗL$I Xp2Jք$$ BIP$ BI$ :{v뭷޼yKI/d5eʔG#<з=A4 %111wC5O?M ܕ*--ݼyC "C߽{>5Ӌ/XRRtY t/SWQQa.4hP}86//oС6l:]CFI5jW811} r-CsmݺU E5kv뭷~/\~Yp!ŴtǺuپ}{=222`$C!F۶m/Ν[~cޖ7SNE0 xi&5>yONrx3fǏ_qƙ3g"^ 6 !zj2ȢqڴiD_K***"b_f 9P}1Nn/&}ժUuBW_mӦM`WXQ~vuw^˱0`ٳ_|'N|7Yw=ztii)taʔ)BVt֌O#pݻ7>a„g}֕l9e0E̙39Ѵ^z.]:f̘.]X L.+Wegg>SN4ڶ́yf%K^Vט8_ VZYj] ._ޮ_9"ڵk/[lZ`86!!sLMKx饗 t۷b$ 9`ǘqa4iɓqו=k,8~wB#b_rD4|0?Qf0Ow`&rȍ9^ Bc"b4wDG.Yyw}Aĉ~7' ,?v%ZP9fT5pnBh=;w`\ v *e˖=!<|pH7e%''0h~7ޠu,p 9H ҵkWB>Qwܹcǎ̺u명_\&fFyM%hK^wݳgO;۽,2>3cǎ-..*S~&M`Q`111V?~<qرc#xUq}AJJ P87JMMh Hj!d {3AR1VŞB9t1x `@͖Hm֭[_ pp[]T7b*va#E#@H,7sqq-.eԨQlt>wqB  0U 2d۶mPNJвgqqqC^Q8@B:V̚5 gn"""ݻۆdR+WⓁCr('xd\p BIޤBz399>&IP $A(IPJ $A(%IJM aIIA߬+AaAA F0'''%%EJRW0b!P'%)X3) CBfnnnjj j!¢PBHF8VW{iA(IBo>C !' 8\{$I^ᥲX~ϟdaB6|C9BI*ԙ3gО-m_Bw3PqA \Sϒ͞xq6HmY'XJWg(B+VE8C iht] FpNyyyphM=,!8q")))!!!..ñWXa9r䔔LQ^nj  JGķu\ny ɓX{jj*4 DR*w0z!))H= çtIe6:{,@Bm5ЏCʣTBSF|$醖ٹ8$QѫnK>N)%]p'hYSGtkf Homepage: https://github.com/xperimental/nextcloud-exporter Section: net Priority: optional Recommends: prometheus Description: Prometheus exporter for Nextcloud metrics The nextcloud-exporter can be used to gather metrics from a Nextcloud server instance. . A Prometheus server is needed to pull and store the metrics generated by the exporter, but it does not need to be on the same machine. prometheus-nextcloud-exporter-0.4.0/contrib/nextcloud-exporter.service000066400000000000000000000005541403206152000264510ustar00rootroot00000000000000[Unit] Description=Prometheus exporter for Nextcloud metrics After=network.target nss-lookup.target [Service] Type=simple ExecStart=/usr/bin/nextcloud-exporter -c /etc/nextcloud-exporter.yml User=nextcloud-exporter Group=nextcloud-exporter PrivateTmp=true ProtectHome=true ProtectSystem=full Restart=on-failure RestartSec=20 [Install] WantedBy=multi-user.target prometheus-nextcloud-exporter-0.4.0/go.mod000066400000000000000000000006211403206152000206550ustar00rootroot00000000000000module github.com/xperimental/nextcloud-exporter go 1.15 require ( github.com/google/go-cmp v0.5.4 github.com/prometheus/client_golang v1.9.0 github.com/prometheus/procfs v0.3.0 // indirect github.com/sirupsen/logrus v1.7.0 github.com/spf13/pflag v1.0.5 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect google.golang.org/protobuf v1.25.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) prometheus-nextcloud-exporter-0.4.0/go.sum000066400000000000000000001255401403206152000207120ustar00rootroot00000000000000cloud.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= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 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-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= 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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 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.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 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/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU= github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/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/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0 h1:Uehi/mxLK0eiUc0H0++5tpMGTexB8wZ598MIgU8VpDM= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 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/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1/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/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 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-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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-20180906233101-161cd47e91fd/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-20190125091013-d26f9f9a57f3/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-20190404232315-eb5bcb51f2a3/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-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20180909124046-d0be0721c37e/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-20181122145206-62eef0e2fa9b/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-20190412213103-97732733099d/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-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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-20180828015842-6cd1fcedba52/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-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-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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/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-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/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= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= prometheus-nextcloud-exporter-0.4.0/internal/000077500000000000000000000000001403206152000213645ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/internal/config/000077500000000000000000000000001403206152000226315ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/internal/config/config.go000066400000000000000000000111631403206152000244270ustar00rootroot00000000000000package config import ( "errors" "fmt" "io/ioutil" "os" "strings" "time" "github.com/spf13/pflag" "gopkg.in/yaml.v2" ) const ( envPrefix = "NEXTCLOUD_" envListenAddress = envPrefix + "LISTEN_ADDRESS" envTimeout = envPrefix + "TIMEOUT" envServerURL = envPrefix + "SERVER" envUsername = envPrefix + "USERNAME" envPassword = envPrefix + "PASSWORD" ) // Config contains the configuration options for nextcloud-exporter. type Config struct { ListenAddr string `yaml:"listenAddress"` Timeout time.Duration `yaml:"timeout"` ServerURL string `yaml:"server"` Username string `yaml:"username"` Password string `yaml:"password"` Login bool ShowHelp bool } // Validate checks if the configuration contains all necessary parameters. func (c Config) Validate() error { if len(c.ServerURL) == 0 { return errors.New("need to set a server URL") } if len(c.Username) == 0 { return errors.New("need to provide a username") } if len(c.Password) == 0 { return errors.New("need to provide a password") } return nil } // Get loads the configuration. Flags, environment variables and configuration file are considered. func Get() (Config, error) { return parseConfig(os.Args, os.Getenv) } func parseConfig(args []string, envFunc func(string) string) (Config, error) { result, configFile, err := loadConfigFromFlags(args) if err != nil { return Config{}, fmt.Errorf("error parsing flags: %s", err) } if configFile != "" { rawFile, err := loadConfigFromFile(configFile) if err != nil { return Config{}, fmt.Errorf("error reading configuration file: %s", err) } result = mergeConfig(result, rawFile) } env, err := loadConfigFromEnv(envFunc) if err != nil { return Config{}, fmt.Errorf("error reading environment variables: %s", err) } result = mergeConfig(result, env) if strings.HasPrefix(result.Password, "@") { fileName := strings.TrimPrefix(result.Password, "@") password, err := readPasswordFile(fileName) if err != nil { return Config{}, fmt.Errorf("can not read password file: %s", err) } result.Password = password } return result, nil } func defaultConfig() Config { return Config{ ListenAddr: ":9205", Timeout: 5 * time.Second, } } func loadConfigFromFlags(args []string) (result Config, configFile string, err error) { defaults := defaultConfig() flags := pflag.NewFlagSet(args[0], pflag.ContinueOnError) flags.StringVarP(&configFile, "config-file", "c", "", "Path to YAML configuration file.") flags.StringVarP(&result.ListenAddr, "addr", "a", defaults.ListenAddr, "Address to listen on for connections.") flags.DurationVarP(&result.Timeout, "timeout", "t", defaults.Timeout, "Timeout for getting server info document.") flags.StringVarP(&result.ServerURL, "server", "s", "", "URL to Nextcloud server.") flags.StringVarP(&result.Username, "username", "u", defaults.Username, "Username for connecting to Nextcloud.") flags.StringVarP(&result.Password, "password", "p", defaults.Password, "Password for connecting to Nextcloud.") flags.BoolVar(&result.Login, "login", defaults.Login, "Use interactive login to create app password.") if err := flags.Parse(args[1:]); err != nil { if err == pflag.ErrHelp { return Config{ ShowHelp: true, }, "", nil } return Config{}, "", err } return result, configFile, nil } func loadConfigFromFile(fileName string) (Config, error) { file, err := os.Open(fileName) if err != nil { return Config{}, err } var result Config if err := yaml.NewDecoder(file).Decode(&result); err != nil { return Config{}, err } return result, nil } func loadConfigFromEnv(getEnv func(string) string) (Config, error) { result := Config{ ListenAddr: getEnv(envListenAddress), ServerURL: getEnv(envServerURL), Username: getEnv(envUsername), Password: getEnv(envPassword), } if raw := getEnv(envTimeout); raw != "" { value, err := time.ParseDuration(raw) if err != nil { return Config{}, err } result.Timeout = value } return result, nil } func mergeConfig(base, override Config) Config { result := base if override.ListenAddr != "" { result.ListenAddr = override.ListenAddr } if override.ServerURL != "" { result.ServerURL = override.ServerURL } if override.Username != "" { result.Username = override.Username } if override.Password != "" { result.Password = override.Password } if override.Timeout != 0 { result.Timeout = override.Timeout } return result } func readPasswordFile(fileName string) (string, error) { bytes, err := ioutil.ReadFile(fileName) if err != nil { return "", err } return strings.TrimSuffix(string(bytes), "\n"), nil } prometheus-nextcloud-exporter-0.4.0/internal/config/config_test.go000066400000000000000000000130431403206152000254650ustar00rootroot00000000000000package config import ( "errors" "net/url" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/xperimental/nextcloud-exporter/internal/testutil" ) func testEnv(env map[string]string) func(string) string { return func(key string) string { return env[key] } } func mustURL(raw string) *url.URL { u, err := url.Parse(raw) if err != nil { panic(err) } return u } func TestConfig(t *testing.T) { defaults := defaultConfig() tt := []struct { desc string args []string env map[string]string wantErr error wantConfig Config }{ { desc: "flags", args: []string{ "test", "--addr", "127.0.0.1:9205", "--timeout", "30s", "--server", "http://localhost", "--username", "testuser", "--password", "testpass", }, env: map[string]string{}, wantErr: nil, wantConfig: Config{ ListenAddr: "127.0.0.1:9205", Timeout: 30 * time.Second, ServerURL: "http://localhost", Username: "testuser", Password: "testpass", }, }, { desc: "password from file", args: []string{ "test", "--server", "http://localhost", "--username", "testuser", "--password", "@testdata/password", }, env: map[string]string{}, wantErr: nil, wantConfig: Config{ ListenAddr: defaults.ListenAddr, Timeout: defaults.Timeout, ServerURL: "http://localhost", Username: "testuser", Password: "testpass", }, }, { desc: "config from file", args: []string{ "test", "--config-file", "testdata/all.yml", }, env: map[string]string{}, wantErr: nil, wantConfig: Config{ ListenAddr: "127.0.0.10:9205", Timeout: 10 * time.Second, ServerURL: "http://localhost", Username: "testuser", Password: "testpass", }, }, { desc: "config and password from file", args: []string{ "test", "--config-file", "testdata/passwordfile.yml", }, env: map[string]string{}, wantErr: nil, wantConfig: Config{ ListenAddr: "127.0.0.10:9205", Timeout: 10 * time.Second, ServerURL: "http://localhost", Username: "testuser", Password: "testpass", }, }, { desc: "env config", args: []string{ "test", }, env: map[string]string{ envListenAddress: "127.0.0.11:9205", envTimeout: "15s", envServerURL: "http://localhost", envUsername: "testuser", envPassword: "testpass", }, wantErr: nil, wantConfig: Config{ ListenAddr: "127.0.0.11:9205", Timeout: 15 * time.Second, ServerURL: "http://localhost", Username: "testuser", Password: "testpass", }, }, { desc: "minimal env", args: []string{ "test", }, env: map[string]string{ envServerURL: "http://localhost", envUsername: "testuser", envPassword: "testpass", }, wantErr: nil, wantConfig: Config{ ListenAddr: defaults.ListenAddr, Timeout: defaults.Timeout, ServerURL: "http://localhost", Username: "testuser", Password: "testpass", }, }, { desc: "wrongflag", args: []string{ "test", "--unknown", }, env: map[string]string{}, wantErr: errors.New("error parsing flags: unknown flag: --unknown"), }, { desc: "env wrong duration", args: []string{ "test", }, env: map[string]string{ "NEXTCLOUD_TIMEOUT": "unknown", }, wantErr: errors.New("error reading environment variables: time: invalid duration \"unknown\""), }, { desc: "password from file error", args: []string{ "test", "--server", "http://localhost", "--password", "@testdata/notfound", }, env: map[string]string{}, wantErr: errors.New("can not read password file: open testdata/notfound: no such file or directory"), }, { desc: "config from file error", args: []string{ "test", "--config-file", "testdata/notfound.yml", }, env: map[string]string{}, wantErr: errors.New("error reading configuration file: open testdata/notfound.yml: no such file or directory"), }, } for _, tc := range tt { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() config, err := parseConfig(tc.args, testEnv(tc.env)) if diff := cmp.Diff(err, tc.wantErr, testutil.ErrorComparer); diff != "" { t.Errorf("error differs: -got +want\n%s", diff) } if err != nil { return } if diff := cmp.Diff(config, tc.wantConfig); diff != "" { t.Errorf("config differs: -got +want\n%s", diff) } }) } } func TestConfigValidate(t *testing.T) { tt := []struct { desc string config Config wantErr error }{ { desc: "minimal", config: Config{ ServerURL: "https://example.com", Username: "exporter", Password: "testpass", }, wantErr: nil, }, { desc: "no url", config: Config{ Username: "exporter", Password: "testpass", }, wantErr: errors.New("need to set a server URL"), }, { desc: "no username", config: Config{ ServerURL: "https://example.com", Password: "testpass", }, wantErr: errors.New("need to provide a username"), }, { desc: "no password", config: Config{ ServerURL: "https://example.com", Username: "exporter", }, wantErr: errors.New("need to provide a password"), }, } for _, tc := range tt { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() err := tc.config.Validate() if diff := cmp.Diff(err, tc.wantErr, testutil.ErrorComparer); diff != "" { t.Errorf("error differs: -got +want\n%s", diff) } }) } } prometheus-nextcloud-exporter-0.4.0/internal/config/testdata/000077500000000000000000000000001403206152000244425ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/internal/config/testdata/all.yml000066400000000000000000000001531403206152000257340ustar00rootroot00000000000000listenAddress: 127.0.0.10:9205 timeout: 10s server: http://localhost username: testuser password: testpass prometheus-nextcloud-exporter-0.4.0/internal/config/testdata/password000066400000000000000000000000111403206152000262170ustar00rootroot00000000000000testpass prometheus-nextcloud-exporter-0.4.0/internal/config/testdata/passwordfile.yml000066400000000000000000000001671403206152000276730ustar00rootroot00000000000000listenAddress: 127.0.0.10:9205 timeout: 10s server: http://localhost username: testuser password: "@testdata/password" prometheus-nextcloud-exporter-0.4.0/internal/login/000077500000000000000000000000001403206152000224745ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/internal/login/login.go000066400000000000000000000112731403206152000241370ustar00rootroot00000000000000package login import ( "encoding/json" "fmt" "io" "net/http" "strconv" "strings" "time" "github.com/sirupsen/logrus" ) const ( statusPath = "/status.php" minimumMajorVersion = 16 loginPath = "/index.php/login/v2" pollInterval = time.Second contentType = "application/x-www-form-urlencoded" ) type loginInfo struct { LoginURL string `json:"login"` PollInfo pollInfo `json:"poll"` } type pollInfo struct { Token string `json:"token"` Endpoint string `json:"endpoint"` } type passwordInfo struct { Server string `json:"server"` LoginName string `json:"loginName"` AppPassword string `json:"appPassword"` } // Login contains the login information gathered during the login session. type Login struct { Username string Password string } // Client can be used to start an interactive login session with a Nextcloud server. type Client struct { log logrus.FieldLogger userAgent string serverURL string client *http.Client sleepFunc func() } // Init creates a new LoginClient. The session can then be started using StartInteractive. func Init(log logrus.FieldLogger, userAgent, serverURL string) *Client { return &Client{ log: log, userAgent: userAgent, serverURL: serverURL, client: &http.Client{ Timeout: 30 * time.Second, }, sleepFunc: func() { time.Sleep(pollInterval) }, } } // StartInteractive starts an interactive login session for the Nextcloud server and user. // The end-result of this is an app-password for the exporter which should be used instead of a user password. func (c *Client) StartInteractive() error { version, err := c.getMajorVersion() if err != nil { return fmt.Errorf("error getting version: %s", err) } if version < minimumMajorVersion { return fmt.Errorf("Nextcloud version too old for login: %d Minimum: %d", version, minimumMajorVersion) } info, err := c.getLoginInfo() if err != nil { return fmt.Errorf("error getting login info: %s", err) } c.log.Infof("Please open this URL in a browser: %s", info.LoginURL) c.log.Infoln("Waiting for login ... (Ctrl-C to abort)") login, err := c.pollLogin(info.PollInfo) if err != nil { return fmt.Errorf("error during poll: %s", err) } c.log.Infof("Username: %s", login.Username) c.log.Infof("Password: %s", login.Password) return nil } func (c *Client) doRequest(method, url string, body io.Reader) (*http.Response, error) { req, err := http.NewRequest(method, url, body) if err != nil { return nil, fmt.Errorf("can not create request: %s", err) } req.Header.Set("User-Agent", c.userAgent) if body != nil { req.Header.Set("Content-Type", contentType) } res, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("error connecting: %s", err) } return res, nil } func (c *Client) getMajorVersion() (int, error) { statusURL := c.serverURL + statusPath res, err := c.doRequest(http.MethodGet, statusURL, nil) if err != nil { return 0, fmt.Errorf("error connecting: %s", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { return 0, fmt.Errorf("non-ok status: %d", res.StatusCode) } var status struct { Version string `json:"version"` } if err := json.NewDecoder(res.Body).Decode(&status); err != nil { return 0, fmt.Errorf("error decoding status: %s", err) } tokens := strings.SplitN(status.Version, ".", 2) version, err := strconv.Atoi(tokens[0]) if err != nil { return 0, fmt.Errorf("can not parse %q as version: %s", status.Version, err) } return version, nil } func (c *Client) getLoginInfo() (loginInfo, error) { loginURL := c.serverURL + loginPath res, err := c.doRequest(http.MethodPost, loginURL, nil) if err != nil { return loginInfo{}, fmt.Errorf("error connecting: %s", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { return loginInfo{}, fmt.Errorf("non-ok status: %d", res.StatusCode) } var result loginInfo if err := json.NewDecoder(res.Body).Decode(&result); err != nil { return loginInfo{}, fmt.Errorf("error decoding login info: %s", err) } return result, nil } func (c *Client) pollLogin(info pollInfo) (Login, error) { body := fmt.Sprintf("token=%s", info.Token) c.log.Debugf("poll endpoint: %s", info.Endpoint) for { c.sleepFunc() reader := strings.NewReader(body) res, err := c.doRequest(http.MethodPost, info.Endpoint, reader) if err != nil { continue } defer res.Body.Close() if res.StatusCode != http.StatusOK { c.log.Debugf("poll status: %d", res.StatusCode) continue } var password passwordInfo if err := json.NewDecoder(res.Body).Decode(&password); err != nil { return Login{}, fmt.Errorf("error decoding password info: %s", err) } return Login{ Username: password.LoginName, Password: password.AppPassword, }, nil } } prometheus-nextcloud-exporter-0.4.0/internal/login/login_test.go000066400000000000000000000103271403206152000251750ustar00rootroot00000000000000package login import ( "errors" "fmt" "net/http" "net/http/httptest" "testing" "github.com/google/go-cmp/cmp" "github.com/sirupsen/logrus" "github.com/xperimental/nextcloud-exporter/internal/testutil" ) func testClient(url string) *Client { return &Client{ log: logrus.New(), client: &http.Client{}, serverURL: url, sleepFunc: func() {}, } } func testHandler(status int, body string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(status) fmt.Fprintln(w, body) }) } func TestGetMajorVersion(t *testing.T) { tt := []struct { desc string testHandler http.Handler wantErr error wantVersion int }{ { desc: "success", testHandler: testHandler(http.StatusOK, `{"version": "18.0.2"}`), wantVersion: 18, }, { desc: "parse error", testHandler: testHandler(http.StatusOK, ``), wantErr: errors.New("error decoding status: EOF"), }, { desc: "error version", testHandler: testHandler(http.StatusOK, `{"version": "unparseable"}`), wantErr: errors.New(`can not parse "unparseable" as version: strconv.Atoi: parsing "unparseable": invalid syntax`), }, { desc: "error http", testHandler: testHandler(http.StatusInternalServerError, "test error"), wantErr: errors.New("non-ok status: 500"), }, } for _, tc := range tt { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() s := httptest.NewServer(tc.testHandler) defer s.Close() c := testClient(s.URL) version, err := c.getMajorVersion() if diff := cmp.Diff(err, tc.wantErr, testutil.ErrorComparer); diff != "" { t.Errorf("errors differ: -got +want\n%s", diff) } if err != nil { return } if version != tc.wantVersion { t.Errorf("got version %d, want %d", version, tc.wantVersion) } }) } } func TestGetLoginInfo(t *testing.T) { tt := []struct { desc string testHandler http.Handler wantErr error wantInfo loginInfo }{ { desc: "success", testHandler: testHandler(http.StatusOK, `{"login": "http://localhost/login", "poll": {"token": "token", "endpoint": "http://localhost/poll"}}`), wantInfo: loginInfo{ LoginURL: "http://localhost/login", PollInfo: pollInfo{ Token: "token", Endpoint: "http://localhost/poll", }, }, }, { desc: "parse error", testHandler: testHandler(http.StatusOK, ``), wantErr: errors.New("error decoding login info: EOF"), }, { desc: "error http", testHandler: testHandler(http.StatusInternalServerError, "test error"), wantErr: errors.New("non-ok status: 500"), }, } for _, tc := range tt { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() s := httptest.NewServer(tc.testHandler) defer s.Close() c := testClient(s.URL) info, err := c.getLoginInfo() if diff := cmp.Diff(err, tc.wantErr, testutil.ErrorComparer); diff != "" { t.Errorf("errors differ: -got +want\n%s", diff) } if err != nil { return } if diff := cmp.Diff(info, tc.wantInfo); diff != "" { t.Errorf("info differs: -got +want\n%s", diff) } }) } } func TestPollPassword(t *testing.T) { tt := []struct { desc string testHandler http.Handler pollInfo pollInfo wantErr error wantLogin Login }{ { desc: "success", testHandler: testHandler(http.StatusOK, `{"loginName": "username", "appPassword": "password"}`), wantLogin: Login{ Username: "username", Password: "password", }, }, { desc: "parse error", testHandler: testHandler(http.StatusOK, ``), wantErr: errors.New("error decoding password info: EOF"), }, } for _, tc := range tt { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() s := httptest.NewServer(tc.testHandler) defer s.Close() tc.pollInfo.Endpoint = s.URL c := testClient("") login, err := c.pollLogin(tc.pollInfo) if diff := cmp.Diff(err, tc.wantErr, testutil.ErrorComparer); diff != "" { t.Errorf("errors differ: -got +want\n%s", diff) } if err != nil { return } if diff := cmp.Diff(login, tc.wantLogin); diff != "" { t.Errorf("login differs: -got +want\n%s", diff) } }) } } prometheus-nextcloud-exporter-0.4.0/internal/testutil/000077500000000000000000000000001403206152000232415ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/internal/testutil/testutil.go000066400000000000000000000003521403206152000254450ustar00rootroot00000000000000package testutil import "github.com/google/go-cmp/cmp" // ErrorComparer provides a way to compare errors using cmp.Diff var ErrorComparer = cmp.Comparer(func(a, b error) bool { aE := a.Error() bE := b.Error() return aE == bE }) prometheus-nextcloud-exporter-0.4.0/main.go000066400000000000000000000045041403206152000210260ustar00rootroot00000000000000package main import ( "fmt" "net/http" "os" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" "github.com/xperimental/nextcloud-exporter/internal/config" "github.com/xperimental/nextcloud-exporter/internal/login" "github.com/xperimental/nextcloud-exporter/serverinfo" ) var ( // Version contains the version as set during the build. Version = "" // GitCommit contains the git commit hash set during the build. GitCommit = "" log = &logrus.Logger{ Out: os.Stderr, Formatter: &logrus.TextFormatter{ DisableTimestamp: true, }, Hooks: make(logrus.LevelHooks), Level: logrus.InfoLevel, ExitFunc: os.Exit, ReportCaller: false, } ) func main() { log.Infof("nextcloud-exporter %s", Version) userAgent := fmt.Sprintf("nextcloud-exporter/%s", Version) cfg, err := config.Get() if err != nil { log.Fatalf("Error loading configuration: %s", err) } if cfg.ShowHelp { return } if cfg.Login { if cfg.ServerURL == "" { log.Fatalf("Need to specify --server for login.") } loginClient := login.Init(log, userAgent, cfg.ServerURL) log.Infof("Starting interactive login on: %s", cfg.ServerURL) if err := loginClient.StartInteractive(); err != nil { log.Fatalf("Error during login: %s", err) } return } if err := cfg.Validate(); err != nil { log.Fatalf("Invalid configuration: %s", err) } log.Infof("Nextcloud server: %s User: %s", cfg.ServerURL, cfg.Username) infoURL := cfg.ServerURL + serverinfo.InfoPath collector := newCollector(infoURL, cfg.Username, cfg.Password, cfg.Timeout, userAgent) if err := prometheus.Register(collector); err != nil { log.Fatalf("Failed to register collector: %s", err) } infoMetric := prometheus.NewGauge(prometheus.GaugeOpts{ Name: metricPrefix + "exporter_info", Help: "Information about the nextcloud-exporter.", ConstLabels: prometheus.Labels{ "version": Version, "commit": GitCommit, }, }) infoMetric.Set(1) if err := prometheus.Register(infoMetric); err != nil { log.Fatalf("Failed to register info metric: %s", err) } http.Handle("/metrics", promhttp.Handler()) http.Handle("/", http.RedirectHandler("/metrics", http.StatusFound)) log.Infof("Listen on %s...", cfg.ListenAddr) log.Fatal(http.ListenAndServe(cfg.ListenAddr, nil)) } prometheus-nextcloud-exporter-0.4.0/serverinfo/000077500000000000000000000000001403206152000217325ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/serverinfo/parse.go000066400000000000000000000004251403206152000233740ustar00rootroot00000000000000package serverinfo import ( "encoding/xml" "io" ) // Parse reads ServerInfo from a Reader. func Parse(r io.Reader) (ServerInfo, error) { result := ServerInfo{} if err := xml.NewDecoder(r).Decode(&result); err != nil { return ServerInfo{}, err } return result, nil } prometheus-nextcloud-exporter-0.4.0/serverinfo/parse_test.go000066400000000000000000000007661403206152000244430ustar00rootroot00000000000000package serverinfo import ( "os" "testing" ) func TestParseServerInfo(t *testing.T) { inputFiles := []string{ "negative-space.xml", "na-values.xml", } for _, inputFile := range inputFiles { inputFile := inputFile t.Run(inputFile, func(t *testing.T) { t.Parallel() reader, err := os.Open("testdata/" + inputFile) if err != nil { t.Fatalf("error opening test data: %s", err) } if _, err := Parse(reader); err != nil { t.Errorf("got error %q", err) } }) } } prometheus-nextcloud-exporter-0.4.0/serverinfo/serverinfo.go000066400000000000000000000112641403206152000244470ustar00rootroot00000000000000package serverinfo import "encoding/xml" const ( // InfoPath contains the path to the serverinfo endpoint. InfoPath = "/ocs/v2.php/apps/serverinfo/api/v1/info" ) // ServerInfo contains the complete data received from the server. type ServerInfo struct { Meta Meta `xml:"meta"` Data Data `xml:"data"` } // Meta contains meta information about the result. type Meta struct { Status string `xml:"status"` StatusCode int `xml:"statuscode"` Message string `xml:"message"` } // Data contains the status information about the instance. type Data struct { Nextcloud Nextcloud `xml:"nextcloud"` Server Server `xml:"server"` ActiveUsers ActiveUsers `xml:"activeUsers"` } // Nextcloud contains information about the nextcloud installation. type Nextcloud struct { System System `xml:"system"` Storage Storage `xml:"storage"` Shares Shares `xml:"shares"` } // System contains nextcloud configuration and system information. type System struct { Version string `xml:"version"` Theme string `xml:"theme"` EnableAvatars bool `xml:"enable_avatars"` EnablePreviews bool `xml:"enable_previews"` MemcacheLocal string `xml:"memcache.local"` MemcacheDistributed string `xml:"memcache.distributed"` MemcacheLocking string `xml:"memcache.locking"` FilelockingEnabled bool `xml:"filelocking.enabled"` Debug bool `xml:"debug"` FreeSpace int64 `xml:"freespace"` Apps Apps `xml:"apps"` } const boolYes = "yes" func (s *System) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { var raw struct { Version string `xml:"version"` Theme string `xml:"theme"` EnableAvatars string `xml:"enable_avatars"` EnablePreviews string `xml:"enable_previews"` MemcacheLocal string `xml:"memcache.local"` MemcacheDistributed string `xml:"memcache.distributed"` MemcacheLocking string `xml:"memcache.locking"` FilelockingEnabled string `xml:"filelocking.enabled"` Debug string `xml:"debug"` FreeSpace int64 `xml:"freespace"` Apps Apps `xml:"apps"` } if err := d.DecodeElement(&raw, &start); err != nil { return err } s.Version = raw.Version s.Theme = raw.Theme s.EnableAvatars = raw.EnableAvatars == boolYes s.EnablePreviews = raw.EnablePreviews == boolYes s.MemcacheLocal = raw.MemcacheLocal s.MemcacheDistributed = raw.MemcacheDistributed s.MemcacheLocking = raw.MemcacheLocking s.FilelockingEnabled = raw.FilelockingEnabled == boolYes s.Debug = raw.Debug == boolYes s.FreeSpace = raw.FreeSpace s.Apps = raw.Apps return nil } // Apps contains information about installed apps and updates. type Apps struct { Installed uint `xml:"num_installed"` AvailableUpdates uint `xml:"num_updates_available"` } // Storage contains information about the nextcloud storage system. type Storage struct { Users uint `xml:"num_users"` Files uint `xml:"num_files"` Storages uint `xml:"num_storages"` StoragesLocal uint `xml:"num_storages_local"` StoragesHome uint `xml:"num_storages_home"` StoragesOther uint `xml:"num_storages_other"` } // Shares contains information about nextcloud shares. type Shares struct { SharesTotal uint `xml:"num_shares"` SharesUser uint `xml:"num_shares_user"` SharesGroups uint `xml:"num_shares_groups"` SharesLink uint `xml:"num_shares_link"` SharesLinkNoPassword uint `xml:"num_shares_link_no_password"` FedSent uint `xml:"num_fed_shares_sent"` FedReceived uint `xml:"num_fed_shares_received"` // 2 // 4 // 2 // 2 // 3 // 1 } // Server contains information about the servers running nextcloud. type Server struct { Webserver string `xml:"webserver"` PHP PHP `xml:"php"` Database Database `xml:"database"` } // PHP contains information about the PHP installation. type PHP struct { Version string `xml:"version"` MemoryLimit int64 `xml:"memory_limit"` MaxExecutionTime uint `xml:"max_execution_time"` UploadMaxFilesize int64 `xml:"upload_max_filesize"` } // Database contains information about the database used by nextcloud. type Database struct { Type string `xml:"type"` Version string `xml:"version"` Size uint64 `xml:"size"` } // ActiveUsers contains statistics about the active users. type ActiveUsers struct { Last5Minutes uint `xml:"last5minutes"` LastHour uint `xml:"last1hour"` LastDay uint `xml:"last24hours"` } prometheus-nextcloud-exporter-0.4.0/serverinfo/testdata/000077500000000000000000000000001403206152000235435ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.4.0/serverinfo/testdata/na-values.xml000066400000000000000000000041551403206152000261650ustar00rootroot00000000000000 ok 200 OK 16.0.3.0 none yes yes none none yes none no 669612507136 3.73 3.02 2.6 N/A N/A N/A N/A 30 0 2 327 4 2 2 0 0 0 0 0 0 0 0 0 0 Apache/2 7.3.8 134217728 3600 67108864 mysql 10.1.38 2719744 1 1 1 prometheus-nextcloud-exporter-0.4.0/serverinfo/testdata/negative-space.xml000066400000000000000000000041161403206152000271620ustar00rootroot00000000000000 ok 200 OK 15.0.4.0 none yes yes \OC\Memcache\APCu \OC\Memcache\Redis yes \OC\Memcache\Redis yes -2 0.36 0.55 0.86 7972964 2716156 0 0 34 0 1 223 2 0 0 2 0 0 0 0 0 0 0 Apache/2.4.25 (Debian) 7.2.15 536870912 3600 2097152 mysql 10.1.37 2457600 1 1 1