pax_global_header00006660000000000000000000000064143553350110014512gustar00rootroot0000000000000052 comment=0766bbbbd439019133c16b9433532ac413a274a0 prometheus-nextcloud-exporter-0.6.0/000077500000000000000000000000001435533501100175615ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/.dockerignore000077700000000000000000000000001435533501100242202.gitignoreustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/.editorconfig000066400000000000000000000003661435533501100222430ustar00rootroot00000000000000root = 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.6.0/.github/000077500000000000000000000000001435533501100211215ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/.github/workflows/000077500000000000000000000000001435533501100231565ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/.github/workflows/package.yml000066400000000000000000000021621435533501100252750ustar00rootroot00000000000000name: Package on: push: branches: - master release: types: - published jobs: test: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 with: fetch-depth: 0 - name: Setup Go uses: actions/setup-go@v3 with: go-version-file: go.mod - name: Build and Test run: make docker: needs: test runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 with: fetch-depth: 0 - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v1 - name: Login to GitHub Container Registry uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Login to Docker hub uses: docker/login-action@v1 with: username: xperimental password: ${{ secrets.DOCKER_TOKEN }} - name: Build image run: make all-images env: GITHUB_REF: ${{ github.ref }} prometheus-nextcloud-exporter-0.6.0/.github/workflows/pull-request.yml000066400000000000000000000011401435533501100263370ustar00rootroot00000000000000name: Pull-request on: pull_request: branches: - master jobs: test: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 with: fetch-depth: 0 - name: Setup Go uses: actions/setup-go@v3 with: go-version-file: go.mod - name: Build and Test run: make lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v3 with: go-version-file: go.mod - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: version: v1.49.0 prometheus-nextcloud-exporter-0.6.0/.gitignore000066400000000000000000000001021435533501100215420ustar00rootroot00000000000000run.sh nextcloud-exporter nextcloud-exporter.yml config.yml dist/ prometheus-nextcloud-exporter-0.6.0/.golangci.yaml000066400000000000000000000000411435533501100223010ustar00rootroot00000000000000linters: enable: - gofumpt prometheus-nextcloud-exporter-0.6.0/CHANGELOG.md000066400000000000000000000036271435533501100214020ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.6.0] - 2022-10-19 ### Added - New database info metric (`nextcloud_database_info`) containing type and version - New metrics for hourly and daily active users - Additional label on `nextcloud_scrape_errors_total` for errors due to rate-limiting - Additional labels for email and room shares on `nextcloud_shares_total` ## [0.5.1] - 2022-04-02 ### Fixed - Updated Prometheus client library for CVE-2022-21698 ## [0.5.0] - 2022-01-15 ### Added - Flag for showing version information - Option to disable TLS validation - Token authentication for Nextcloud 22 and newer ### Changed - Switched to JSON from XML for getting information from server - Use different metric for authentication errors ## [0.4.0] - 2021-01-21 ### Added - Metrics for installed apps and available updates ## [0.3.0] - 2020-06-01 ### Added - Makefile target for building deb - Login flow for app password ### Changed - Simpler configuration of server URL ### Fixed - Error in version information ## [0.2.0] - 2020-05-20 ### Added - Version information in binary - Custom User-Agent header - systemd service unit ### Changed - No timestamp in log output ## [0.1.0] - 2019-10-12 - Initial release [0.5.1]: https://github.com/xperimental/nextcloud-exporter/releases/tag/v0.5.1 [0.5.0]: https://github.com/xperimental/nextcloud-exporter/releases/tag/v0.5.0 [0.4.0]: https://github.com/xperimental/nextcloud-exporter/releases/tag/v0.4.0 [0.3.0]: https://github.com/xperimental/nextcloud-exporter/releases/tag/v0.3.0 [0.2.0]: https://github.com/xperimental/nextcloud-exporter/releases/tag/v0.2.0 [0.1.0]: https://github.com/xperimental/nextcloud-exporter/releases/tag/v0.1.0 prometheus-nextcloud-exporter-0.6.0/Dockerfile000066400000000000000000000006671435533501100215640ustar00rootroot00000000000000FROM golang:1.19.2 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.6.0/LICENSE000066400000000000000000000020671435533501100205730ustar00rootroot00000000000000The 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.6.0/Makefile000066400000000000000000000025551435533501100212300ustar00rootroot00000000000000SHELL := /bin/bash 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) GITHUB_REF ?= refs/heads/master DOCKER_TAG != if [[ "$(GITHUB_REF)" == "refs/heads/master" ]]; then \ echo "latest"; \ else \ echo "$(VERSION)"; \ fi .PHONY: all all: test build-binary .PHONY: test test: $(GO_CMD) test -cover ./... .PHONY: lint lint: golangci-lint run --fix .PHONY: build-binary build-binary: $(GO_CMD) build -tags netgo -ldflags "-w -X main.Version=$(VERSION) -X main.GitCommit=$(GIT_COMMIT)" -o nextcloud-exporter . .PHONY: deb 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 .PHONY: install install: install -D -t $(DESTDIR)/usr/bin/ nextcloud-exporter install -D -m 0644 -t $(DESTDIR)/lib/systemd/system/ contrib/nextcloud-exporter.service .PHONY: image image: docker build -t "xperimental/nextcloud-exporter:$(DOCKER_TAG)" . .PHONY: all-images all-images: docker buildx build -t "ghcr.io/xperimental/nextcloud-exporter:$(DOCKER_TAG)" -t "xperimental/nextcloud-exporter:$(DOCKER_TAG)" --platform linux/amd64,linux/arm64 --push . .PHONY: clean clean: rm -f nextcloud-exporter rm -r dist prometheus-nextcloud-exporter-0.6.0/README.md000066400000000000000000000231211435533501100210370ustar00rootroot00000000000000# nextcloud-exporter A [prometheus](https://prometheus.io) exporter for getting some metrics of a nextcloud server instance. ## Installation ### Docker Image The preferred way to use `nextcloud-exporter` is by running the provided Docker image. It is currently provided on Docker Hub and GitHub Container Registry: ```plain docker pull ghcr.io/xperimental/nextcloud-exporter:latest docker pull xperimental/nextcloud-exporter:latest ``` In addition to the `latest` tag which points to the version currently in the `master` branch, tagged versions are also available. ### From Source If you have a recent (>= 1.16) working Go installation and GNU Make, getting the binary should be as simple as ```bash git clone https://github.com/xperimental/nextcloud-exporter.git cd nextcloud-exporter make ``` After this there should be a `nextcloud-exporter` binary in your current directory. ## Client credentials The exporter supports two different approaches for authenticating with the Nextcloud server: - Token authentication (needs Nextcloud 22 or newer) - Username and password If you have Nextcloud 22 then using the token authentication is recommended, because it does not need a normal user account with admin privileges. If both a token and username/password are specified in the configuration, the token will take precedence. ### Token authentication Nextcloud 22 and newer versions support "token authentication" for the serverinfo. That way, accessing this information does not need a normal user account with admin privileges. You can set the token to anything you like, but the recommendation is to set it to a long random number: ```bash # Generate random value (for example using openssl) TOKEN=$(openssl rand -hex 32) # Set token (using the occ console application) occ config:app:set serverinfo token --value "$TOKEN" ``` You can then use this generated token in the exported configuration instead of username and password. ### Username and password authentication 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 ghcr.io/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") --auth-token string Authentication token. Can replace username and password when using Nextcloud 22 or newer. -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) --tls-skip-verify Skip certificate verification of Nextcloud server. -u, --username string Username for connecting to Nextcloud. -V, --version Show version information and exit. ``` 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_AUTH_TOKEN` | --auth-token | | `NEXTCLOUD_LISTEN_ADDRESS` | --addr | | `NEXTCLOUD_TIMEOUT` | --timeout | | `NEXTCLOUD_TLS_SKIP_VERIFY` | --tls-skip-verify | #### Configuration file The `--config-file` option can be used to read the configuration options from a YAML file: ```yaml # required server: "https://example.com" # required for token authentication authToken: "example-token" # required for username/password authentication username: "example" password: "example" # optional listenAddress: ":9205" timeout: "5s" tlsSkipVerify: false ``` ### Loading Credentials from Files Both the authentication token and the password can optionally be read from a separate file instead of directly from the input methods above. This can be achieved by setting the value to the path of the file prefixed with an "@", for example: ```bash # Authentication token nextcloud-exporter -c config-without-token.yml --auth-token @/path/to/tokenfile # Password nextcloud-exporter -c config-without-password.yml -p @/path/to/passwordfile ``` This also works when the password or token is set using one of the other configuration modes (configuration file or environment variables). ## 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'] ``` ### Exported metrics These metrics are exported by `nextcloud-exporter`: | name | description | |----------------------------------------|------------------------------------------------------------------------| | nextcloud_active_users_daily_total | Number of active users in the last 24 hours | | nextcloud_active_users_hourly_total | Number of active users in the last hour | | nextcloud_active_users_total | Number of active users for the last five minutes | | nextcloud_apps_installed_total | Number of currently installed apps | | nextcloud_apps_updates_available_total | Number of apps that have available updates | | nextcloud_database_info | Contains meta information about the database as labels. Value is always 1. | | nextcloud_database_size_bytes | Size of database in bytes as reported from engine | | nextcloud_exporter_info | Contains meta information of the exporter. Value is always 1. | | nextcloud_files_total | Number of files served by the instance | | nextcloud_free_space_bytes | Free disk space in data directory in bytes | | nextcloud_php_info | Contains meta information about PHP as labels. Value is always 1. | | nextcloud_php_memory_limit_bytes | Configured PHP memory limit in bytes | | nextcloud_php_upload_max_size_bytes | Configured maximum upload size in bytes | | nextcloud_scrape_errors_total | Counts the number of scrape errors by this collector | | nextcloud_shares_federated_total | Number of federated shares by direction `sent` / `received` | | nextcloud_shares_total | Number of shares by type:
`authlink`: shared password protected links
`group`: shared groups
`link`: all shared links
`user`: shared users
`mail`: shared by mail
`room`: shared with room | | nextcloud_system_info | Contains meta information about Nextcloud as labels. Value is always 1.| | nextcloud_up | Indicates if the metrics could be scraped by the exporter:
`1`: successful
`0`: unsuccessful (server down, server/endpoint not reachable, invalid credentials, ...) | | nextcloud_users_total | Number of users of the instance | prometheus-nextcloud-exporter-0.6.0/contrib/000077500000000000000000000000001435533501100212215ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/contrib/allow-filesystem.png000066400000000000000000000314111435533501100252270ustar00rootroot00000000000000PNG  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.6.0/contrib/nextcloud-exporter.service000066400000000000000000000006541435533501100264630ustar00rootroot00000000000000[Unit] Description=Prometheus exporter for Nextcloud metrics Documentation=https://github.com/xperimental/nextcloud-exporter 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.6.0/go.mod000066400000000000000000000013311435533501100206650ustar00rootroot00000000000000module github.com/xperimental/nextcloud-exporter go 1.19 require ( github.com/google/go-cmp v0.5.9 github.com/prometheus/client_golang v1.13.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/pflag v1.0.5 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect google.golang.org/protobuf v1.28.1 // indirect ) prometheus-nextcloud-exporter-0.6.0/go.sum000066400000000000000000001375571435533501100207360ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 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/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 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/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 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-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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/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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 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.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 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/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/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/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/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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 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.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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/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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-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/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 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/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/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-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/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-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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 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/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 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/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= prometheus-nextcloud-exporter-0.6.0/internal/000077500000000000000000000000001435533501100213755ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/internal/client/000077500000000000000000000000001435533501100226535ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/internal/client/client.go000066400000000000000000000030221435533501100244550ustar00rootroot00000000000000package client import ( "crypto/tls" "errors" "fmt" "net/http" "time" "github.com/xperimental/nextcloud-exporter/serverinfo" ) const ( nextcloudTokenHeader = "NC-Token" ) var ( ErrNotAuthorized = errors.New("wrong credentials") ErrRatelimit = errors.New("too many requests") ) type InfoClient func() (*serverinfo.ServerInfo, error) func New(infoURL, username, password, authToken string, timeout time.Duration, userAgent string, tlsSkipVerify bool) InfoClient { client := &http.Client{ Timeout: timeout, Transport: &http.Transport{ TLSClientConfig: &tls.Config{ // disable TLS certification verification, if desired InsecureSkipVerify: tlsSkipVerify, }, }, } return func() (*serverinfo.ServerInfo, error) { req, err := http.NewRequest(http.MethodGet, infoURL, nil) if err != nil { return nil, err } if authToken == "" { req.SetBasicAuth(username, password) } else { req.Header.Set(nextcloudTokenHeader, authToken) } req.Header.Set("User-Agent", userAgent) res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() switch res.StatusCode { case http.StatusOK: break case http.StatusUnauthorized: return nil, ErrNotAuthorized case http.StatusTooManyRequests: return nil, ErrRatelimit default: return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode) } status, err := serverinfo.ParseJSON(res.Body) if err != nil { return nil, fmt.Errorf("can not parse server info: %w", err) } return status, nil } } prometheus-nextcloud-exporter-0.6.0/internal/client/client_test.go000066400000000000000000000074311435533501100255240ustar00rootroot00000000000000package client import ( "errors" "fmt" "net/http" "net/http/httptest" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/xperimental/nextcloud-exporter/internal/testutil" "github.com/xperimental/nextcloud-exporter/serverinfo" ) func TestClient(t *testing.T) { wantUserAgent := "test-ua" wantUsername := "test-user" wantPassword := "test-password" wantToken := "test-token" tt := []struct { desc string password string token string handler func(t *testing.T) http.Handler wantInfo *serverinfo.ServerInfo wantErr error }{ { desc: "password", password: wantPassword, token: "", handler: func(t *testing.T) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { user, password, ok := req.BasicAuth() if !ok { t.Error("failed to get authentication header") } if user != wantUsername { t.Errorf("got username %q, want %q", user, wantUsername) } if password != wantPassword { t.Errorf("got password %q, want %q", password, wantPassword) } fmt.Fprintln(w, "{}") }) }, wantInfo: &serverinfo.ServerInfo{}, wantErr: nil, }, { desc: "token", password: "", token: wantToken, handler: func(t *testing.T) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { token := req.Header.Get(nextcloudTokenHeader) if token != wantToken { t.Errorf("got token %q, want %q", token, wantToken) } fmt.Fprintln(w, "{}") }) }, wantInfo: &serverinfo.ServerInfo{}, wantErr: nil, }, { desc: "user-agent", token: wantToken, handler: func(t *testing.T) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ua := req.UserAgent() if ua != wantUserAgent { t.Errorf("got user-agent %q, want %q", ua, wantUserAgent) } fmt.Fprintln(w, "{}") }) }, wantInfo: &serverinfo.ServerInfo{}, wantErr: nil, }, { desc: "auth error", password: "", token: "", handler: func(t *testing.T) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusUnauthorized) }) }, wantInfo: nil, wantErr: ErrNotAuthorized, }, { desc: "simple info", token: wantToken, handler: func(t *testing.T) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { fmt.Fprintln(w, `{"ocs": {"meta": {"status": "OK", "statuscode": 200}}}`) }) }, wantInfo: &serverinfo.ServerInfo{ Meta: serverinfo.Meta{ Status: "OK", StatusCode: http.StatusOK, }, }, wantErr: nil, }, { desc: "parse error", password: "", token: "", handler: func(t *testing.T) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) }) }, wantInfo: nil, wantErr: errors.New("can not parse server info: EOF"), }, { desc: "ratelimit", handler: func(t *testing.T) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusTooManyRequests) }) }, wantErr: ErrRatelimit, }, } for _, tc := range tt { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() s := httptest.NewServer(tc.handler(t)) defer s.Close() client := New(s.URL, wantUsername, tc.password, tc.token, time.Second, wantUserAgent, false) info, err := client() if !testutil.EqualErrorMessage(err, tc.wantErr) { t.Errorf("got error %q, want %q", err, tc.wantErr) } if err != nil { return } if diff := cmp.Diff(info, tc.wantInfo); diff != "" { t.Errorf("info differs: -got+want\n%s", diff) } }) } } prometheus-nextcloud-exporter-0.6.0/internal/config/000077500000000000000000000000001435533501100226425ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/internal/config/config.go000066400000000000000000000161361435533501100244450ustar00rootroot00000000000000package config import ( "errors" "fmt" "os" "strconv" "strings" "time" "github.com/spf13/pflag" "gopkg.in/yaml.v3" ) const ( envPrefix = "NEXTCLOUD_" envListenAddress = envPrefix + "LISTEN_ADDRESS" envTimeout = envPrefix + "TIMEOUT" envServerURL = envPrefix + "SERVER" envUsername = envPrefix + "USERNAME" envPassword = envPrefix + "PASSWORD" envAuthToken = envPrefix + "AUTH_TOKEN" envTLSSkipVerify = envPrefix + "TLS_SKIP_VERIFY" ) // RunMode signals what the main application should do after parsing the options. type RunMode int const ( // RunModeExporter is the normal operation as an exporter serving metrics via HTTP. RunModeExporter RunMode = iota // RunModeHelp shows information about available options. RunModeHelp // RunModeLogin is used to interactively login to a Nextcloud instance. RunModeLogin // RunModeVersion shows version information. RunModeVersion ) func (m RunMode) String() string { switch m { case RunModeExporter: return "exporter" case RunModeHelp: return "help" case RunModeLogin: return "login" case RunModeVersion: return "version" default: return "error" } } // 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"` AuthToken string `yaml:"authToken"` TLSSkipVerify bool `yaml:"tlsSkipVerify"` RunMode RunMode } var ( errValidateNoServerURL = errors.New("need to set a server URL") errValidateNoAuth = errors.New("need to either set username/password or a token") errValidateNoUsername = errors.New("need to provide a username") errValidateNoPassword = errors.New("need to provide a password") ) // Validate checks if the configuration contains all necessary parameters. func (c Config) Validate() error { if len(c.ServerURL) == 0 { return errValidateNoServerURL } if len(c.AuthToken) == 0 { if len(c.Username) == 0 && len(c.Password) == 0 { return errValidateNoAuth } if len(c.Username) == 0 { return errValidateNoUsername } if len(c.Password) == 0 { return errValidateNoPassword } } 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: %w", err) } if configFile != "" { rawFile, err := loadConfigFromFile(configFile) if err != nil { return Config{}, fmt.Errorf("error reading configuration file: %w", err) } result = mergeConfig(result, rawFile) } env, err := loadConfigFromEnv(envFunc) if err != nil { return Config{}, fmt.Errorf("error reading environment variables: %w", 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: %w", err) } result.Password = password } if strings.HasPrefix(result.AuthToken, "@") { fileName := strings.TrimPrefix(result.AuthToken, "@") authToken, err := readPasswordFile(fileName) if err != nil { return Config{}, fmt.Errorf("can not read token file: %w", err) } result.AuthToken = authToken } 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.StringVar(&result.AuthToken, "auth-token", defaults.AuthToken, "Authentication token. Can replace username and password when using Nextcloud 22 or newer.") flags.BoolVar(&result.TLSSkipVerify, "tls-skip-verify", defaults.TLSSkipVerify, "Skip certificate verification of Nextcloud server.") modeLogin := flags.Bool("login", false, "Use interactive login to create app password.") modeVersion := flags.BoolP("version", "V", false, "Show version information and exit.") if err := flags.Parse(args[1:]); err != nil { if err == pflag.ErrHelp { return Config{ RunMode: RunModeHelp, }, "", nil } return Config{}, "", err } if *modeVersion { return Config{ RunMode: RunModeVersion, }, "", nil } if *modeLogin { result.RunMode = RunModeLogin } 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) { tlsSkipVerify := false if rawValue := getEnv(envTLSSkipVerify); rawValue != "" { value, err := strconv.ParseBool(rawValue) if err != nil { return Config{}, fmt.Errorf("can not parse value for %q: %s", envTLSSkipVerify, rawValue) } tlsSkipVerify = value } result := Config{ ListenAddr: getEnv(envListenAddress), ServerURL: getEnv(envServerURL), Username: getEnv(envUsername), Password: getEnv(envPassword), AuthToken: getEnv(envAuthToken), TLSSkipVerify: tlsSkipVerify, } 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.AuthToken != "" { result.AuthToken = override.AuthToken } if override.Timeout != 0 { result.Timeout = override.Timeout } if override.TLSSkipVerify { result.TLSSkipVerify = override.TLSSkipVerify } return result } func readPasswordFile(fileName string) (string, error) { bytes, err := os.ReadFile(fileName) if err != nil { return "", err } return strings.TrimSuffix(string(bytes), "\n"), nil } prometheus-nextcloud-exporter-0.6.0/internal/config/config_test.go000066400000000000000000000171361435533501100255050ustar00rootroot00000000000000package config import ( "errors" "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 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", TLSSkipVerify: false, }, }, { 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", TLSSkipVerify: false, }, }, { 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", TLSSkipVerify: false, }, }, { 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", TLSSkipVerify: false, }, }, { desc: "don't check tls certificates", args: []string{ "test", "--tls-skip-verify", "true", }, env: map[string]string{}, wantErr: nil, wantConfig: Config{ ListenAddr: ":9205", Timeout: 5 * time.Second, ServerURL: "", Username: "", Password: "", TLSSkipVerify: true, }, }, { 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", envTLSSkipVerify: "true", }, wantErr: nil, wantConfig: Config{ ListenAddr: "127.0.0.11:9205", Timeout: 15 * time.Second, ServerURL: "http://localhost", Username: "testuser", Password: "testpass", TLSSkipVerify: true, }, }, { 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", TLSSkipVerify: false, }, }, { desc: "token file", args: []string{ "test", "--config-file", "testdata/authtoken.yml", }, env: map[string]string{}, wantErr: nil, wantConfig: Config{ ListenAddr: defaults.ListenAddr, Timeout: defaults.Timeout, ServerURL: "http://localhost", Username: "", Password: "", AuthToken: "auth-token", TLSSkipVerify: false, }, }, { desc: "show help", args: []string{ "test", "--help", }, env: map[string]string{}, wantErr: nil, wantConfig: Config{ RunMode: RunModeHelp, }, }, { desc: "show version", args: []string{ "test", "--version", }, env: map[string]string{}, wantErr: nil, wantConfig: Config{ RunMode: RunModeVersion, }, }, { desc: "login mode", args: []string{ "test", "--login", "--server", "http://localhost", }, env: map[string]string{}, wantErr: nil, wantConfig: Config{ ListenAddr: defaults.ListenAddr, Timeout: defaults.Timeout, ServerURL: "http://localhost", RunMode: RunModeLogin, }, }, { 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"), }, { desc: "fail parsing tlsSkipVerify env", args: []string{ "test", }, env: map[string]string{ envTLSSkipVerify: "invalid", }, wantErr: errors.New(`error reading environment variables: can not parse value for "NEXTCLOUD_TLS_SKIP_VERIFY": invalid`), }, } 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 !testutil.EqualErrorMessage(err, tc.wantErr) { t.Errorf("got error %q, want %q", err, tc.wantErr) } 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: "auth token", config: Config{ ServerURL: "https://example.com", AuthToken: "auth-token", }, wantErr: nil, }, { desc: "no url", config: Config{ Username: "exporter", Password: "testpass", }, wantErr: errValidateNoServerURL, }, { desc: "auth help", config: Config{ ServerURL: "https://example.com", }, wantErr: errValidateNoAuth, }, { desc: "no username", config: Config{ ServerURL: "https://example.com", Password: "testpass", }, wantErr: errValidateNoUsername, }, { desc: "no password", config: Config{ ServerURL: "https://example.com", Username: "exporter", }, wantErr: errValidateNoPassword, }, } for _, tc := range tt { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() err := tc.config.Validate() if !testutil.EqualErrorMessage(err, tc.wantErr) { t.Errorf("got error %q, want %q", err, tc.wantErr) } }) } } prometheus-nextcloud-exporter-0.6.0/internal/config/testdata/000077500000000000000000000000001435533501100244535ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/internal/config/testdata/all.yml000066400000000000000000000001531435533501100257450ustar00rootroot00000000000000listenAddress: 127.0.0.10:9205 timeout: 10s server: http://localhost username: testuser password: testpass prometheus-nextcloud-exporter-0.6.0/internal/config/testdata/authtoken.yml000066400000000000000000000000571435533501100272020ustar00rootroot00000000000000server: http://localhost authToken: auth-token prometheus-nextcloud-exporter-0.6.0/internal/config/testdata/password000066400000000000000000000000111435533501100262300ustar00rootroot00000000000000testpass prometheus-nextcloud-exporter-0.6.0/internal/config/testdata/passwordfile.yml000066400000000000000000000001671435533501100277040ustar00rootroot00000000000000listenAddress: 127.0.0.10:9205 timeout: 10s server: http://localhost username: testuser password: "@testdata/password" prometheus-nextcloud-exporter-0.6.0/internal/login/000077500000000000000000000000001435533501100225055ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/internal/login/login.go000066400000000000000000000115231435533501100241460ustar00rootroot00000000000000package login import ( "crypto/tls" "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, tlsSkipVerify bool) *Client { return &Client{ log: log, userAgent: userAgent, serverURL: serverURL, client: &http.Client{ Timeout: 30 * time.Second, Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: tlsSkipVerify, }, }, }, 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: %w", 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: %w", 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: %w", 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: %w", 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: %w", 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: %w", 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: %w", 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: %w", 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: %w", 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: %w", 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: %w", err) } return Login{ Username: password.LoginName, Password: password.AppPassword, }, nil } } prometheus-nextcloud-exporter-0.6.0/internal/login/login_test.go000066400000000000000000000102251435533501100252030ustar00rootroot00000000000000package 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 !testutil.EqualErrorMessage(err, tc.wantErr) { t.Errorf("got error %q, want %q", err, tc.wantErr) } 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 !testutil.EqualErrorMessage(err, tc.wantErr) { t.Errorf("got error %q, want %q", err, tc.wantErr) } 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 !testutil.EqualErrorMessage(err, tc.wantErr) { t.Errorf("got error %q, want %q", err, tc.wantErr) } 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.6.0/internal/metrics/000077500000000000000000000000001435533501100230435ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/internal/metrics/collector.go000066400000000000000000000176671435533501100254010ustar00rootroot00000000000000package metrics import ( "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/sirupsen/logrus" "github.com/xperimental/nextcloud-exporter/internal/client" "github.com/xperimental/nextcloud-exporter/serverinfo" ) const ( metricPrefix = "nextcloud_" labelErrorCauseOther = "other" labelErrorCauseAuth = "auth" labelErrorCauseRatelimit = "ratelimit" ) 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", "Free disk space in data directory in bytes.", 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) hourlyActiveUsersDesc = prometheus.NewDesc( metricPrefix+"active_users_hourly_total", "Number of active users in the last hour.", nil, nil) dailyActiveUsersDesc = prometheus.NewDesc( metricPrefix+"active_users_daily_total", "Number of active users in the last 24 hours.", 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) databaseInfoDesc = prometheus.NewDesc( metricPrefix+"database_info", "Contains meta information about the database as labels. Value is always 1.", []string{"version", "type"}, nil) databaseSizeDesc = prometheus.NewDesc( metricPrefix+"database_size_bytes", "Size of database in bytes as reported from engine.", nil, nil) ) type nextcloudCollector struct { log logrus.FieldLogger infoClient client.InfoClient upMetric prometheus.Gauge scrapeErrorsMetric *prometheus.CounterVec } func RegisterCollector(log logrus.FieldLogger, infoClient client.InfoClient) error { c := &nextcloudCollector{ log: log, infoClient: infoClient, upMetric: prometheus.NewGauge(prometheus.GaugeOpts{ Name: metricPrefix + "up", Help: "Indicates if the metrics could be scraped by the exporter.", }), scrapeErrorsMetric: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: metricPrefix + "scrape_errors_total", Help: "Counts the number of scrape errors by this collector.", }, []string{"cause"}), } return prometheus.Register(c) } func (c *nextcloudCollector) Describe(ch chan<- *prometheus.Desc) { c.upMetric.Describe(ch) c.scrapeErrorsMetric.Describe(ch) ch <- usersDesc ch <- filesDesc ch <- freeSpaceDesc ch <- sharesDesc ch <- federationsDesc ch <- activeUsersDesc ch <- hourlyActiveUsersDesc ch <- dailyActiveUsersDesc } func (c *nextcloudCollector) Collect(ch chan<- prometheus.Metric) { if err := c.collectNextcloud(ch); err != nil { c.log.Errorf("Error during scrape: %s", err) cause := labelErrorCauseOther if err == client.ErrNotAuthorized { cause = labelErrorCauseAuth } else if err == client.ErrRatelimit { cause = labelErrorCauseRatelimit } c.scrapeErrorsMetric.WithLabelValues(cause).Inc() c.upMetric.Set(0) } else { c.upMetric.Set(1) } c.upMetric.Collect(ch) c.scrapeErrorsMetric.Collect(ch) } func (c *nextcloudCollector) collectNextcloud(ch chan<- prometheus.Metric) error { status, err := c.infoClient() if err != nil { return 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 } databaseInfo := []string{ status.Data.Server.Database.Version, status.Data.Server.Database.Type, } if err := collectInfoMetric(ch, databaseInfoDesc, databaseInfo); 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: hourlyActiveUsersDesc, value: float64(status.Data.ActiveUsers.LastHour), }, { desc: dailyActiveUsersDesc, value: float64(status.Data.ActiveUsers.LastDay), }, { 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: %w", 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) values["mail"] = float64(shares.SharesMail) values["room"] = float64(shares.SharesRoom) 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: %w", 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.6.0/internal/metrics/info.go000066400000000000000000000006711435533501100243310ustar00rootroot00000000000000package metrics import ( "github.com/prometheus/client_golang/prometheus" ) func RegisterInfoMetric(version, gitCommit string) error { 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) return prometheus.Register(infoMetric) } prometheus-nextcloud-exporter-0.6.0/internal/testutil/000077500000000000000000000000001435533501100232525ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/internal/testutil/testutil.go000066400000000000000000000004671435533501100254650ustar00rootroot00000000000000package testutil import ( "strings" ) // EqualErrorMessage compares two errors by just comparing their messages. func EqualErrorMessage(a, b error) bool { aMsg := "" if a != nil { aMsg = a.Error() } bMsg := "" if b != nil { bMsg = b.Error() } return strings.Compare(aMsg, bMsg) == 0 } prometheus-nextcloud-exporter-0.6.0/internal/testutil/testutil_test.go000066400000000000000000000023641435533501100265220ustar00rootroot00000000000000package testutil import ( "errors" "testing" ) type testError struct{} func (e testError) Error() string { return "test message" } func TestEqualErrorMessage(t *testing.T) { tt := []struct { desc string a error b error wantEqual bool }{ { desc: "two nil", a: nil, b: nil, wantEqual: true, }, { desc: "a not nil", a: errors.New("error A"), b: nil, wantEqual: false, }, { desc: "b not nil", a: nil, b: errors.New("error B"), wantEqual: false, }, { desc: "both not nil", a: errors.New("error A"), b: errors.New("error B"), wantEqual: false, }, { desc: "equal message, same type", a: errors.New("test message"), b: errors.New("test message"), wantEqual: true, }, { desc: "equal message, different type", a: errors.New("test message"), b: testError{}, wantEqual: true, }, } for _, tc := range tt { tc := tc t.Run(tc.desc, func(t *testing.T) { t.Parallel() equal := EqualErrorMessage(tc.a, tc.b) if equal != tc.wantEqual { t.Errorf("got equal %v, want %v", equal, tc.wantEqual) } }) } } prometheus-nextcloud-exporter-0.6.0/main.go000066400000000000000000000050421435533501100210350ustar00rootroot00000000000000package main import ( "fmt" "net/http" "os" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" "github.com/xperimental/nextcloud-exporter/internal/client" "github.com/xperimental/nextcloud-exporter/internal/config" "github.com/xperimental/nextcloud-exporter/internal/login" "github.com/xperimental/nextcloud-exporter/internal/metrics" "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() { cfg, err := config.Get() if err != nil { log.Fatalf("Error loading configuration: %s", err) } if cfg.RunMode == config.RunModeHelp { return } if cfg.RunMode == config.RunModeVersion { fmt.Println(Version) return } log.Infof("nextcloud-exporter %s", Version) userAgent := fmt.Sprintf("nextcloud-exporter/%s", Version) if cfg.RunMode == config.RunModeLogin { if cfg.ServerURL == "" { log.Fatalf("Need to specify --server for login.") } loginClient := login.Init(log, userAgent, cfg.ServerURL, cfg.TLSSkipVerify) 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) } if cfg.AuthToken == "" { log.Infof("Nextcloud server: %s User: %s", cfg.ServerURL, cfg.Username) } else { log.Infof("Nextcloud server: %s Authentication using token.", cfg.ServerURL) } infoURL := cfg.ServerURL + serverinfo.InfoPath if cfg.TLSSkipVerify { log.Warn("HTTPS certificate verification is disabled.") } infoClient := client.New(infoURL, cfg.Username, cfg.Password, cfg.AuthToken, cfg.Timeout, userAgent, cfg.TLSSkipVerify) if err := metrics.RegisterCollector(log, infoClient); err != nil { log.Fatalf("Failed to register collector: %s", err) } if err := metrics.RegisterInfoMetric(Version, GitCommit); 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.6.0/serverinfo/000077500000000000000000000000001435533501100217435ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/serverinfo/parse.go000066400000000000000000000005301435533501100234020ustar00rootroot00000000000000package serverinfo import ( "encoding/json" "io" ) // ParseJSON reads ServerInfo from a Reader in JSON format. func ParseJSON(r io.Reader) (*ServerInfo, error) { result := struct { ServerInfo ServerInfo `json:"ocs"` }{} if err := json.NewDecoder(r).Decode(&result); err != nil { return nil, err } return &result.ServerInfo, nil } prometheus-nextcloud-exporter-0.6.0/serverinfo/parse_test.go000066400000000000000000000010241435533501100244400ustar00rootroot00000000000000package serverinfo import ( "os" "testing" ) func TestParseJSON(t *testing.T) { inputFiles := []string{ "info.json", "negative-space.json", "na-values.json", "nc22.json", } 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 := ParseJSON(reader); err != nil { t.Errorf("got error %q", err) } }) } } prometheus-nextcloud-exporter-0.6.0/serverinfo/serverinfo.go000066400000000000000000000153331435533501100244610ustar00rootroot00000000000000package serverinfo import ( "encoding/json" "encoding/xml" "fmt" "strconv" ) const ( // InfoPath contains the path to the serverinfo endpoint. InfoPath = "/ocs/v2.php/apps/serverinfo/api/v1/info?format=json" ) // ServerInfo contains the complete data received from the server. type ServerInfo struct { Meta Meta `json:"meta"` Data Data `json:"data"` } // Meta contains meta information about the result. type Meta struct { Status string `json:"status"` StatusCode int `json:"statuscode"` Message string `json:"message"` } // Data contains the status information about the instance. type Data struct { Nextcloud Nextcloud `json:"nextcloud"` Server Server `json:"server"` ActiveUsers ActiveUsers `json:"activeUsers"` } // Nextcloud contains information about the nextcloud installation. type Nextcloud struct { System System `json:"system"` Storage Storage `json:"storage"` Shares Shares `json:"shares"` } // System contains nextcloud configuration and system information. type System struct { Version string `json:"version"` Theme string `json:"theme"` EnableAvatars bool `json:"enable_avatars"` EnablePreviews bool `json:"enable_previews"` MemcacheLocal string `json:"memcache.local"` MemcacheDistributed string `json:"memcache.distributed"` MemcacheLocking string `json:"memcache.locking"` FilelockingEnabled bool `json:"filelocking.enabled"` Debug bool `json:"debug"` FreeSpace int64 `json:"freespace"` Apps Apps `json:"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 } func (s *System) UnmarshalJSON(data []byte) error { var raw struct { Version string `json:"version"` Theme string `json:"theme"` EnableAvatars string `json:"enable_avatars"` EnablePreviews string `json:"enable_previews"` MemcacheLocal string `json:"memcache.local"` MemcacheDistributed string `json:"memcache.distributed"` MemcacheLocking string `json:"memcache.locking"` FilelockingEnabled string `json:"filelocking.enabled"` Debug string `json:"debug"` FreeSpace int64 `json:"freespace"` Apps Apps `json:"apps"` } if err := json.Unmarshal(data, &raw); 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 `json:"num_installed"` AvailableUpdates uint `json:"num_updates_available"` } // Storage contains information about the nextcloud storage system. type Storage struct { Users uint `json:"num_users"` Files uint `json:"num_files"` Storages uint `json:"num_storages"` StoragesLocal uint `json:"num_storages_local"` StoragesHome uint `json:"num_storages_home"` StoragesOther uint `json:"num_storages_other"` } // Shares contains information about nextcloud shares. type Shares struct { SharesTotal uint `json:"num_shares"` SharesUser uint `json:"num_shares_user"` SharesGroups uint `json:"num_shares_groups"` SharesLink uint `json:"num_shares_link"` SharesLinkNoPassword uint `json:"num_shares_link_no_password"` SharesMail uint `json:"num_shares_mail"` SharesRoom uint `json:"num_shares_room"` FedSent uint `json:"num_fed_shares_sent"` FedReceived uint `json:"num_fed_shares_received"` // 2 // 4 // 2 // 2 // 3 // 1 } // Server contains information about the servers running nextcloud. type Server struct { Webserver string `json:"webserver"` PHP PHP `json:"php"` Database Database `json:"database"` } // PHP contains information about the PHP installation. type PHP struct { Version string `json:"version"` MemoryLimit int64 `json:"memory_limit"` MaxExecutionTime uint `json:"max_execution_time"` UploadMaxFilesize int64 `json:"upload_max_filesize"` } // Database contains information about the database used by nextcloud. type Database struct { Type string `json:"type"` Version string `json:"version"` Size uint64 `json:"size"` } func (d *Database) UnmarshalJSON(data []byte) error { var raw struct { Type string `json:"type"` Version string `json:"version"` Size interface{} `json:"size"` } if err := json.Unmarshal(data, &raw); err != nil { return err } d.Type = raw.Type d.Version = raw.Version switch rawSize := raw.Size.(type) { case float64: if rawSize < 0 { return fmt.Errorf("negative value for database.size: %f", rawSize) } d.Size = uint64(rawSize) case string: parsedSize, err := strconv.ParseUint(rawSize, 10, 64) if err != nil { return fmt.Errorf("can not parse database.size %q: %w", rawSize, err) } d.Size = parsedSize default: return fmt.Errorf("unexpected type for database.size: %t", rawSize) } return nil } // ActiveUsers contains statistics about the active users. type ActiveUsers struct { Last5Minutes uint `json:"last5minutes"` LastHour uint `json:"last1hour"` LastDay uint `json:"last24hours"` } prometheus-nextcloud-exporter-0.6.0/serverinfo/testdata/000077500000000000000000000000001435533501100235545ustar00rootroot00000000000000prometheus-nextcloud-exporter-0.6.0/serverinfo/testdata/info.json000066400000000000000000000074161435533501100254120ustar00rootroot00000000000000{ "ocs": { "meta": { "status": "ok", "statuscode": 200, "message": "OK" }, "data": { "nextcloud": { "system": { "version": "21.0.3.1", "theme": "", "enable_avatars": "yes", "enable_previews": "yes", "memcache.local": "\\OC\\Memcache\\APCu", "memcache.distributed": "none", "filelocking.enabled": "yes", "memcache.locking": "\\OC\\Memcache\\Redis", "debug": "no", "freespace": 7635480576, "cpuload": [ 0.08, 0.05, 0.06 ], "mem_total": 1986232, "mem_free": 1285532, "swap_total": 0, "swap_free": 0, "apps": { "num_installed": 42, "num_updates_available": 0, "app_updates": [] } }, "storage": { "num_users": 4, "num_files": 148948, "num_storages": 32, "num_storages_local": 3, "num_storages_home": 4, "num_storages_other": 25 }, "shares": { "num_shares": 10, "num_shares_user": 0, "num_shares_groups": 2, "num_shares_link": 4, "num_shares_mail": 1, "num_shares_room": 0, "num_shares_link_no_password": 4, "num_fed_shares_sent": 0, "num_fed_shares_received": 0, "permissions_3_1": "2", "permissions_3_17": "1", "permissions_4_17": "1", "permissions_1_31": "2", "permissions_2_31": "3", "permissions_3_31": "1" } }, "server": { "webserver": "Apache\/2.4.41 (Ubuntu)", "php": { "version": "7.4.3", "memory_limit": 536870912, "max_execution_time": 3600, "upload_max_filesize": 2097152, "opcache": { "opcache_enabled": true, "cache_full": false, "restart_pending": false, "restart_in_progress": false, "memory_usage": { "used_memory": 35866872, "free_memory": 98334320, "wasted_memory": 16536, "current_wasted_percentage": 0.012320280075073242 }, "interned_strings_usage": { "buffer_size": 6291008, "used_memory": 4225688, "free_memory": 2065320, "number_of_strings": 68439 }, "opcache_statistics": { "num_cached_scripts": 1818, "num_cached_keys": 3489, "max_cached_keys": 16229, "hits": 2725757, "start_time": 1627817478, "last_restart_time": 0, "oom_restarts": 0, "hash_restarts": 0, "manual_restarts": 0, "misses": 1830, "blacklist_misses": 0, "blacklist_miss_ratio": 0, "opcache_hit_rate": 99.93290773126576 } }, "apcu": { "cache": { "num_slots": 4099, "ttl": 0, "num_hits": 175992, "num_misses": 1948, "num_inserts": 2009, "num_entries": 599, "expunges": 0, "start_time": 1627817478, "mem_size": 295024, "memory_type": "mmap" }, "sma": { "num_seg": 1, "seg_size": 33554312, "avail_mem": 33206176 } } }, "database": { "type": "mysql", "version": "10.5.11", "size": 59457536 } }, "activeUsers": { "last5minutes": 1, "last1hour": 1, "last24hours": 2 } } } } prometheus-nextcloud-exporter-0.6.0/serverinfo/testdata/na-values.json000066400000000000000000000074221435533501100263470ustar00rootroot00000000000000{ "ocs": { "meta": { "status": "ok", "statuscode": 200, "message": "OK" }, "data": { "nextcloud": { "system": { "version": "21.0.3.1", "theme": "", "enable_avatars": "yes", "enable_previews": "yes", "memcache.local": "\\OC\\Memcache\\APCu", "memcache.distributed": "none", "filelocking.enabled": "yes", "memcache.locking": "\\OC\\Memcache\\Redis", "debug": "no", "freespace": 7635480576, "cpuload": [ 0.08, 0.05, 0.06 ], "mem_total": "N/A", "mem_free": "N/A", "swap_total": "N/A", "swap_free": "N/A", "apps": { "num_installed": 42, "num_updates_available": 0, "app_updates": [] } }, "storage": { "num_users": 4, "num_files": 148948, "num_storages": 32, "num_storages_local": 3, "num_storages_home": 4, "num_storages_other": 25 }, "shares": { "num_shares": 10, "num_shares_user": 0, "num_shares_groups": 2, "num_shares_link": 4, "num_shares_mail": 1, "num_shares_room": 0, "num_shares_link_no_password": 4, "num_fed_shares_sent": 0, "num_fed_shares_received": 0, "permissions_3_1": "2", "permissions_3_17": "1", "permissions_4_17": "1", "permissions_1_31": "2", "permissions_2_31": "3", "permissions_3_31": "1" } }, "server": { "webserver": "Apache\/2.4.41 (Ubuntu)", "php": { "version": "7.4.3", "memory_limit": 536870912, "max_execution_time": 3600, "upload_max_filesize": 2097152, "opcache": { "opcache_enabled": true, "cache_full": false, "restart_pending": false, "restart_in_progress": false, "memory_usage": { "used_memory": 35866872, "free_memory": 98334320, "wasted_memory": 16536, "current_wasted_percentage": 0.012320280075073242 }, "interned_strings_usage": { "buffer_size": 6291008, "used_memory": 4225688, "free_memory": 2065320, "number_of_strings": 68439 }, "opcache_statistics": { "num_cached_scripts": 1818, "num_cached_keys": 3489, "max_cached_keys": 16229, "hits": 2725757, "start_time": 1627817478, "last_restart_time": 0, "oom_restarts": 0, "hash_restarts": 0, "manual_restarts": 0, "misses": 1830, "blacklist_misses": 0, "blacklist_miss_ratio": 0, "opcache_hit_rate": 99.93290773126576 } }, "apcu": { "cache": { "num_slots": 4099, "ttl": 0, "num_hits": 175992, "num_misses": 1948, "num_inserts": 2009, "num_entries": 599, "expunges": 0, "start_time": 1627817478, "mem_size": 295024, "memory_type": "mmap" }, "sma": { "num_seg": 1, "seg_size": 33554312, "avail_mem": 33206176 } } }, "database": { "type": "mysql", "version": "10.5.11", "size": 59457536 } }, "activeUsers": { "last5minutes": 1, "last1hour": 1, "last24hours": 2 } } } } prometheus-nextcloud-exporter-0.6.0/serverinfo/testdata/nc22.json000066400000000000000000000076021435533501100252200ustar00rootroot00000000000000{ "ocs": { "meta": { "status": "ok", "statuscode": 200, "message": "OK" }, "data": { "nextcloud": { "system": { "version": "22.2.0.2", "theme": "", "enable_avatars": "yes", "enable_previews": "yes", "memcache.local": "\\OC\\Memcache\\Redis", "memcache.distributed": "\\OC\\Memcache\\Redis", "filelocking.enabled": "yes", "memcache.locking": "\\OC\\Memcache\\Redis", "debug": "no", "freespace": 12975042, "cpuload": [ 0.8, 0.4, 0.3 ], "mem_total": 394078, "mem_free": 184536, "swap_total": 52428, "swap_free": 3960, "apps": { "num_installed": 4, "num_updates_available": 0, "app_updates": [] } }, "storage": { "num_users": 3, "num_files": 412, "num_storages": 6, "num_storages_local": 4, "num_storages_home": 3, "num_storages_other": 2 }, "shares": { "num_shares": 8, "num_shares_user": 4, "num_shares_groups": 2, "num_shares_link": 7, "num_shares_mail": 0, "num_shares_room": 0, "num_shares_link_no_password": 7, "num_fed_shares_sent": 1, "num_fed_shares_received": 2, "permissions_0_1": "3", "permissions_3_1": "43", "permissions_1_15": "1", "permissions_2_15": "1", "permissions_3_15": "3", "permissions_3_17": "27", "permissions_0_31": "1", "permissions_1_31": "1", "permissions_2_31": "5", "permissions_3_31": "2", "permissions_6_31": "1" } }, "server": { "webserver": "nginx\/1.14.0", "php": { "version": "7.4.0", "memory_limit": 26843545, "max_execution_time": 360, "upload_max_filesize": 1677721, "opcache": { "opcache_enabled": true, "cache_full": false, "restart_pending": false, "restart_in_progress": false, "memory_usage": { "used_memory": 3928244, "free_memory": 9490738, "wasted_memory": 2789, "current_wasted_percentage": 0.020 }, "interned_strings_usage": { "buffer_size": 629100, "used_memory": 489804, "free_memory": 139296, "number_of_strings": 7795 }, "opcache_statistics": { "num_cached_scripts": 209, "num_cached_keys": 399, "max_cached_keys": 1622, "hits": 391187, "start_time": 1634933931, "last_restart_time": 0, "oom_restarts": 0, "hash_restarts": 0, "manual_restarts": 0, "misses": 210, "blacklist_misses": 0, "blacklist_miss_ratio": 0, "opcache_hit_rate": 99.994 } }, "apcu": { "cache": { "num_slots": 409, "ttl": 0, "num_hits": 0, "num_misses": 0, "num_inserts": 0, "num_entries": 0, "expunges": 0, "start_time": 1634933931, "mem_size": 0, "memory_type": "mmap" }, "sma": { "num_seg": 1, "seg_size": 335543, "avail_mem": 335213 } } }, "database": { "type": "mysql", "version": "10.5.12", "size": "30638080" } }, "activeUsers": { "last5minutes": 3, "last1hour": 3, "last24hours": 3 } } } } prometheus-nextcloud-exporter-0.6.0/serverinfo/testdata/negative-space.json000066400000000000000000000074061435533501100273510ustar00rootroot00000000000000{ "ocs": { "meta": { "status": "ok", "statuscode": 200, "message": "OK" }, "data": { "nextcloud": { "system": { "version": "21.0.3.1", "theme": "", "enable_avatars": "yes", "enable_previews": "yes", "memcache.local": "\\OC\\Memcache\\APCu", "memcache.distributed": "none", "filelocking.enabled": "yes", "memcache.locking": "\\OC\\Memcache\\Redis", "debug": "no", "freespace": -2, "cpuload": [ 0.08, 0.05, 0.06 ], "mem_total": 1986232, "mem_free": 1285532, "swap_total": 0, "swap_free": 0, "apps": { "num_installed": 42, "num_updates_available": 0, "app_updates": [] } }, "storage": { "num_users": 4, "num_files": 148948, "num_storages": 32, "num_storages_local": 3, "num_storages_home": 4, "num_storages_other": 25 }, "shares": { "num_shares": 10, "num_shares_user": 0, "num_shares_groups": 2, "num_shares_link": 4, "num_shares_mail": 1, "num_shares_room": 0, "num_shares_link_no_password": 4, "num_fed_shares_sent": 0, "num_fed_shares_received": 0, "permissions_3_1": "2", "permissions_3_17": "1", "permissions_4_17": "1", "permissions_1_31": "2", "permissions_2_31": "3", "permissions_3_31": "1" } }, "server": { "webserver": "Apache\/2.4.41 (Ubuntu)", "php": { "version": "7.4.3", "memory_limit": 536870912, "max_execution_time": 3600, "upload_max_filesize": 2097152, "opcache": { "opcache_enabled": true, "cache_full": false, "restart_pending": false, "restart_in_progress": false, "memory_usage": { "used_memory": 35866872, "free_memory": 98334320, "wasted_memory": 16536, "current_wasted_percentage": 0.012320280075073242 }, "interned_strings_usage": { "buffer_size": 6291008, "used_memory": 4225688, "free_memory": 2065320, "number_of_strings": 68439 }, "opcache_statistics": { "num_cached_scripts": 1818, "num_cached_keys": 3489, "max_cached_keys": 16229, "hits": 2725757, "start_time": 1627817478, "last_restart_time": 0, "oom_restarts": 0, "hash_restarts": 0, "manual_restarts": 0, "misses": 1830, "blacklist_misses": 0, "blacklist_miss_ratio": 0, "opcache_hit_rate": 99.93290773126576 } }, "apcu": { "cache": { "num_slots": 4099, "ttl": 0, "num_hits": 175992, "num_misses": 1948, "num_inserts": 2009, "num_entries": 599, "expunges": 0, "start_time": 1627817478, "mem_size": 295024, "memory_type": "mmap" }, "sma": { "num_seg": 1, "seg_size": 33554312, "avail_mem": 33206176 } } }, "database": { "type": "mysql", "version": "10.5.11", "size": 59457536 } }, "activeUsers": { "last5minutes": 1, "last1hour": 1, "last24hours": 2 } } } }