├── .bingo ├── .gitignore ├── README.md ├── Variables.mk ├── calens.mod ├── calens.sum ├── go.mod ├── reflex.mod ├── reflex.sum ├── revive.mod ├── revive.sum ├── staticcheck.mod ├── staticcheck.sum └── variables.env ├── .codacy.yml ├── .dockerignore ├── .editorconfig ├── .github ├── issue_template.md ├── pull_request_template.md ├── renovate.json ├── settings.yml └── workflows │ ├── automerge.yml │ ├── binaries.yml │ ├── changes.yml │ ├── docker.yml │ ├── docs.yml │ ├── flake.yml │ ├── general.yml │ └── kustomize.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── DCO ├── LICENSE ├── Makefile ├── README.md ├── artifacthub-repo.yml ├── changelog ├── 0.1.0_2018-10-06 │ └── initial-release.md ├── 0.1.1_2018-12-19 │ ├── fix-typo.md │ └── pin-golang.md ├── 0.2.0_2019-03-11 │ ├── backup-enabled.md │ └── pricing-collector.md ├── 1.0.0_2019-03-12 │ └── vat-label.md ├── 1.1.0_2022-03-09 │ ├── drop-darwin-386.md │ ├── loadbalancer-collector.md │ ├── refactor-structure.md │ └── volumes-collector.md ├── 1.2.0_2022-05-11 │ ├── servermetrics-collector.md │ └── web-config.md ├── 1.2.1_2022-05-12 │ └── fix-goroutines.md ├── 1.2.2_2022-05-14 │ ├── fix-goroutines.md │ └── out-of-range.md ├── 1.2.3_2023-08-10 │ └── fix-lb-traffic.md ├── 1.3.0_2023-10-26 │ ├── file-secrets.md │ ├── pprof-profiler.md │ └── web-config.md ├── 2.0.0_2024-08-29 │ ├── pricing-errors.md │ ├── server-paging.md │ └── traffic-metrics.md ├── 2.1.0_2024-10-26 │ ├── logging-library.md │ └── pricing-changes.md ├── CHANGELOG.tmpl ├── README.md ├── TEMPLATE └── unreleased │ └── .keep ├── cmd └── hcloud_exporter │ └── main.go ├── deploy └── kubernetes │ ├── deployment.yml │ ├── kustomization.yml │ ├── service.yml │ └── servicemonitor.yml ├── docker ├── Dockerfile.linux.386 ├── Dockerfile.linux.amd64 ├── Dockerfile.linux.arm └── Dockerfile.linux.arm64 ├── docs ├── .gitignore ├── archetypes │ └── default.md ├── content │ ├── building.md │ ├── kubernetes.md │ ├── license.md │ └── usage.md ├── hugo.toml ├── layouts │ ├── index.html │ ├── partials │ │ └── style.html │ └── shortcodes │ │ └── partial.html ├── partials │ ├── envvars.md │ └── metrics.md └── static │ └── syntax.css ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── hack ├── generate-envvars-docs.go └── generate-metrics-docs.go ├── pkg ├── action │ ├── helper.go │ ├── metrics.go │ └── server.go ├── command │ ├── command.go │ ├── health.go │ ├── setup.go │ └── setup_test.go ├── config │ └── config.go ├── exporter │ ├── floating_ip.go │ ├── image.go │ ├── load_balancer.go │ ├── pricing.go │ ├── server.go │ ├── server_metrics.go │ ├── ssh_key.go │ └── volume.go ├── middleware │ ├── cache.go │ ├── profiler.go │ ├── realip.go │ ├── recoverer.go │ └── timeout.go └── version │ ├── collector.go │ └── version.go ├── reflex.conf └── revive.toml /.bingo/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore everything 3 | * 4 | 5 | # But not these files: 6 | !.gitignore 7 | !*.mod 8 | !*.sum 9 | !README.md 10 | !Variables.mk 11 | !variables.env 12 | 13 | *tmp.mod 14 | -------------------------------------------------------------------------------- /.bingo/README.md: -------------------------------------------------------------------------------- 1 | # Project Development Dependencies. 2 | 3 | This is directory which stores Go modules with pinned buildable package that is used within this repository, managed by https://github.com/bwplotka/bingo. 4 | 5 | * Run `bingo get` to install all tools having each own module file in this directory. 6 | * Run `bingo get ` to install that have own module file in this directory. 7 | * For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $() variable where is the .bingo/.mod. 8 | * For shell: Run `source .bingo/variables.env` to source all environment variable for each tool. 9 | * For go: Import `.bingo/variables.go` to for variable names. 10 | * See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies. 11 | 12 | ## Requirements 13 | 14 | * Go 1.14+ 15 | -------------------------------------------------------------------------------- /.bingo/Variables.mk: -------------------------------------------------------------------------------- 1 | # Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT. 2 | # All tools are designed to be build inside $GOBIN. 3 | BINGO_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 4 | GOPATH ?= $(shell go env GOPATH) 5 | GOBIN ?= $(firstword $(subst :, ,${GOPATH}))/bin 6 | GO ?= $(shell which go) 7 | 8 | # Below generated variables ensure that every time a tool under each variable is invoked, the correct version 9 | # will be used; reinstalling only if needed. 10 | # For example for calens variable: 11 | # 12 | # In your main Makefile (for non array binaries): 13 | # 14 | #include .bingo/Variables.mk # Assuming -dir was set to .bingo . 15 | # 16 | #command: $(CALENS) 17 | # @echo "Running calens" 18 | # @$(CALENS) 19 | # 20 | CALENS := $(GOBIN)/calens-v0.4.0 21 | $(CALENS): $(BINGO_DIR)/calens.mod 22 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 23 | @echo "(re)installing $(GOBIN)/calens-v0.4.0" 24 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=calens.mod -o=$(GOBIN)/calens-v0.4.0 "github.com/restic/calens" 25 | 26 | REFLEX := $(GOBIN)/reflex-v0.3.1 27 | $(REFLEX): $(BINGO_DIR)/reflex.mod 28 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 29 | @echo "(re)installing $(GOBIN)/reflex-v0.3.1" 30 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=reflex.mod -o=$(GOBIN)/reflex-v0.3.1 "github.com/cespare/reflex" 31 | 32 | REVIVE := $(GOBIN)/revive-v1.3.9 33 | $(REVIVE): $(BINGO_DIR)/revive.mod 34 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 35 | @echo "(re)installing $(GOBIN)/revive-v1.3.9" 36 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=revive.mod -o=$(GOBIN)/revive-v1.3.9 "github.com/mgechev/revive" 37 | 38 | STATICCHECK := $(GOBIN)/staticcheck-v0.5.1 39 | $(STATICCHECK): $(BINGO_DIR)/staticcheck.mod 40 | @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. 41 | @echo "(re)installing $(GOBIN)/staticcheck-v0.5.1" 42 | @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=staticcheck.mod -o=$(GOBIN)/staticcheck-v0.5.1 "honnef.co/go/tools/cmd/staticcheck" 43 | 44 | -------------------------------------------------------------------------------- /.bingo/calens.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.15 4 | 5 | require github.com/restic/calens v0.4.0 6 | -------------------------------------------------------------------------------- /.bingo/calens.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= 2 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 3 | github.com/Masterminds/semver/v3 v3.0.2 h1:tRi7ENs+AaOUCH+j6qwNQgPYfV26dX3JNonq+V4mhqc= 4 | github.com/Masterminds/semver/v3 v3.0.2/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 5 | github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= 6 | github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= 7 | github.com/Masterminds/sprig/v3 v3.0.1 h1:RuaOafp+8qOLUPX1lInLfUrLc1MEVbnz7a40RLoixKY= 8 | github.com/Masterminds/sprig/v3 v3.0.1/go.mod h1:Cp7HwZjmqKrC+Y7XqSJOU2yRvAJRGLiohfgz5ZJj8+4= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 12 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 13 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 14 | github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= 15 | github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= 16 | github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= 17 | github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 18 | github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= 19 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 20 | github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= 21 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 22 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 23 | github.com/restic/calens v0.2.0 h1:LVNAtmFc+Pb4ODX66qdX1T3Di1P0OTLyUsVyvM/xD7E= 24 | github.com/restic/calens v0.2.0/go.mod h1:UXwyAKS4wsgUZGEc7NrzzygJbLsQZIo3wl+62Q1wvmU= 25 | github.com/restic/calens v0.3.0 h1:GtkB4butZZQ+GyKYlIGN3SVKvOdaR8eZ/81wkitaUFI= 26 | github.com/restic/calens v0.3.0/go.mod h1:UXwyAKS4wsgUZGEc7NrzzygJbLsQZIo3wl+62Q1wvmU= 27 | github.com/restic/calens v0.4.0 h1:j0K2Lv1cnvD3Q2v/ULitLHqAOFKoCH2djBCmJ/Gzqvk= 28 | github.com/restic/calens v0.4.0/go.mod h1:ZBRZWv467s1l+a4O92N9vu7zQ37sHvBhfHRPOdPT4rg= 29 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 30 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 31 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 32 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 33 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 34 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 35 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 36 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 37 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU= 38 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 39 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 40 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 41 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 43 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 44 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 45 | -------------------------------------------------------------------------------- /.bingo/go.mod: -------------------------------------------------------------------------------- 1 | module _ // Fake go.mod auto-created by 'bingo' for go -moddir compatibility with non-Go projects. Commit this file, together with other .mod files. -------------------------------------------------------------------------------- /.bingo/reflex.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.15 4 | 5 | require github.com/cespare/reflex v0.3.1 6 | -------------------------------------------------------------------------------- /.bingo/reflex.sum: -------------------------------------------------------------------------------- 1 | github.com/cespare/reflex v0.3.1 h1:N4Y/UmRrjwOkNT0oQQnYsdr6YBxvHqtSfPB4mqOyAKk= 2 | github.com/cespare/reflex v0.3.1/go.mod h1:I+0Pnu2W693i7Hv6ZZG76qHTY0mgUa7uCIfCtikXojE= 3 | github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= 4 | github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 5 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 6 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 7 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 8 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 9 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 10 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 11 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 12 | github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750= 13 | github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g= 14 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= 15 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 16 | -------------------------------------------------------------------------------- /.bingo/revive.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.21 4 | 5 | toolchain go1.23.0 6 | 7 | require github.com/mgechev/revive v1.3.9 8 | -------------------------------------------------------------------------------- /.bingo/revive.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 2 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= 4 | github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 5 | github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= 6 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 7 | github.com/chavacava/garif v0.0.0-20221024190013-b3ef35877348 h1:cy5GCEZLUCshCGCRRUjxHrDUqkB4l5cuUt3ShEckQEo= 8 | github.com/chavacava/garif v0.0.0-20221024190013-b3ef35877348/go.mod h1:f/miWtG3SSuTxKsNK3o58H1xl+XV6ZIfbC6p7lPPB8U= 9 | github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= 10 | github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= 14 | github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= 15 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 16 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 17 | github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= 18 | github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= 19 | github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= 20 | github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= 21 | github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= 22 | github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 23 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 24 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 25 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 26 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 27 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 28 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 29 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 30 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 31 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 32 | github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= 33 | github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= 34 | github.com/mgechev/revive v1.2.5 h1:UF9AR8pOAuwNmhXj2odp4mxv9Nx2qUIwVz8ZsU+Mbec= 35 | github.com/mgechev/revive v1.2.5/go.mod h1:nFOXent79jMTISAfOAasKfy0Z2Ejq0WX7Qn/KAdYopI= 36 | github.com/mgechev/revive v1.3.4 h1:k/tO3XTaWY4DEHal9tWBkkUMJYO/dLDVyMmAQxmIMDc= 37 | github.com/mgechev/revive v1.3.4/go.mod h1:W+pZCMu9qj8Uhfs1iJMQsEFLRozUfvwFwqVvRbSNLVw= 38 | github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= 39 | github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= 40 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 41 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 42 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 43 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 44 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 45 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 46 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 47 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 48 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 49 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 50 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 51 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 52 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 53 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 54 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 55 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 56 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= 58 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 59 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 60 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 61 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 62 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 63 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 64 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 65 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 66 | golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= 67 | golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= 68 | golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= 69 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 70 | golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= 71 | golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= 72 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 73 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /.bingo/staticcheck.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.22.1 4 | 5 | toolchain go1.22.3 6 | 7 | require honnef.co/go/tools v0.5.1 // cmd/staticcheck 8 | -------------------------------------------------------------------------------- /.bingo/staticcheck.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= 2 | github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 4 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 5 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= 6 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 7 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 8 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 9 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 10 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 11 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 12 | golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 13 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 14 | golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= 15 | golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= 16 | golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM= 17 | golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 18 | golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= 19 | golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 20 | golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= 21 | golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 22 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= 23 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 24 | golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= 25 | golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 26 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 27 | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 28 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 29 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= 30 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 31 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 32 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 33 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 34 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 35 | golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= 36 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 37 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 38 | golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= 39 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 40 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 42 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 43 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 44 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 45 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 46 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 47 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 48 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 49 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 51 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= 52 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 53 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 54 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 55 | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= 56 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 59 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= 60 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 61 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 62 | golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= 63 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 64 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 65 | golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= 66 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 67 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 68 | golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 69 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 70 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 71 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 72 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 73 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 74 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 75 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 76 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 77 | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 78 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 79 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 80 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 81 | golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f h1:OKYpQQVE3DKSc3r3zHVzq46vq5YH7x8xpR3/k9ixmUg= 82 | golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= 83 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 84 | golang.org/x/tools v0.4.1-0.20221208213631-3f74d914ae6d h1:9ZNWAi4CYhNv60mXGgAncgq7SGc5qa7C8VZV8Tg7Ggs= 85 | golang.org/x/tools v0.4.1-0.20221208213631-3f74d914ae6d/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= 86 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 87 | golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5 h1:Vk4mysSz+GqQK2eqgWbo4zEO89wkeAjJiFIr9bpqa8k= 88 | golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 89 | golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3 h1:SHq4Rl+B7WvyM4XODon1LXtP7gcG49+7Jubt1gWWswY= 90 | golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3/go.mod h1:bqv7PJ/TtlrzgJKhOAGdDUkUltQapRik/UEHubLVBWo= 91 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 92 | honnef.co/go/tools v0.3.2 h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34= 93 | honnef.co/go/tools v0.3.2/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= 94 | honnef.co/go/tools v0.4.2 h1:6qXr+R5w+ktL5UkwEbPp+fEvfyoMPche6GkOpGHZcLc= 95 | honnef.co/go/tools v0.4.2/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= 96 | honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8= 97 | honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= 98 | honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= 99 | honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= 100 | -------------------------------------------------------------------------------- /.bingo/variables.env: -------------------------------------------------------------------------------- 1 | # Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT. 2 | # All tools are designed to be build inside $GOBIN. 3 | # Those variables will work only until 'bingo get' was invoked, or if tools were installed via Makefile's Variables.mk. 4 | GOBIN=${GOBIN:=$(go env GOBIN)} 5 | 6 | if [ -z "$GOBIN" ]; then 7 | GOBIN="$(go env GOPATH)/bin" 8 | fi 9 | 10 | 11 | CALENS="${GOBIN}/calens-v0.4.0" 12 | 13 | REFLEX="${GOBIN}/reflex-v0.3.1" 14 | 15 | REVIVE="${GOBIN}/revive-v1.3.9" 16 | 17 | STATICCHECK="${GOBIN}/staticcheck-v0.5.1" 18 | 19 | -------------------------------------------------------------------------------- /.codacy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | exclude_paths: 3 | - .github/** 4 | - .bingo/** 5 | - changelog/** 6 | - docs/** 7 | 8 | - CHANGELOG.md 9 | 10 | ... 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !bin/ 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [Makefile] 11 | indent_style = tab 12 | indent_size = 4 13 | 14 | [*.go] 15 | indent_style = tab 16 | indent_size = 4 17 | 18 | [*.md] 19 | trim_trailing_whitespace = true 20 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>promhippie/.github//renovate/preset" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | repository: 3 | name: hcloud_exporter 4 | description: Prometheus exporter for Hetzner Cloud 5 | homepage: https://promhippie.github.io/hcloud_exporter/ 6 | topics: prometheus, prometheus-exporter, metrics, hcloud 7 | 8 | private: false 9 | has_issues: true 10 | has_wiki: false 11 | has_downloads: false 12 | 13 | default_branch: master 14 | 15 | allow_squash_merge: true 16 | allow_merge_commit: true 17 | allow_rebase_merge: true 18 | 19 | allow_update_branch: true 20 | allow_auto_merge: true 21 | delete_branch_on_merge: true 22 | enable_automated_security_fixes: true 23 | enable_vulnerability_alerts: true 24 | 25 | branches: 26 | - name: master 27 | protection: 28 | required_pull_request_reviews: null 29 | required_status_checks: 30 | strict: true 31 | contexts: 32 | - testing 33 | enforce_admins: false 34 | restrictions: 35 | apps: 36 | - renovate 37 | - promhippie 38 | users: [] 39 | teams: 40 | - admins 41 | - bots 42 | - members 43 | 44 | teams: 45 | - name: admins 46 | permission: admin 47 | - name: bots 48 | permission: admin 49 | - name: members 50 | permission: maintain 51 | 52 | labels: 53 | - name: bug 54 | color: fc2929 55 | description: Something isn't working 56 | - name: duplicate 57 | color: cccccc 58 | description: This issue or pull request already exists 59 | - name: enhancement 60 | color: 84b6eb 61 | description: New feature or request 62 | - name: good first issue 63 | color: 7057ff 64 | description: Good for newcomers 65 | - name: help wanted 66 | color: 159818 67 | description: Extra attention is needed 68 | - name: invalid 69 | color: e6e6e6 70 | description: This doesn't seem right 71 | - name: question 72 | color: cc317c 73 | description: Further information is requested 74 | - name: renovate 75 | color: 1d76db 76 | description: Automated action from Renovate 77 | - name: wontfix 78 | color: 5319e7 79 | description: This will not be worked on 80 | - name: hacktoberfest 81 | color: d4c5f9 82 | description: Contribution at Hacktoberfest appreciated 83 | - name: ready 84 | color: ededed 85 | description: This is ready to be worked on 86 | - name: in progress 87 | color: ededed 88 | description: This is currently worked on 89 | - name: infra 90 | color: 006b75 91 | description: Related to the infrastructure 92 | - name: lint 93 | color: fbca04 94 | description: Related to linting tools 95 | - name: poc 96 | color: c2e0c6 97 | description: Proof of concept for new feature 98 | - name: rebase 99 | color: ffa8a5 100 | description: Branch requires a rebase 101 | - name: third-party 102 | color: e99695 103 | description: Depends on third-party tool or library 104 | - name: translation 105 | color: b60205 106 | description: Change or issue related to translations 107 | - name: ci 108 | color: b60105 109 | description: Related to Continous Integration 110 | - name: docs 111 | color: b60305 112 | description: Related to documentation 113 | - name: outdated 114 | color: cccccc 115 | description: This is out of scope and outdated 116 | 117 | ... 118 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: automerge 3 | 4 | "on": 5 | workflow_dispatch: 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | 14 | jobs: 15 | dependabot: 16 | runs-on: ubuntu-latest 17 | if: github.actor == 'dependabot[bot]' 18 | 19 | steps: 20 | - name: Generate token 21 | id: token 22 | uses: tibdex/github-app-token@v2 23 | with: 24 | app_id: ${{ secrets.TOKEN_EXCHANGE_APP }} 25 | installation_retrieval_mode: id 26 | installation_retrieval_payload: ${{ secrets.TOKEN_EXCHANGE_INSTALL }} 27 | private_key: ${{ secrets.TOKEN_EXCHANGE_KEY }} 28 | permissions: >- 29 | {"contents": "write", "pull_requests": "write", "issues": "write"} 30 | 31 | - name: Fetch metadata 32 | id: metadata 33 | uses: dependabot/fetch-metadata@v2 34 | with: 35 | github-token: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - name: Approve request 38 | id: approve 39 | run: gh pr review --approve "${{github.event.pull_request.html_url}}" 40 | env: 41 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | - name: Enable automerge 44 | id: automerge 45 | run: gh pr merge --rebase --auto "${{github.event.pull_request.html_url}}" 46 | env: 47 | GH_TOKEN: ${{ steps.token.outputs.token }} 48 | 49 | ... 50 | -------------------------------------------------------------------------------- /.github/workflows/binaries.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: binaries 3 | 4 | "on": 5 | workflow_dispatch: 6 | pull_request: 7 | branches: 8 | - master 9 | push: 10 | branches: 11 | - master 12 | tags: 13 | - v* 14 | 15 | permissions: 16 | contents: write 17 | 18 | jobs: 19 | binaries: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout source 24 | id: source 25 | uses: actions/checkout@v4 26 | 27 | - name: Setup golang 28 | id: golang 29 | uses: actions/setup-go@v5 30 | with: 31 | go-version: ^1.23.0 32 | 33 | - name: Run generate 34 | id: generate 35 | run: make generate 36 | 37 | - name: Run release 38 | id: release 39 | run: make release 40 | 41 | - name: Sign release 42 | id: gpgsign 43 | if: startsWith(github.ref, 'refs/tags/') 44 | uses: actionhippie/gpgsign@v1 45 | with: 46 | private_key: ${{ secrets.GNUPG_KEY }} 47 | passphrase: ${{ secrets.GNUPG_PASSWORD }} 48 | detach_sign: true 49 | files: dist/* 50 | excludes: dist/*.sha256 51 | 52 | - name: Build changes 53 | id: changelog 54 | if: startsWith(github.ref, 'refs/tags/') 55 | uses: actionhippie/calens@v1 56 | with: 57 | version: ${{ github.ref }} 58 | 59 | - name: Upload release 60 | id: upload 61 | if: startsWith(github.ref, 'refs/tags/') 62 | uses: ncipollo/release-action@v1 63 | with: 64 | body: ${{ steps.changelog.outputs.generated }} 65 | artifacts: dist/* 66 | 67 | ... 68 | -------------------------------------------------------------------------------- /.github/workflows/changes.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: changes 3 | 4 | "on": 5 | workflow_dispatch: 6 | pull_request: 7 | branches: 8 | - master 9 | push: 10 | branches: 11 | - master 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | changelog: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout source 22 | id: source 23 | if: github.event_name != 'pull_request' 24 | uses: actions/checkout@v4 25 | with: 26 | token: ${{ secrets.BOT_ACCESS_TOKEN }} 27 | 28 | - name: PR checkout 29 | id: altsource 30 | if: github.event_name == 'pull_request' 31 | uses: actions/checkout@v4 32 | 33 | - name: Setup golang 34 | id: golang 35 | uses: actions/setup-go@v5 36 | with: 37 | go-version: ^1.23.0 38 | 39 | - name: Run changelog 40 | id: changelog 41 | run: make changelog 42 | 43 | - name: Commit changes 44 | id: commit 45 | if: github.event_name != 'pull_request' 46 | uses: EndBug/add-and-commit@v9 47 | with: 48 | author_name: GitHub Actions 49 | author_email: github@webhippie.de 50 | add: CHANGELOG.md 51 | message: "docs: automated changelog update" 52 | push: true 53 | commit: --signoff 54 | 55 | envvars: 56 | runs-on: ubuntu-latest 57 | 58 | steps: 59 | - name: Checkout source 60 | id: source 61 | if: github.event_name != 'pull_request' 62 | uses: actions/checkout@v4 63 | with: 64 | token: ${{ secrets.BOT_ACCESS_TOKEN }} 65 | 66 | - name: PR checkout 67 | id: altsource 68 | if: github.event_name == 'pull_request' 69 | uses: actions/checkout@v4 70 | 71 | - name: Setup golang 72 | id: golang 73 | uses: actions/setup-go@v5 74 | with: 75 | go-version: ^1.23.0 76 | 77 | - name: Generate envvars 78 | id: envvars 79 | run: make envvars 80 | 81 | - name: Commit changes 82 | id: commit 83 | if: github.event_name != 'pull_request' 84 | uses: EndBug/add-and-commit@v9 85 | with: 86 | author_name: GitHub Actions 87 | author_email: github@webhippie.de 88 | add: docs/partials/envvars.md 89 | message: "docs: automated envvars update" 90 | push: true 91 | commit: --signoff 92 | 93 | metrics: 94 | runs-on: ubuntu-latest 95 | 96 | steps: 97 | - name: Checkout source 98 | id: source 99 | if: github.event_name != 'pull_request' 100 | uses: actions/checkout@v4 101 | with: 102 | token: ${{ secrets.BOT_ACCESS_TOKEN }} 103 | 104 | - name: PR checkout 105 | id: altsource 106 | if: github.event_name == 'pull_request' 107 | uses: actions/checkout@v4 108 | 109 | - name: Setup golang 110 | id: golang 111 | uses: actions/setup-go@v5 112 | with: 113 | go-version: ^1.23.0 114 | 115 | - name: Generate metrics 116 | id: metrics 117 | run: make metrics 118 | 119 | - name: Commit changes 120 | id: commit 121 | if: github.event_name != 'pull_request' 122 | uses: EndBug/add-and-commit@v9 123 | with: 124 | author_name: GitHub Actions 125 | author_email: github@webhippie.de 126 | add: docs/partials/metrics.md 127 | message: "docs: automated metrics update" 128 | push: true 129 | commit: --signoff 130 | 131 | ... 132 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: docker 3 | 4 | "on": 5 | workflow_dispatch: 6 | pull_request: 7 | branches: 8 | - master 9 | push: 10 | branches: 11 | - master 12 | tags: 13 | - v* 14 | 15 | permissions: 16 | contents: read 17 | packages: write 18 | 19 | jobs: 20 | docker: 21 | runs-on: ubuntu-latest 22 | 23 | strategy: 24 | matrix: 25 | include: 26 | - platform: linux/386 27 | goos: linux 28 | goarch: 386 29 | tags: netgo 30 | - platform: linux/amd64 31 | goos: linux 32 | goarch: amd64 33 | tags: netgo 34 | - platform: linux/arm/6 35 | goos: linux 36 | goarch: arm 37 | goarm: 6 38 | tags: netgo 39 | - platform: linux/arm64 40 | goos: linux 41 | goarch: arm64 42 | tags: netgo 43 | 44 | steps: 45 | - name: Checkout source 46 | id: source 47 | uses: actions/checkout@v4 48 | 49 | - name: Setup golang 50 | id: golang 51 | uses: actions/setup-go@v5 52 | with: 53 | go-version: ^1.23.0 54 | 55 | - name: Run generate 56 | id: generate 57 | env: 58 | GOOS: ${{ matrix.goos }} 59 | GOARCH: ${{ matrix.goarch }} 60 | GOARM: ${{ matrix.goarm }} 61 | TAGS: ${{ matrix.tags }} 62 | run: make generate 63 | 64 | - name: Run build 65 | id: build 66 | env: 67 | GOOS: ${{ matrix.goos }} 68 | GOARCH: ${{ matrix.goarch }} 69 | GOARM: ${{ matrix.goarm }} 70 | TAGS: ${{ matrix.tags }} 71 | run: make build 72 | 73 | - name: Docker meta 74 | id: meta 75 | uses: docker/metadata-action@v5 76 | with: 77 | images: | 78 | promhippie/hcloud-exporter 79 | quay.io/promhippie/hcloud-exporter 80 | ghcr.io/promhippie/hcloud-exporter 81 | labels: | 82 | io.artifacthub.package.readme-url=https://raw.githubusercontent.com/promhippie/hcloud_exporter/master/README.md 83 | org.opencontainers.image.vendor=Webhippie 84 | maintainer=Thomas Boerger 85 | tags: | 86 | type=ref,event=pr 87 | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} 88 | type=semver,pattern={{version}} 89 | type=semver,pattern={{major}}.{{minor}} 90 | type=semver,pattern={{major}} 91 | flavor: | 92 | suffix=-${{ matrix.goos }}-${{ matrix.goarch }} 93 | 94 | - name: Setup qemu 95 | id: qemu 96 | uses: docker/setup-qemu-action@v3 97 | 98 | - name: Setup buildx 99 | id: buildx 100 | uses: docker/setup-buildx-action@v3 101 | 102 | - name: Hub login 103 | id: login1 104 | uses: docker/login-action@v3 105 | if: github.event_name != 'pull_request' 106 | with: 107 | username: ${{ secrets.DOCKER_USERNAME }} 108 | password: ${{ secrets.DOCKER_PASSWORD }} 109 | 110 | - name: Quay login 111 | id: login2 112 | uses: docker/login-action@v3 113 | if: github.event_name != 'pull_request' 114 | with: 115 | registry: quay.io 116 | username: ${{ secrets.QUAY_USERNAME }} 117 | password: ${{ secrets.QUAY_PASSWORD }} 118 | 119 | - name: Ghcr login 120 | id: login3 121 | uses: docker/login-action@v3 122 | if: github.event_name != 'pull_request' 123 | with: 124 | registry: ghcr.io 125 | username: bothippie 126 | password: ${{ secrets.GITHUB_TOKEN }} 127 | 128 | - name: Build image 129 | id: publish 130 | uses: docker/build-push-action@v6 131 | with: 132 | builder: ${{ steps.buildx.outputs.name }} 133 | context: . 134 | provenance: false 135 | file: docker/Dockerfile.${{ matrix.goos }}.${{ matrix.goarch }} 136 | platforms: ${{ matrix.platform }} 137 | push: ${{ github.event_name != 'pull_request' }} 138 | labels: ${{ steps.meta.outputs.labels }} 139 | tags: ${{ steps.meta.outputs.tags }} 140 | 141 | manifest: 142 | runs-on: ubuntu-latest 143 | needs: docker 144 | if: github.event_name != 'pull_request' 145 | 146 | steps: 147 | - name: Checkout source 148 | id: source 149 | uses: actions/checkout@v4 150 | 151 | - name: Hub tags 152 | id: hubTags 153 | uses: docker/metadata-action@v5 154 | with: 155 | images: promhippie/hcloud-exporter 156 | tags: | 157 | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} 158 | type=semver,pattern={{version}} 159 | type=semver,pattern={{major}}.{{minor}} 160 | type=semver,pattern={{major}} 161 | 162 | - name: Hub manifest 163 | id: hub 164 | uses: actionhippie/manifest@v1 165 | with: 166 | username: ${{ secrets.DOCKER_USERNAME }} 167 | password: ${{ secrets.DOCKER_PASSWORD }} 168 | platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6 169 | template: promhippie/hcloud-exporter:VERSION-OS-ARCH 170 | target: ${{ steps.hubTags.outputs.tags }} 171 | ignore_missing: true 172 | 173 | - name: Quay tags 174 | id: quayTags 175 | uses: docker/metadata-action@v5 176 | with: 177 | images: quay.io/promhippie/hcloud-exporter 178 | tags: | 179 | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} 180 | type=semver,pattern={{version}} 181 | type=semver,pattern={{major}}.{{minor}} 182 | type=semver,pattern={{major}} 183 | 184 | - name: Quay manifest 185 | id: quay 186 | if: github.event_name != 'pull_request' 187 | uses: actionhippie/manifest@v1 188 | with: 189 | username: ${{ secrets.QUAY_USERNAME }} 190 | password: ${{ secrets.QUAY_PASSWORD }} 191 | platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6 192 | template: quay.io/promhippie/hcloud-exporter:VERSION-OS-ARCH 193 | target: ${{ steps.quayTags.outputs.tags }} 194 | ignore_missing: true 195 | 196 | - name: Ghcr tags 197 | id: ghcrTags 198 | if: github.event_name != 'pull_request' 199 | uses: docker/metadata-action@v5 200 | with: 201 | images: ghcr.io/promhippie/hcloud-exporter 202 | tags: | 203 | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} 204 | type=semver,pattern={{version}} 205 | type=semver,pattern={{major}}.{{minor}} 206 | type=semver,pattern={{major}} 207 | 208 | - name: Ghcr manifest 209 | id: ghcr 210 | if: github.event_name != 'pull_request' 211 | uses: actionhippie/manifest@v1 212 | with: 213 | username: bothippie 214 | password: ${{ secrets.GITHUB_TOKEN }} 215 | platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6 216 | template: ghcr.io/promhippie/hcloud-exporter:VERSION-OS-ARCH 217 | target: ${{ steps.ghcrTags.outputs.tags }} 218 | ignore_missing: true 219 | 220 | readme: 221 | runs-on: ubuntu-latest 222 | needs: docker 223 | if: github.event_name != 'pull_request' 224 | 225 | steps: 226 | - name: Checkout source 227 | id: source 228 | uses: actions/checkout@v4 229 | 230 | - name: Hub readme 231 | id: hub 232 | if: github.event_name != 'pull_request' 233 | uses: actionhippie/pushrm@v1 234 | with: 235 | provider: dockerhub 236 | target: promhippie/hcloud-exporter 237 | username: ${{ secrets.DOCKER_USERNAME }} 238 | password: ${{ secrets.DOCKER_PASSWORD }} 239 | description: Hetzner Cloud Exporter 240 | readme: README.md 241 | 242 | - name: Quay readme 243 | id: quay 244 | if: github.event_name != 'pull_request' 245 | uses: actionhippie/pushrm@v1 246 | with: 247 | provider: quay 248 | target: quay.io/promhippie/hcloud-exporter 249 | apikey: ${{ secrets.QUAY_APIKEY }} 250 | readme: README.md 251 | 252 | ... 253 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: docs 3 | 4 | "on": 5 | workflow_dispatch: 6 | pull_request: 7 | branches: 8 | - master 9 | push: 10 | branches: 11 | - master 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | docs: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout source 22 | id: source 23 | uses: actions/checkout@v4 24 | 25 | - name: Setup hugo 26 | id: hugo 27 | uses: peaceiris/actions-hugo@v3 28 | with: 29 | hugo-version: latest 30 | extended: true 31 | 32 | - name: Run docs 33 | id: docs 34 | run: make docs 35 | 36 | - name: Deploy pages 37 | id: deploy 38 | if: github.event_name != 'pull_request' 39 | uses: peaceiris/actions-gh-pages@v4 40 | with: 41 | github_token: ${{ secrets.GITHUB_TOKEN }} 42 | publish_dir: docs/public/ 43 | 44 | ... 45 | -------------------------------------------------------------------------------- /.github/workflows/flake.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: flake 3 | 4 | "on": 5 | workflow_dispatch: 6 | schedule: 7 | - cron: "0 8 * * 1" 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | flake: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout source 18 | id: source 19 | uses: actions/checkout@v4 20 | with: 21 | token: ${{ secrets.BOT_ACCESS_TOKEN }} 22 | 23 | - name: Install nix 24 | id: nix 25 | uses: cachix/install-nix-action@v31 26 | 27 | - name: Update flake 28 | id: flake 29 | run: nix flake update 30 | 31 | - name: Source rebase 32 | id: rebase 33 | run: git pull --autostash --rebase 34 | 35 | - name: Commit changes 36 | uses: EndBug/add-and-commit@v9 37 | with: 38 | author_name: GitHub Actions 39 | author_email: github@webhippie.de 40 | add: flake.lock 41 | message: "chore(flake): updated lockfile [skip ci]" 42 | push: true 43 | commit: --signoff 44 | 45 | ... 46 | -------------------------------------------------------------------------------- /.github/workflows/general.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: general 3 | 4 | "on": 5 | workflow_dispatch: 6 | pull_request: 7 | branches: 8 | - master 9 | push: 10 | branches: 11 | - master 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | testing: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout source 22 | id: source 23 | uses: actions/checkout@v4 24 | 25 | - name: Setup golang 26 | id: golang 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version: ^1.23.0 30 | 31 | - name: Run generate 32 | id: generate 33 | run: make generate 34 | 35 | - name: Run vet 36 | id: vet 37 | run: make vet 38 | 39 | - name: Run staticcheck 40 | id: staticcheck 41 | run: make staticcheck 42 | 43 | - name: Run lint 44 | id: lint 45 | run: make lint 46 | 47 | - name: Run build 48 | id: build 49 | run: make build 50 | 51 | - name: Run test 52 | id: test 53 | run: make test 54 | 55 | - name: Coverage report 56 | id: codacy 57 | if: github.event_name != 'pull_request' 58 | uses: codacy/codacy-coverage-reporter-action@v1 59 | with: 60 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 61 | coverage-reports: coverage.out 62 | force-coverage-parser: go 63 | 64 | ... 65 | -------------------------------------------------------------------------------- /.github/workflows/kustomize.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: kustomize 3 | 4 | "on": 5 | workflow_dispatch: 6 | pull_request: 7 | branches: 8 | - master 9 | push: 10 | branches: 11 | - master 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | generate: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout source 22 | id: source 23 | uses: actions/checkout@v4 24 | 25 | - name: Generate manifest 26 | id: kustomize 27 | uses: actionhippie/kustomize@v2 28 | with: 29 | version: 5.2.1 30 | path: deploy/kubernetes/ 31 | target: deploy/kubernetes/bundle.yml 32 | 33 | ... 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | .devenv 3 | coverage.out 4 | 5 | /bin 6 | /dist 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for 2.1.0 2 | 3 | The following sections list the changes for 2.1.0. 4 | 5 | ## Summary 6 | 7 | * Chg #258: Switch to official logging library 8 | * Chg #272: Add type to IP pricing and add metrics for primary IPs 9 | 10 | ## Details 11 | 12 | * Change #258: Switch to official logging library 13 | 14 | Since there have been a structured logger part of the Go standard library we 15 | thought it's time to replace the library with that. Be aware that log messages 16 | should change a little bit. 17 | 18 | https://github.com/promhippie/hcloud_exporter/issues/258 19 | 20 | * Change #272: Add type to IP pricing and add metrics for primary IPs 21 | 22 | Since the client SDK has deprecated the previous handling for the pricing of IP 23 | addresses we had to update the metrics to include the type and location of the 24 | IPs. Besides that we have also added metrics for the pricing of the primary IP 25 | addresses. 26 | 27 | https://github.com/promhippie/hcloud_exporter/pull/272 28 | 29 | 30 | # Changelog for 2.0.0 31 | 32 | The following sections list the changes for 2.0.0. 33 | 34 | ## Summary 35 | 36 | * Fix #246: Fetch metrics for all servers 37 | * Chg #240: Improve pricing error handling 38 | * Chg #240: New traffic pricing metrics because of deprecation 39 | 40 | ## Details 41 | 42 | * Bugfix #246: Fetch metrics for all servers 43 | 44 | For previous versions we have used the wrong client function to gather the list 45 | of servers for the server metrics, this hvae been fixed by using a function that 46 | automatically fetches all servers by iterating of the pagination. 47 | 48 | https://github.com/promhippie/hcloud_exporter/issues/246 49 | 50 | * Change #240: Improve pricing error handling 51 | 52 | So far we always existed the scraping if there have been any kind of error while 53 | parsing the metric values, from now on we are logging an error but continue to 54 | provide the remaining metrics to avoid loosing unrelated metrics. 55 | 56 | https://github.com/promhippie/hcloud_exporter/pull/240 57 | 58 | * Change #240: New traffic pricing metrics because of deprecation 59 | 60 | The previous traffic pricing metrics have been deprecated and got to be replaced 61 | by new metrics as the new metrics have been split between service type like load 62 | balancers and server types. 63 | 64 | https://github.com/promhippie/hcloud_exporter/issues/248 65 | https://github.com/promhippie/hcloud_exporter/pull/240 66 | 67 | 68 | # Changelog for 1.3.0 69 | 70 | The following sections list the changes for 1.3.0. 71 | 72 | ## Summary 73 | 74 | * Chg #193: Read secrets form files 75 | * Chg #193: Integrate standard web config 76 | * Enh #193: Integrate option pprof profiling 77 | 78 | ## Details 79 | 80 | * Change #193: Read secrets form files 81 | 82 | We have added proper support to load secrets like the password from files or 83 | from base64-encoded strings. Just provide the flags or environment variables for 84 | token or private key with a DSN formatted string like `file://path/to/file` or 85 | `base64://Zm9vYmFy`. 86 | 87 | https://github.com/promhippie/hcloud_exporter/pull/193 88 | 89 | * Change #193: Integrate standard web config 90 | 91 | We integrated the new web config from the Prometheus toolkit which provides a 92 | configuration for TLS support and also some basic builtin authentication. For 93 | the detailed configuration you can check out the documentation. 94 | 95 | https://github.com/promhippie/hcloud_exporter/pull/193 96 | 97 | * Enhancement #193: Integrate option pprof profiling 98 | 99 | We have added an option to enable a pprof endpoint for proper profiling support 100 | with the help of tools like Parca. The endpoint `/debug/pprof` can now 101 | optionally be enabled to get the profiling details for catching potential memory 102 | leaks. 103 | 104 | https://github.com/promhippie/hcloud_exporter/pull/193 105 | 106 | 107 | # Changelog for 1.2.3 108 | 109 | The following sections list the changes for 1.2.3. 110 | 111 | ## Summary 112 | 113 | * Fix #175: Correctly read loadbalancer traffic 114 | 115 | ## Details 116 | 117 | * Bugfix #175: Correctly read loadbalancer traffic 118 | 119 | We used a wrong attribute to read the loadbalancer traffic which resulted in 120 | missing metrics for the realtime traffic in and out for all loadbalancers. With 121 | this fix you should be able to use the metrics. 122 | 123 | https://github.com/promhippie/hcloud_exporter/issues/175 124 | 125 | 126 | # Changelog for 1.2.2 127 | 128 | The following sections list the changes for 1.2.2. 129 | 130 | ## Summary 131 | 132 | * Fix #72: Fix index out of range issue within server metrics 133 | * Fix #74: Another fix for go routines within server metrics 134 | 135 | ## Details 136 | 137 | * Bugfix #72: Fix index out of range issue within server metrics 138 | 139 | The code has not checked if an index have been really available within the 140 | server metrics API response. With this fix it gets properly handled. 141 | 142 | https://github.com/promhippie/hcloud_exporter/issues/72 143 | 144 | * Bugfix #74: Another fix for go routines within server metrics 145 | 146 | We disabled the server metrics by default for now until the implementation is 147 | really stable to avoid any side effects. I have reintroduced routines, otherwise 148 | the scrapetime will be far too high. This time I used wait groups to get 149 | everything handled properly. 150 | 151 | https://github.com/promhippie/hcloud_exporter/issues/74 152 | 153 | 154 | # Changelog for 1.2.1 155 | 156 | The following sections list the changes for 1.2.1. 157 | 158 | ## Summary 159 | 160 | * Fix #70: Fix go routine errors within server metrics 161 | 162 | ## Details 163 | 164 | * Bugfix #70: Fix go routine errors within server metrics 165 | 166 | We fixed a go routines issue within the new server metrics. We just got rid of 167 | the routines to avoid any errors related to sending to closed channels. 168 | 169 | https://github.com/promhippie/hcloud_exporter/issues/70 170 | 171 | 172 | # Changelog for 1.2.0 173 | 174 | The following sections list the changes for 1.2.0. 175 | 176 | ## Summary 177 | 178 | * Chg #53: Integrate standard web config 179 | * Chg #67: Add collector for server metrics 180 | 181 | ## Details 182 | 183 | * Change #53: Integrate standard web config 184 | 185 | We integrated the new web config from the Prometheus toolkit which provides a 186 | configuration for TLS support and also some basic builtin authentication. For 187 | the detailed configuration you check out the documentation. 188 | 189 | https://github.com/promhippie/hcloud_exporter/issues/53 190 | 191 | * Change #67: Add collector for server metrics 192 | 193 | Hetzner Cloud collects basic metrics on the hypervisor-level for each server. We 194 | have added a new collector which scrapes the latest available metric point for 195 | each running server. It is enabled by default. 196 | 197 | https://github.com/promhippie/hcloud_exporter/pull/67 198 | 199 | 200 | # Changelog for 1.1.0 201 | 202 | The following sections list the changes for 1.1.0. 203 | 204 | ## Summary 205 | 206 | * Chg #21: Add collector for volumes 207 | * Chg #24: Refactor build tools and project structure 208 | * Chg #25: Drop darwin/386 release builds 209 | * Chg #39: Add collector for load balancers 210 | 211 | ## Details 212 | 213 | * Change #21: Add collector for volumes 214 | 215 | We have added a new optional collector, which is disabled by default, to gather 216 | metrics about the volumes part of the configured Hetzner Cloud project. 217 | 218 | https://github.com/promhippie/hcloud_exporter/issues/21 219 | 220 | * Change #24: Refactor build tools and project structure 221 | 222 | To have a unified project structure and build tooling we have integrated the 223 | same structure we already got within our GitHub exporter. 224 | 225 | https://github.com/promhippie/hcloud_exporter/issues/24 226 | 227 | * Change #25: Drop darwin/386 release builds 228 | 229 | We dropped the build of 386 builds on Darwin as this architecture is not 230 | supported by current Go versions anymore. 231 | 232 | https://github.com/promhippie/hcloud_exporter/issues/25 233 | 234 | * Change #39: Add collector for load balancers 235 | 236 | We have added a new optional collector, which is enabled by default, to gather 237 | metrics about all loadbalancers part of the configured Hetzner Cloud project. 238 | 239 | https://github.com/promhippie/hcloud_exporter/issues/39 240 | 241 | 242 | # Changelog for 1.0.0 243 | 244 | The following sections list the changes for 1.0.0. 245 | 246 | ## Summary 247 | 248 | * Chg #19: Add `vat` labels for net and gross values 249 | 250 | ## Details 251 | 252 | * Change #19: Add `vat` labels for net and gross values 253 | 254 | Added a new `vat` label for `gross` or `net` values to the `hcloud_server_price` 255 | metric. Depending on the setup this can be a breaking change and it may be 256 | necessary to adjust some dashboards and alerting rules. 257 | 258 | https://github.com/promhippie/hcloud_exporter/pull/19 259 | 260 | 261 | # Changelog for 0.2.0 262 | 263 | The following sections list the changes for 0.2.0. 264 | 265 | ## Summary 266 | 267 | * Chg #17: Add pricing collector 268 | * Chg #18: Add new metric to see if backups enabled 269 | 270 | ## Details 271 | 272 | * Change #17: Add pricing collector 273 | 274 | We added a new collector to gather information about the pricings, that way 275 | somebody could do calculations how much the costs are increasing or decreasing 276 | by sclae up or sclae down. The new collector includes new metrics named 277 | `hcloud_pricing_floating_ip`, `hcloud_pricing_image`, 278 | `hcloud_pricing_server_backup` and `hcloud_pricing_traffic`. 279 | 280 | https://github.com/promhippie/hcloud_exporter/pull/17 281 | 282 | * Change #18: Add new metric to see if backups enabled 283 | 284 | We added a new metric named `hcloud_server_backup` which indicates if a server 285 | got backups enabled or not, that way somebody could add some alerting if a 286 | server is missing a backup. 287 | 288 | https://github.com/promhippie/hcloud_exporter/pull/18 289 | 290 | 291 | # Changelog for 0.1.1 292 | 293 | The following sections list the changes for 0.1.1. 294 | 295 | ## Summary 296 | 297 | * Fix #11: Fix typo within `hcloud_server_incoming_traffic_bytes` 298 | * Chg #13: Pin go version to 1.10 299 | 300 | ## Details 301 | 302 | * Bugfix #11: Fix typo within `hcloud_server_incoming_traffic_bytes` 303 | 304 | We fixed a typo within the `hcloud_server_incoming_traffic_bytes` metric where 305 | we were just missing a tiny single letter. 306 | 307 | https://github.com/promhippie/hcloud_exporter/pull/11 308 | 309 | * Change #13: Pin go version to 1.10 310 | 311 | To make sure we got something nearly like reproducible builds and to fix the 312 | builds we should pin the build dependencies like the Go version to make sure it 313 | is always buildable. 314 | 315 | https://github.com/promhippie/hcloud_exporter/pull/13 316 | 317 | 318 | # Changelog for 0.1.0 319 | 320 | The following sections list the changes for 0.1.0. 321 | 322 | ## Summary 323 | 324 | * Chg #23: Initial release of basic version 325 | 326 | ## Details 327 | 328 | * Change #23: Initial release of basic version 329 | 330 | Just prepared an initial basic version which could be released to the public. 331 | 332 | https://github.com/promhippie/hcloud_exporter/issues/23 333 | 334 | 335 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Promhippie 2 | 3 | Welcome! Our community focuses on helping others and making this project the 4 | best it can be. We gladly accept contributions and encourage you to get 5 | involved! 6 | 7 | ## Bug reports 8 | 9 | Please search the issues on the issue tracker with a variety of keywords to 10 | ensure your bug is not already reported. 11 | 12 | If unique, [open an issue][issues] and 13 | answer the questions so we can understand and reproduce the problematic 14 | behavior. 15 | 16 | The burden is on you to convince us that it is actually a bug in our project. 17 | This is easiest to do when you write clear, concise instructions so we can 18 | reproduce the behavior (even if it seems obvious). The more detailed and 19 | specific you are, the faster we will be able to help you. Check out 20 | [How to Report Bugs Effectively][bugreport]. 21 | 22 | Please be kind, remember that this project comes at no cost to you, and you're 23 | getting free help. 24 | 25 | ## Check for assigned people 26 | 27 | We are using Github Issues for submitting known issues, e.g. bugs, features, 28 | etc. Some issues will have someone assigned, meaning that there's already 29 | someone that takes responsibility for fixing said issue. This is not done to 30 | discourage contributions, rather to not step in the work that has already been 31 | done by the assignee. If you want to work on a known issue with someone already 32 | assigned to it, please consider contacting the assignee first, e.g. by 33 | mentioning the assignee in a new comment on the specific issue. This way you can 34 | contribute with ideas, or even with code if the assignee decides that you can 35 | step in. 36 | 37 | If you plan to work on a non assigned issue, please add a comment on the issue 38 | to prevent duplicated work. 39 | 40 | ## Minor improvements and new tests 41 | 42 | Submit pull requests at any time for minor changes or new tests. Make sure to 43 | write tests to assert your change is working properly and is thoroughly covered. 44 | We'll ask most pull requests to be squashed, especially with small commits. 45 | 46 | Your pull request may be thoroughly reviewed. This is because if we accept the 47 | PR, we also assume responsibility for it, although we would prefer you to help 48 | maintain your code after it gets merged. 49 | 50 | ## Mind the Style 51 | 52 | We believe that in order to have a healthy codebase we need to abide to a 53 | certain code style. We use `gofmt` with Go and `eslint` with Javascript for this 54 | matter, which are tools that has proved to be useful. So, before submitting your 55 | pull request, make sure that `gofmt` and if viable `eslint` are passing for you. 56 | 57 | Finally, note that `gofmt` and if viable `eslint` are called on the CI system. 58 | This means that your pull request will not be merged until the changes are 59 | approved. 60 | 61 | ## Update the Changelog 62 | 63 | We keep a changelog in the `CHANGELOG.md` file. This is useful to understand 64 | what has changed between each version. When you implement a new feature, or a 65 | fix for an issue, please also update the `CHANGELOG.md` file accordingly. We 66 | don't follow a strict style for the changelog, just try to be consistent with 67 | the rest of the file. 68 | 69 | ## Sign your work 70 | 71 | The sign-off is a simple line at the end of the explanation for the patch. Your 72 | signature certifies that you wrote the patch or otherwise have the right to pass 73 | it on as an open-source patch. The rules are pretty simple: If you can certify 74 | [DCO](./DCO), then you just add a line to every git commit message: 75 | 76 | ```console 77 | Signed-off-by: Joe Smith 78 | ``` 79 | 80 | Please use your real name, we really dislike pseudonyms or anonymous 81 | contributions. We are in the opensource world without secrets. If you set your 82 | `user.name` and `user.email` git configs, you can sign your commit automatically 83 | with `git commit -s`. 84 | 85 | ## Collaborator status 86 | 87 | If your pull request is merged, congratulations! You're technically a 88 | collaborator. We may also grant you "collaborator status" which means you can 89 | push to the repository and merge other pull requests. We hope that you will stay 90 | involved by reviewing pull requests, submitting more of your own, and resolving 91 | issues as you are able to. Thanks for making this project amazing! 92 | 93 | We ask that collaborators will conduct thorough code reviews and be nice to new 94 | contributors. Before merging a PR, it's best to get the approval of at least one 95 | or two other collaborators and/or the project owner. We prefer squashed commits 96 | instead of many little, semantically-unimportant commits. Also, CI and other 97 | post-commit hooks must pass before being merged except in certain unusual 98 | circumstances. 99 | 100 | Collaborator status may be removed for inactive users from time to time as we 101 | see fit; this is not an insult, just a basic security precaution in case the 102 | account becomes inactive or abandoned. Privileges can always be restored later. 103 | 104 | **Reviewing pull requests:** Please help submit and review pull requests as you 105 | are able! We would ask that every pull request be reviewed by at least one 106 | collaborator who did not open the pull request before merging. This will help 107 | ensure high code quality as new collaborators are added to the project. 108 | 109 | ## Vulnerabilities 110 | 111 | If you've found a vulnerability that is serious, please email to 112 | thomas@webhippie.de. If it's not a big deal, a pull request will probably be 113 | faster. 114 | 115 | ## Thank you 116 | 117 | Thanks for your help! This project would not be what it is today without your 118 | contributions. 119 | 120 | [issues]: https://github.com/promhippie/hcloud_exporter/issues 121 | [bugreport]: http://www.chiark.greenend.org.uk/~sgtatham/bugs.html 122 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 660 York Street, Suite 102, 6 | San Francisco, CA 94110 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | 12 | Developer's Certificate of Origin 1.1 13 | 14 | By making a contribution to this project, I certify that: 15 | 16 | (a) The contribution was created in whole or in part by me and I 17 | have the right to submit it under the open source license 18 | indicated in the file; or 19 | 20 | (b) The contribution is based upon previous work that, to the best 21 | of my knowledge, is covered under an appropriate open source 22 | license and I have the right under that license to submit that 23 | work with modifications, whether created in whole or in part 24 | by me, under the same open source license (unless I am 25 | permitted to submit under a different license), as indicated 26 | in the file; or 27 | 28 | (c) The contribution was provided directly to me by some other 29 | person who certified (a), (b) or (c) and I have not modified 30 | it. 31 | 32 | (d) I understand and agree that this project and the contribution 33 | are public and that a record of the contribution (including all 34 | personal information I submit with it, including my sign-off) is 35 | maintained indefinitely and may be redistributed consistent with 36 | this project or the open source license(s) involved. 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include .bingo/Variables.mk 2 | 3 | SHELL := bash 4 | NAME := hcloud_exporter 5 | IMPORT := github.com/promhippie/$(NAME) 6 | BIN := bin 7 | DIST := dist 8 | 9 | ifeq ($(OS), Windows_NT) 10 | EXECUTABLE := $(NAME).exe 11 | UNAME := Windows 12 | else 13 | EXECUTABLE := $(NAME) 14 | UNAME := $(shell uname -s) 15 | endif 16 | 17 | GOBUILD ?= CGO_ENABLED=0 go build 18 | PACKAGES ?= $(shell go list ./...) 19 | SOURCES ?= $(shell find . -name "*.go" -type f -not -path ./.devenv/\* -not -path ./.direnv/\*) 20 | GENERATE ?= $(PACKAGES) 21 | 22 | TAGS ?= netgo 23 | 24 | ifndef OUTPUT 25 | ifeq ($(GITHUB_REF_TYPE), tag) 26 | OUTPUT ?= $(subst v,,$(GITHUB_REF_NAME)) 27 | else 28 | OUTPUT ?= testing 29 | endif 30 | endif 31 | 32 | ifndef VERSION 33 | ifeq ($(GITHUB_REF_TYPE), tag) 34 | VERSION ?= $(subst v,,$(GITHUB_REF_NAME)) 35 | else 36 | VERSION ?= $(shell git rev-parse --short HEAD) 37 | endif 38 | endif 39 | 40 | ifndef DATE 41 | DATE := $(shell date -u '+%Y%m%d') 42 | endif 43 | 44 | ifndef SHA 45 | SHA := $(shell git rev-parse --short HEAD) 46 | endif 47 | 48 | LDFLAGS += -s -w -extldflags "-static" -X "$(IMPORT)/pkg/version.String=$(VERSION)" -X "$(IMPORT)/pkg/version.Revision=$(SHA)" -X "$(IMPORT)/pkg/version.Date=$(DATE)" 49 | GCFLAGS += all=-N -l 50 | 51 | .PHONY: all 52 | all: build 53 | 54 | .PHONY: sync 55 | sync: 56 | go mod download 57 | 58 | .PHONY: clean 59 | clean: 60 | go clean -i ./... 61 | rm -rf $(BIN) $(DIST) 62 | 63 | .PHONY: fmt 64 | fmt: 65 | gofmt -s -w $(SOURCES) 66 | 67 | .PHONY: vet 68 | vet: 69 | go vet $(PACKAGES) 70 | 71 | .PHONY: staticcheck 72 | staticcheck: $(STATICCHECK) 73 | $(STATICCHECK) -tags '$(TAGS)' $(PACKAGES) 74 | 75 | .PHONY: lint 76 | lint: $(REVIVE) 77 | for PKG in $(PACKAGES); do $(REVIVE) -config revive.toml -set_exit_status $$PKG || exit 1; done; 78 | 79 | .PHONY: generate 80 | generate: 81 | go generate $(GENERATE) 82 | 83 | .PHONY: changelog 84 | changelog: $(CALENS) 85 | $(CALENS) >| CHANGELOG.md 86 | 87 | .PHONY: test 88 | test: 89 | go test -coverprofile coverage.out $(PACKAGES) 90 | 91 | .PHONY: install 92 | install: $(SOURCES) 93 | go install -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/$(NAME) 94 | 95 | .PHONY: build 96 | build: $(BIN)/$(EXECUTABLE) 97 | 98 | $(BIN)/$(EXECUTABLE): $(SOURCES) 99 | $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 100 | 101 | $(BIN)/$(EXECUTABLE)-debug: $(SOURCES) 102 | $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -gcflags '$(GCFLAGS)' -o $@ ./cmd/$(NAME) 103 | 104 | .PHONY: release 105 | release: $(DIST) release-linux release-darwin release-windows release-reduce release-checksum 106 | 107 | $(DIST): 108 | mkdir -p $(DIST) 109 | 110 | .PHONY: release-linux 111 | release-linux: $(DIST) \ 112 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-386 \ 113 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-amd64 \ 114 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-arm-5 \ 115 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-arm-6 \ 116 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-arm-7 \ 117 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-arm64 \ 118 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-mips \ 119 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-mips64 \ 120 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-mipsle \ 121 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-mips64le 122 | 123 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-386: 124 | GOOS=linux GOARCH=386 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 125 | 126 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-amd64: 127 | GOOS=linux GOARCH=amd64 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 128 | 129 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-arm-5: 130 | GOOS=linux GOARCH=arm GOARM=5 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 131 | 132 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-arm-6: 133 | GOOS=linux GOARCH=arm GOARM=6 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 134 | 135 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-arm-7: 136 | GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 137 | 138 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-arm64: 139 | GOOS=linux GOARCH=arm64 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 140 | 141 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-mips: 142 | GOOS=linux GOARCH=mips $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 143 | 144 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-mips64: 145 | GOOS=linux GOARCH=mips64 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 146 | 147 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-mipsle: 148 | GOOS=linux GOARCH=mipsle $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 149 | 150 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-linux-mips64le: 151 | GOOS=linux GOARCH=mips64le $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 152 | 153 | .PHONY: release-darwin 154 | release-darwin: $(DIST) \ 155 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-darwin-amd64 \ 156 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-darwin-arm64 157 | 158 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-darwin-amd64: 159 | GOOS=darwin GOARCH=amd64 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 160 | 161 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-darwin-arm64: 162 | GOOS=darwin GOARCH=arm64 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 163 | 164 | .PHONY: release-windows 165 | release-windows: $(DIST) \ 166 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-windows-4.0-386.exe \ 167 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-windows-4.0-amd64.exe 168 | 169 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-windows-4.0-386.exe: 170 | GOOS=windows GOARCH=386 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 171 | 172 | $(DIST)/$(EXECUTABLE)-$(OUTPUT)-windows-4.0-amd64.exe: 173 | GOOS=windows GOARCH=amd64 $(GOBUILD) -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 174 | 175 | .PHONY: release-reduce 176 | release-reduce: 177 | cd $(DIST); $(foreach file,$(wildcard $(DIST)/$(EXECUTABLE)-*),upx $(notdir $(file));) 178 | 179 | .PHONY: release-checksum 180 | release-checksum: 181 | cd $(DIST); $(foreach file,$(wildcard $(DIST)/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;) 182 | 183 | .PHONY: release-finish 184 | release-finish: release-reduce release-checksum 185 | 186 | .PHONY: docs 187 | docs: 188 | hugo -s docs/ 189 | 190 | .PHONY: envvars 191 | envvars: 192 | go run hack/generate-envvars-docs.go 193 | 194 | .PHONY: metrics 195 | metrics: 196 | go run hack/generate-metrics-docs.go 197 | 198 | .PHONY: watch 199 | watch: $(REFLEX) 200 | $(REFLEX) -c reflex.conf 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hetzner Cloud Exporter 2 | 3 | [![Current Tag](https://img.shields.io/github/v/tag/promhippie/hcloud_exporter?sort=semver)](https://github.com/promhippie/prometheus-scw-sd) [![General Build](https://github.com/promhippie/hcloud_exporter/actions/workflows/general.yml/badge.svg)](https://github.com/promhippie/hcloud_exporter/actions/workflows/general.yml) [![Join the Matrix chat at https://matrix.to/#/#webhippie:matrix.org](https://img.shields.io/badge/matrix-%23webhippie-7bc9a4.svg)](https://matrix.to/#/#webhippie:matrix.org) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/0621f7fa70104074ad05ab9ac304d185)](https://www.codacy.com/gh/promhippie/hcloud_exporter/dashboard?utm_source=github.com&utm_medium=referral&utm_content=promhippie/hcloud_exporter&utm_campaign=Badge_Grade) [![Go Doc](https://godoc.org/github.com/promhippie/hcloud_exporter?status.svg)](http://godoc.org/github.com/promhippie/hcloud_exporter) [![Go Report](http://goreportcard.com/badge/github.com/promhippie/hcloud_exporter)](http://goreportcard.com/report/github.com/promhippie/hcloud_exporter) 4 | 5 | An exporter for [Prometheus][prometheus] that collects metrics from 6 | [Hetzner Cloud][hcloud]. 7 | 8 | ## Install 9 | 10 | You can download prebuilt binaries from our [GitHub releases][releases], or you 11 | can use our containers published on [Docker Hub][dockerhub] and [Quay][quayio]. 12 | If you need further guidance how to install this take a look at our 13 | [documentation][docs]. 14 | 15 | ## Development 16 | 17 | Make sure you have a working Go environment, for further reference or a guide 18 | take a look at the [install instructions][golang]. This project requires 19 | Go >= v1.17, at least that's the version we are using. 20 | 21 | ```console 22 | git clone https://github.com/promhippie/hcloud_exporter.git 23 | cd hcloud_exporter 24 | 25 | make generate build 26 | 27 | ./bin/hcloud_exporter -h 28 | ``` 29 | 30 | ## Security 31 | 32 | If you find a security issue please contact 33 | [thomas@webhippie.de](mailto:thomas@webhippie.de) first. 34 | 35 | ## Contributing 36 | 37 | Fork -> Patch -> Push -> Pull Request 38 | 39 | ## Authors 40 | 41 | - [Thomas Boerger](https://github.com/tboerger) 42 | 43 | ## License 44 | 45 | Apache-2.0 46 | 47 | ## Copyright 48 | 49 | ```console 50 | Copyright (c) 2018 Thomas Boerger 51 | ``` 52 | 53 | [prometheus]: https://prometheus.io 54 | [hcloud]: https://console.hetzner.cloud 55 | [releases]: https://github.com/promhippie/hcloud_exporter/releases 56 | [dockerhub]: https://hub.docker.com/r/promhippie/hcloud-exporter/tags/ 57 | [quayio]: https://quay.io/repository/promhippie/hcloud-exporter?tab=tags 58 | [docs]: https://promhippie.github.io/hcloud_exporter/#getting-started 59 | [golang]: http://golang.org/doc/install.html 60 | -------------------------------------------------------------------------------- /artifacthub-repo.yml: -------------------------------------------------------------------------------- 1 | --- 2 | repositoryID: ef97f6f8-12f8-4a0c-a860-336ed016aab7 3 | owners: 4 | - name: tboerger 5 | email: thomas@webhippie.de 6 | 7 | ... 8 | -------------------------------------------------------------------------------- /changelog/0.1.0_2018-10-06/initial-release.md: -------------------------------------------------------------------------------- 1 | Change: Initial release of basic version 2 | 3 | Just prepared an initial basic version which could be released to the public. 4 | 5 | https://github.com/promhippie/hcloud_exporter/issues/23 6 | -------------------------------------------------------------------------------- /changelog/0.1.1_2018-12-19/fix-typo.md: -------------------------------------------------------------------------------- 1 | Bugfix: Fix typo within `hcloud_server_incoming_traffic_bytes` 2 | 3 | We fixed a typo within the `hcloud_server_incoming_traffic_bytes` metric where 4 | we were just missing a tiny single letter. 5 | 6 | https://github.com/promhippie/hcloud_exporter/pull/11 7 | -------------------------------------------------------------------------------- /changelog/0.1.1_2018-12-19/pin-golang.md: -------------------------------------------------------------------------------- 1 | Change: Pin go version to 1.10 2 | 3 | To make sure we got something nearly like reproducible builds and to fix the 4 | builds we should pin the build dependencies like the Go version to make sure it 5 | is always buildable. 6 | 7 | https://github.com/promhippie/hcloud_exporter/pull/13 8 | -------------------------------------------------------------------------------- /changelog/0.2.0_2019-03-11/backup-enabled.md: -------------------------------------------------------------------------------- 1 | Change: Add new metric to see if backups enabled 2 | 3 | We added a new metric named `hcloud_server_backup` which indicates if a server 4 | got backups enabled or not, that way somebody could add some alerting if a 5 | server is missing a backup. 6 | 7 | https://github.com/promhippie/hcloud_exporter/pull/18 8 | -------------------------------------------------------------------------------- /changelog/0.2.0_2019-03-11/pricing-collector.md: -------------------------------------------------------------------------------- 1 | Change: Add pricing collector 2 | 3 | We added a new collector to gather information about the pricings, that way 4 | somebody could do calculations how much the costs are increasing or decreasing 5 | by sclae up or sclae down. The new collector includes new metrics named 6 | `hcloud_pricing_floating_ip`, `hcloud_pricing_image`, 7 | `hcloud_pricing_server_backup` and `hcloud_pricing_traffic`. 8 | 9 | https://github.com/promhippie/hcloud_exporter/pull/17 10 | -------------------------------------------------------------------------------- /changelog/1.0.0_2019-03-12/vat-label.md: -------------------------------------------------------------------------------- 1 | Change: Add `vat` labels for net and gross values 2 | 3 | Added a new `vat` label for `gross` or `net` values to the `hcloud_server_price` 4 | metric. Depending on the setup this can be a breaking change and it may be 5 | necessary to adjust some dashboards and alerting rules. 6 | 7 | https://github.com/promhippie/hcloud_exporter/pull/19 8 | -------------------------------------------------------------------------------- /changelog/1.1.0_2022-03-09/drop-darwin-386.md: -------------------------------------------------------------------------------- 1 | Change: Drop darwin/386 release builds 2 | 3 | We dropped the build of 386 builds on Darwin as this architecture is not 4 | supported by current Go versions anymore. 5 | 6 | https://github.com/promhippie/hcloud_exporter/issues/25 7 | -------------------------------------------------------------------------------- /changelog/1.1.0_2022-03-09/loadbalancer-collector.md: -------------------------------------------------------------------------------- 1 | Change: Add collector for load balancers 2 | 3 | We have added a new optional collector, which is enabled by default, to gather 4 | metrics about all loadbalancers part of the configured Hetzner Cloud project. 5 | 6 | https://github.com/promhippie/hcloud_exporter/issues/39 7 | -------------------------------------------------------------------------------- /changelog/1.1.0_2022-03-09/refactor-structure.md: -------------------------------------------------------------------------------- 1 | Change: Refactor build tools and project structure 2 | 3 | To have a unified project structure and build tooling we have integrated the 4 | same structure we already got within our GitHub exporter. 5 | 6 | https://github.com/promhippie/hcloud_exporter/issues/24 7 | -------------------------------------------------------------------------------- /changelog/1.1.0_2022-03-09/volumes-collector.md: -------------------------------------------------------------------------------- 1 | Change: Add collector for volumes 2 | 3 | We have added a new optional collector, which is disabled by default, to gather 4 | metrics about the volumes part of the configured Hetzner Cloud project. 5 | 6 | https://github.com/promhippie/hcloud_exporter/issues/21 7 | -------------------------------------------------------------------------------- /changelog/1.2.0_2022-05-11/servermetrics-collector.md: -------------------------------------------------------------------------------- 1 | Change: Add collector for server metrics 2 | 3 | Hetzner Cloud collects basic metrics on the hypervisor-level for each server. We 4 | have added a new collector which scrapes the latest available metric point for 5 | each running server. It is enabled by default. 6 | 7 | https://github.com/promhippie/hcloud_exporter/pull/67 8 | -------------------------------------------------------------------------------- /changelog/1.2.0_2022-05-11/web-config.md: -------------------------------------------------------------------------------- 1 | Change: Integrate standard web config 2 | 3 | We integrated the new web config from the Prometheus toolkit which provides a 4 | configuration for TLS support and also some basic builtin authentication. For 5 | the detailed configuration you check out the documentation. 6 | 7 | https://github.com/promhippie/hcloud_exporter/issues/53 8 | -------------------------------------------------------------------------------- /changelog/1.2.1_2022-05-12/fix-goroutines.md: -------------------------------------------------------------------------------- 1 | Bugfix: Fix go routine errors within server metrics 2 | 3 | We fixed a go routines issue within the new server metrics. We just got rid of 4 | the routines to avoid any errors related to sending to closed channels. 5 | 6 | https://github.com/promhippie/hcloud_exporter/issues/70 7 | -------------------------------------------------------------------------------- /changelog/1.2.2_2022-05-14/fix-goroutines.md: -------------------------------------------------------------------------------- 1 | Bugfix: Another fix for go routines within server metrics 2 | 3 | We disabled the server metrics by default for now until the implementation is 4 | really stable to avoid any side effects. I have reintroduced routines, otherwise 5 | the scrapetime will be far too high. This time I used wait groups to get 6 | everything handled properly. 7 | 8 | https://github.com/promhippie/hcloud_exporter/issues/74 9 | -------------------------------------------------------------------------------- /changelog/1.2.2_2022-05-14/out-of-range.md: -------------------------------------------------------------------------------- 1 | Bugfix: Fix index out of range issue within server metrics 2 | 3 | The code has not checked if an index have been really available within the 4 | server metrics API response. With this fix it gets properly handled. 5 | 6 | https://github.com/promhippie/hcloud_exporter/issues/72 7 | -------------------------------------------------------------------------------- /changelog/1.2.3_2023-08-10/fix-lb-traffic.md: -------------------------------------------------------------------------------- 1 | Bugfix: Correctly read loadbalancer traffic 2 | 3 | We used a wrong attribute to read the loadbalancer traffic which resulted in 4 | missing metrics for the realtime traffic in and out for all loadbalancers. With 5 | this fix you should be able to use the metrics. 6 | 7 | https://github.com/promhippie/hcloud_exporter/issues/175 8 | -------------------------------------------------------------------------------- /changelog/1.3.0_2023-10-26/file-secrets.md: -------------------------------------------------------------------------------- 1 | Change: Read secrets form files 2 | 3 | We have added proper support to load secrets like the password from files or 4 | from base64-encoded strings. Just provide the flags or environment variables 5 | for token or private key with a DSN formatted string like `file://path/to/file` 6 | or `base64://Zm9vYmFy`. 7 | 8 | https://github.com/promhippie/hcloud_exporter/pull/193 9 | -------------------------------------------------------------------------------- /changelog/1.3.0_2023-10-26/pprof-profiler.md: -------------------------------------------------------------------------------- 1 | Enhancement: Integrate option pprof profiling 2 | 3 | We have added an option to enable a pprof endpoint for proper profiling support 4 | with the help of tools like Parca. The endpoint `/debug/pprof` can now 5 | optionally be enabled to get the profiling details for catching potential memory 6 | leaks. 7 | 8 | https://github.com/promhippie/hcloud_exporter/pull/193 9 | -------------------------------------------------------------------------------- /changelog/1.3.0_2023-10-26/web-config.md: -------------------------------------------------------------------------------- 1 | Change: Integrate standard web config 2 | 3 | We integrated the new web config from the Prometheus toolkit which provides a 4 | configuration for TLS support and also some basic builtin authentication. For 5 | the detailed configuration you can check out the documentation. 6 | 7 | https://github.com/promhippie/hcloud_exporter/pull/193 8 | -------------------------------------------------------------------------------- /changelog/2.0.0_2024-08-29/pricing-errors.md: -------------------------------------------------------------------------------- 1 | Change: Improve pricing error handling 2 | 3 | So far we always existed the scraping if there have been any kind of error while 4 | parsing the metric values, from now on we are logging an error but continue to 5 | provide the remaining metrics to avoid loosing unrelated metrics. 6 | 7 | https://github.com/promhippie/hcloud_exporter/pull/240 8 | -------------------------------------------------------------------------------- /changelog/2.0.0_2024-08-29/server-paging.md: -------------------------------------------------------------------------------- 1 | Bugfix: Fetch metrics for all servers 2 | 3 | For previous versions we have used the wrong client function to gather the list 4 | of servers for the server metrics, this hvae been fixed by using a function that 5 | automatically fetches all servers by iterating of the pagination. 6 | 7 | https://github.com/promhippie/hcloud_exporter/issues/246 8 | -------------------------------------------------------------------------------- /changelog/2.0.0_2024-08-29/traffic-metrics.md: -------------------------------------------------------------------------------- 1 | Change: New traffic pricing metrics because of deprecation 2 | 3 | The previous traffic pricing metrics have been deprecated and got to be replaced 4 | by new metrics as the new metrics have been split between service type like load 5 | balancers and server types. 6 | 7 | https://github.com/promhippie/hcloud_exporter/pull/240 8 | https://github.com/promhippie/hcloud_exporter/issues/248 9 | -------------------------------------------------------------------------------- /changelog/2.1.0_2024-10-26/logging-library.md: -------------------------------------------------------------------------------- 1 | Change: Switch to official logging library 2 | 3 | Since there have been a structured logger part of the Go standard library we 4 | thought it's time to replace the library with that. Be aware that log messages 5 | should change a little bit. 6 | 7 | https://github.com/promhippie/hcloud_exporter/issues/258 8 | -------------------------------------------------------------------------------- /changelog/2.1.0_2024-10-26/pricing-changes.md: -------------------------------------------------------------------------------- 1 | Change: Add type to IP pricing and add metrics for primary IPs 2 | 3 | Since the client SDK has deprecated the previous handling for the pricing of IP 4 | addresses we had to update the metrics to include the type and location of the 5 | IPs. Besides that we have also added metrics for the pricing of the primary IP 6 | addresses. 7 | 8 | https://github.com/promhippie/hcloud_exporter/pull/272 9 | -------------------------------------------------------------------------------- /changelog/CHANGELOG.tmpl: -------------------------------------------------------------------------------- 1 | {{- range $changes := . }}{{ with $changes -}} 2 | # Changelog for {{ .Version }} 3 | 4 | The following sections list the changes for {{ .Version }}. 5 | 6 | ## Summary 7 | {{ range $entry := .Entries }}{{ with $entry }} 8 | * {{ .TypeShort }} #{{ .PrimaryID }}: {{ .Title }} 9 | {{- end }}{{ end }} 10 | 11 | ## Details 12 | {{ range $entry := .Entries }}{{ with $entry }} 13 | * {{ .Type }} #{{ .PrimaryID }}: {{ .Title }} 14 | {{ range $par := .Paragraphs }} 15 | {{ wrapIndent $par 80 3 }} 16 | {{ end -}} 17 | {{ range $url := .IssueURLs }} 18 | {{ $url -}} 19 | {{ end -}} 20 | {{ range $url := .PRURLs }} 21 | {{ $url -}} 22 | {{ end -}} 23 | {{ range $url := .OtherURLs }} 24 | {{ $url -}} 25 | {{ end }} 26 | {{ end }}{{ end }} 27 | 28 | {{ end }}{{ end -}} 29 | -------------------------------------------------------------------------------- /changelog/README.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | We are using [calens](https://github.com/restic/calens) to properly generate a 4 | changelog before we are tagging a new release. To get an idea how this could 5 | look like would be the 6 | best reference. 7 | -------------------------------------------------------------------------------- /changelog/TEMPLATE: -------------------------------------------------------------------------------- 1 | Bugfix: Fix behavior for foobar (in present tense) 2 | 3 | We've fixed the behavior for foobar, a long-standing annoyance for users. The 4 | text should be wrapped at 80 characters length. 5 | 6 | The text in the paragraphs is written in past tense. The last section is a list 7 | of issue URLs, PR URLs and other URLs. The first issue ID (or the first PR ID, 8 | in case there aren't any issue links) is used as the primary ID. 9 | 10 | https://github.com/promhippie/hcloud_exporter/issues/1234 11 | https://github.com/promhippie/hcloud_exporter/pull/55555 12 | -------------------------------------------------------------------------------- /changelog/unreleased/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/promhippie/hcloud_exporter/df6baf62d5db6971660c2ae0a282ba2e4777dc2a/changelog/unreleased/.keep -------------------------------------------------------------------------------- /cmd/hcloud_exporter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/joho/godotenv" 7 | "github.com/promhippie/hcloud_exporter/pkg/command" 8 | ) 9 | 10 | func main() { 11 | if env := os.Getenv("HCLOUD_EXPORTER_ENV_FILE"); env != "" { 12 | godotenv.Load(env) 13 | } 14 | 15 | if err := command.Run(); err != nil { 16 | os.Exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /deploy/kubernetes/deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | 5 | metadata: 6 | name: hcloud-exporter 7 | labels: 8 | app.kubernetes.io/name: hcloud-exporter 9 | app.kubernetes.io/component: exporter 10 | 11 | spec: 12 | replicas: 1 13 | 14 | revisionHistoryLimit: 3 15 | progressDeadlineSeconds: 600 16 | 17 | strategy: 18 | type: Recreate 19 | 20 | selector: 21 | matchLabels: 22 | app.kubernetes.io/name: hcloud-exporter 23 | app.kubernetes.io/component: exporter 24 | 25 | template: 26 | metadata: 27 | labels: 28 | app.kubernetes.io/name: hcloud-exporter 29 | app.kubernetes.io/component: exporter 30 | 31 | spec: 32 | restartPolicy: Always 33 | terminationGracePeriodSeconds: 30 34 | 35 | containers: 36 | - name: exporter 37 | image: hcloud-exporter 38 | imagePullPolicy: Always 39 | 40 | envFrom: 41 | - configMapRef: 42 | name: hcloud-exporter 43 | - secretRef: 44 | name: hcloud-exporter 45 | 46 | ports: 47 | - name: http 48 | containerPort: 9501 49 | protocol: TCP 50 | 51 | livenessProbe: 52 | httpGet: 53 | path: /healthz 54 | port: http 55 | 56 | readinessProbe: 57 | httpGet: 58 | path: /readyz 59 | port: http 60 | 61 | ... 62 | -------------------------------------------------------------------------------- /deploy/kubernetes/kustomization.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | 5 | resources: 6 | - servicemonitor.yml 7 | - service.yml 8 | - deployment.yml 9 | 10 | configMapGenerator: 11 | - name: hcloud-exporter 12 | literals: [] 13 | 14 | secretGenerator: 15 | - name: hcloud-exporter 16 | literals: [] 17 | 18 | images: 19 | - name: hcloud-exporter 20 | newName: quay.io/promhippie/hcloud-exporter 21 | newTag: latest 22 | 23 | ... 24 | -------------------------------------------------------------------------------- /deploy/kubernetes/service.yml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: Service 3 | apiVersion: v1 4 | 5 | metadata: 6 | name: hcloud-exporter 7 | labels: 8 | app.kubernetes.io/name: hcloud-exporter 9 | app.kubernetes.io/component: exporter 10 | 11 | spec: 12 | selector: 13 | app.kubernetes.io/name: hcloud-exporter 14 | app.kubernetes.io/component: exporter 15 | 16 | ports: 17 | - name: http 18 | port: 9501 19 | targetPort: http 20 | protocol: TCP 21 | 22 | ... 23 | -------------------------------------------------------------------------------- /deploy/kubernetes/servicemonitor.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | 5 | metadata: 6 | name: hcloud-exporter 7 | labels: 8 | app.kubernetes.io/name: hcloud-exporter 9 | app.kubernetes.io/component: exporter 10 | 11 | spec: 12 | endpoints: 13 | - interval: 60s 14 | port: http 15 | scheme: http 16 | path: /metrics 17 | 18 | selector: 19 | matchLabels: 20 | app.kubernetes.io/name: hcloud-exporter 21 | app.kubernetes.io/component: exporter 22 | 23 | ... 24 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.386: -------------------------------------------------------------------------------- 1 | FROM i386/alpine:3.22@sha256:dcfdb8bfec3218e0d2e402265f965bc241871392b0b686796137d63cead3945b AS build 2 | RUN apk add --no-cache ca-certificates mailcap 3 | 4 | FROM scratch 5 | 6 | EXPOSE 9501 7 | ENTRYPOINT ["/usr/bin/hcloud_exporter"] 8 | HEALTHCHECK CMD ["/usr/bin/hcloud_exporter", "health"] 9 | 10 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 11 | COPY --from=build /etc/mime.types /etc/ 12 | 13 | COPY bin/hcloud_exporter /usr/bin/hcloud_exporter 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.amd64: -------------------------------------------------------------------------------- 1 | FROM amd64/alpine:3.22@sha256:f29909b294ed398ae64ad9bc268a3ce2c4824fb37375cac63763e6e8f886f3b1 AS build 2 | RUN apk add --no-cache ca-certificates mailcap 3 | 4 | FROM scratch 5 | 6 | EXPOSE 9501 7 | ENTRYPOINT ["/usr/bin/hcloud_exporter"] 8 | HEALTHCHECK CMD ["/usr/bin/hcloud_exporter", "health"] 9 | 10 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 11 | COPY --from=build /etc/mime.types /etc/ 12 | 13 | COPY bin/hcloud_exporter /usr/bin/hcloud_exporter 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.arm: -------------------------------------------------------------------------------- 1 | FROM arm32v6/alpine:3.22@sha256:1b418ed7e714de83d1340852aa0127c4b0a20f70dd4af970e452a2dc06979f98 AS build 2 | RUN apk add --no-cache ca-certificates mailcap 3 | 4 | FROM scratch 5 | 6 | EXPOSE 9501 7 | ENTRYPOINT ["/usr/bin/hcloud_exporter"] 8 | HEALTHCHECK CMD ["/usr/bin/hcloud_exporter", "health"] 9 | 10 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 11 | COPY --from=build /etc/mime.types /etc/ 12 | 13 | COPY bin/hcloud_exporter /usr/bin/hcloud_exporter 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.arm64: -------------------------------------------------------------------------------- 1 | FROM arm64v8/alpine:3.22@sha256:fa4cf50559eaaaafd69341a3bc5fc09047b53480c884a3bc3e4f6e13da13f503 AS build 2 | RUN apk add --no-cache ca-certificates mailcap 3 | 4 | FROM scratch 5 | 6 | EXPOSE 9501 7 | ENTRYPOINT ["/usr/bin/hcloud_exporter"] 8 | HEALTHCHECK CMD ["/usr/bin/hcloud_exporter", "health"] 9 | 10 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 11 | COPY --from=build /etc/mime.types /etc/ 12 | 13 | COPY bin/hcloud_exporter /usr/bin/hcloud_exporter 14 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | public/ 2 | resources/ 3 | 4 | .hugo_build.lock 5 | -------------------------------------------------------------------------------- /docs/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .TranslationBaseName "-" " " | title }}" 3 | date: {{ .Date }} 4 | anchor: "{{ replace .TranslationBaseName "-" " " | title | urlize }}" 5 | weight: 6 | --- 7 | -------------------------------------------------------------------------------- /docs/content/building.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Building" 3 | date: 2022-07-20T00:00:00+00:00 4 | anchor: "building" 5 | weight: 20 6 | --- 7 | 8 | As this project is built with Go you need to install Go first. The installation 9 | of Go is out of the scope of this document, please follow the 10 | [official documentation][golang]. After the installation of Go you need to get 11 | the sources: 12 | 13 | {{< highlight txt >}} 14 | git clone https://github.com/promhippie/hcloud_exporter.git 15 | cd hcloud_exporter/ 16 | {{< / highlight >}} 17 | 18 | All required tool besides Go itself are bundled, all you need is part of the 19 | `Makefile`: 20 | 21 | {{< highlight txt >}} 22 | make generate build 23 | {{< / highlight >}} 24 | 25 | Finally you should have the binary within the `bin/` folder now, give it a try 26 | with `./bin/hcloud_exporter -h` to see all available options. 27 | 28 | [golang]: https://golang.org/doc/install 29 | -------------------------------------------------------------------------------- /docs/content/kubernetes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Kubernetes" 3 | date: 2022-07-22T00:00:00+00:00 4 | anchor: "kubernetes" 5 | weight: 20 6 | --- 7 | 8 | ## Kubernetes 9 | 10 | Currently we are covering the most famous installation methods on Kubernetes, 11 | you can choose between [Kustomize][kustomize] and [Helm][helm]. 12 | 13 | ### Kustomize 14 | 15 | We won't cover the installation of [Kustomize][kustomize] within this guide, to 16 | get it installed and working please read the upstream documentation. After the 17 | installation of [Kustomize][kustomize] you just need to prepare a 18 | `kustomization.yml` wherever you like similar to this: 19 | 20 | {{< highlight yaml >}} 21 | apiVersion: kustomize.config.k8s.io/v1beta1 22 | kind: Kustomization 23 | namespace: hcloud-exporter 24 | 25 | resources: 26 | - github.com/promhippie/hcloud_exporter//deploy/kubernetes?ref=master 27 | 28 | configMapGenerator: 29 | - name: hcloud-exporter 30 | behavior: merge 31 | literals: [] 32 | 33 | secretGenerator: 34 | - name: hcloud-exporter 35 | behavior: merge 36 | literals: [] 37 | {{< / highlight >}} 38 | 39 | After that you can simply execute `kustomize build | kubectl apply -f -` to get 40 | the manifest applied. Generally it's best to use fixed versions of the container 41 | images, this can be done quite easy, you just need to append this block to your 42 | `kustomization.yml` to use this specific version: 43 | 44 | {{< highlight yaml >}} 45 | images: 46 | - name: quay.io/promhippie/hcloud-exporter 47 | newTag: 1.1.0 48 | {{< / highlight >}} 49 | 50 | After applying this manifest the exporter should be directly visible within your 51 | Prometheus instance if you are using the Prometheus Operator as these manifests 52 | are providing a ServiceMonitor. 53 | 54 | ### Helm 55 | 56 | We won't cover the installation of [Helm][helm] within this guide, to get it 57 | installed and working please read the upstream documentation. After the 58 | installation of [Helm][helm] you just need to execute the following commands: 59 | 60 | {{< highlight console >}} 61 | helm repo add promhippie https://promhippie.github.io/charts 62 | helm show values promhippie/hcloud-exporter 63 | helm install hcloud-exporter promhippie/hcloud-exporter 64 | {{< / highlight >}} 65 | 66 | You can also watch that available values and generally the details of the chart 67 | provided by us within our [chart][chart] repository or on [Artifacthub][ahub]. 68 | 69 | After applying this manifest the exporter should be directly visible within your 70 | Prometheus instance depending on your installation if you enabled the 71 | annotations or the service monitor. 72 | 73 | [kustomize]: https://github.com/kubernetes-sigs/kustomize 74 | [helm]: https://helm.sh 75 | [chart]: https://github.com/promhippie/charts/tree/master/stable/hcloud-exporter 76 | [ahub]: https://artifacthub.io/packages/helm/promhippie/hcloud-exporter 77 | -------------------------------------------------------------------------------- /docs/content/license.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "License" 3 | date: 2022-07-20T00:00:00+00:00 4 | anchor: "license" 5 | weight: 40 6 | --- 7 | 8 | This project is licensed under the [Apache 2.0][license] license. For the 9 | license of the used libraries you have to check the respective sources. 10 | 11 | [license]: https://github.com/promhippie/hcloud_exporter/blob/master/LICENSE 12 | -------------------------------------------------------------------------------- /docs/content/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Usage" 3 | date: 2022-07-22T00:00:00+00:00 4 | anchor: "getting-started" 5 | weight: 10 6 | --- 7 | 8 | ## Installation 9 | 10 | We won't cover further details how to properly setup [Prometheus][prometheus] 11 | itself, we will only cover some basic setup based on [docker-compose][compose]. 12 | But if you want to run this exporter without [docker-compose][compose] you 13 | should be able to adopt that to your needs. 14 | 15 | First of all we need to prepare a configuration for [Prometheus][prometheus] 16 | that includes the exporter based on a static configuration with the container 17 | name as a hostname: 18 | 19 | {{< highlight yaml >}} 20 | global: 21 | scrape_interval: 1m 22 | scrape_timeout: 10s 23 | evaluation_interval: 1m 24 | 25 | scrape_configs: 26 | - job_name: hcloud 27 | static_configs: 28 | - targets: 29 | - hcloud_exporter:9501 30 | {{< / highlight >}} 31 | 32 | After preparing the configuration we need to create the `docker-compose.yml` 33 | within the same folder, this `docker-compose.yml` starts a simple 34 | [Prometheus][prometheus] instance together with the exporter. Don't forget to 35 | update the environment variables with the required credentials. 36 | 37 | {{< highlight yaml >}} 38 | version: '2' 39 | 40 | volumes: 41 | prometheus: 42 | 43 | services: 44 | prometheus: 45 | image: prom/prometheus:latest 46 | restart: always 47 | ports: 48 | - 9090:9090 49 | volumes: 50 | - prometheus:/prometheus 51 | - ./prometheus.yml:/etc/prometheus/prometheus.yml 52 | 53 | hcloud_exporter: 54 | image: promhippie/hcloud-exporter:latest 55 | restart: always 56 | environment: 57 | - HCLOUD_EXPORTER_TOKEN=bldyecdtysdahs76ygtbw51w3oeo6a4cvjwoitmb 58 | - HCLOUD_EXPORTER_LOG_PRETTY=true 59 | {{< / highlight >}} 60 | 61 | Since our `latest` tag always refers to the `master` branch of the Git 62 | repository you should always use some fixed version. You can see all available 63 | tags at [DockerHub][dockerhub] or [Quay][quayio], there you will see that we 64 | also provide a manifest, you can easily start the exporter on various 65 | architectures without any change to the image name. You should apply a change 66 | like this to the `docker-compose.yml` file: 67 | 68 | {{< highlight diff >}} 69 | hcloud_exporter: 70 | - image: promhippie/hcloud-exporter:latest 71 | + image: promhippie/hcloud-exporter:1.0.0 72 | restart: always 73 | environment: 74 | - HCLOUD_EXPORTER_TOKEN=bldyecdtysdahs76ygtbw51w3oeo6a4cvjwoitmb 75 | - HCLOUD_EXPORTER_LOG_PRETTY=true 76 | {{< / highlight >}} 77 | 78 | If you want to access the exporter directly you should bind it to a local port, 79 | otherwise only [Prometheus][prometheus] will have access to the exporter. For 80 | debugging purpose or just to discover all available metrics directly you can 81 | apply this change to your `docker-compose.yml`, after that you can access it 82 | directly at [http://localhost:9501/metrics](http://localhost:9501/metrics): 83 | 84 | {{< highlight diff >}} 85 | hcloud-exporter: 86 | image: promhippie/hcloud-exporter:latest 87 | restart: always 88 | + ports: 89 | + - 127.0.0.1:9501:9501 90 | environment: 91 | - HCLOUD_EXPORTER_TOKEN=bldyecdtysdahs76ygtbw51w3oeo6a4cvjwoitmb 92 | - HCLOUD_EXPORTER_LOG_PRETTY=true 93 | {{< / highlight >}} 94 | 95 | If you want to secure the access to the exporter you can provide a web config. 96 | You just need to provide a path to the config file in order to enable the 97 | support for it, for details about the config format look at the 98 | [documentation](#web-configuration) section: 99 | 100 | {{< highlight diff >}} 101 | hcloud_exporter: 102 | image: promhippie/hcloud-exporter:latest 103 | restart: always 104 | environment: 105 | + - HCLOUD_EXPORTER_WEB_CONFIG=path/to/web-config.json 106 | - HCLOUD_EXPORTER_TOKEN=bldyecdtysdahs76ygtbw51w3oeo6a4cvjwoitmb 107 | - HCLOUD_EXPORTER_LOG_PRETTY=true 108 | {{< / highlight >}} 109 | 110 | If you want to provide the required secrets from a file it's also possible. For 111 | this use case you can write the secret to a file on any path and reference it 112 | with the following format: 113 | 114 | {{< highlight diff >}} 115 | hcloud_exporter: 116 | image: promhippie/hcloud-exporter:latest 117 | restart: always 118 | environment: 119 | - - HCLOUD_EXPORTER_TOKEN=bldyecdtysdahs76ygtbw51w3oeo6a4cvjwoitmb 120 | + - HCLOUD_EXPORTER_TOKEN=file://path/to/secret/file/with/token 121 | - HCLOUD_EXPORTER_LOG_PRETTY=true 122 | {{< / highlight >}} 123 | 124 | Finally the exporter should be configured fine, let's start this stack with 125 | [docker-compose][compose], you just need to execute `docker-compose up` within 126 | the directory where you have stored the `prometheus.yml` and 127 | `docker-compose.yml`. 128 | 129 | That's all, the exporter should be up and running. Have fun with it and 130 | hopefully you will gather interesting metrics and never run into issues. You can 131 | access the exporter at 132 | [http://localhost:9501/metrics](http://localhost:9501/metrics) and 133 | [Prometheus][prometheus] at [http://localhost:9090](http://localhost:9090). 134 | 135 | ## Configuration 136 | 137 | {{< partial "envvars.md" >}} 138 | 139 | ### Web Configuration 140 | 141 | If you want to secure the service by TLS or by some basic authentication you can 142 | provide a `YAML` configuration file which follows the [Prometheus][prometheus] 143 | toolkit format. You can see a full configuration example within the 144 | [toolkit documentation][toolkit]. 145 | 146 | ## Metrics 147 | 148 | You can a rough list of available metrics below, additionally to these metrics 149 | you will always get the standard metrics exported by the Golang client of 150 | [Prometheus][prometheus]. If you want to know more about these standard metrics 151 | take a look at the [process collector][proccollector] and the 152 | [Go collector][gocollector]. 153 | 154 | {{< partial "metrics.md" >}} 155 | 156 | [prometheus]: https://prometheus.io 157 | [compose]: https://docs.docker.com/compose/ 158 | [dockerhub]: https://hub.docker.com/r/promhippie/hcloud-exporter/tags/ 159 | [quayio]: https://quay.io/repository/promhippie/hcloud-exporter?tab=tags 160 | [toolkit]: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md 161 | [proccollector]: https://github.com/prometheus/client_golang/blob/master/prometheus/process_collector.go 162 | [gocollector]: https://github.com/prometheus/client_golang/blob/master/prometheus/go_collector.go 163 | -------------------------------------------------------------------------------- /docs/hugo.toml: -------------------------------------------------------------------------------- 1 | baseURL = "https://promhippie.github.io/hcloud_exporter/" 2 | languageCode = "en-us" 3 | title = "Hetzner Cloud Exporter" 4 | pygmentsUseClasses = true 5 | 6 | disableKinds = ["RSS", "sitemap", "taxonomy", "term", "section"] 7 | enableRobotsTXT = true 8 | 9 | [markup.goldmark.renderer] 10 | unsafe = true 11 | 12 | [blackfriday] 13 | angledQuotes = true 14 | fractions = false 15 | plainIDAnchors = true 16 | smartlists = true 17 | extensions = ["hardLineBreak"] 18 | 19 | [params] 20 | author = "Thomas Boerger" 21 | description = "Prometheus exporter for Hetzner Cloud" 22 | keywords = "prometheus, exporter, hetzner, cloud" 23 | -------------------------------------------------------------------------------- /docs/layouts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{ .Site.Title }} 10 | 11 | 12 | 13 | 14 | 15 | {{ partial "style.html" . }} 16 | 17 | 18 | 19 | 38 | 39 | {{ range .Data.Pages.ByWeight }} 40 |
41 |

42 | 43 | {{ .Title }} 44 | 45 | 46 | 47 | 48 | Back to Top 49 | 50 | 51 |

52 | 53 | {{ .Content | markdownify }} 54 |
55 | {{ end }} 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/layouts/partials/style.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 344 | -------------------------------------------------------------------------------- /docs/layouts/shortcodes/partial.html: -------------------------------------------------------------------------------- 1 | {{- $file := printf "/partials/%s" (.Get 0) -}}{{- $file | readFile | markdownify -}} -------------------------------------------------------------------------------- /docs/partials/envvars.md: -------------------------------------------------------------------------------- 1 | HCLOUD_EXPORTER_LOG_LEVEL 2 | : Only log messages with given severity, defaults to `info` 3 | 4 | HCLOUD_EXPORTER_LOG_PRETTY 5 | : Enable pretty messages for logging, defaults to `false` 6 | 7 | HCLOUD_EXPORTER_WEB_ADDRESS 8 | : Address to bind the metrics server, defaults to `0.0.0.0:9501` 9 | 10 | HCLOUD_EXPORTER_WEB_PATH 11 | : Path to bind the metrics server, defaults to `/metrics` 12 | 13 | HCLOUD_EXPORTER_WEB_PPROF 14 | : Enable pprof debugging for server, defaults to `false` 15 | 16 | HCLOUD_EXPORTER_WEB_TIMEOUT 17 | : Server metrics endpoint timeout, defaults to `10s` 18 | 19 | HCLOUD_EXPORTER_WEB_CONFIG 20 | : Path to web-config file 21 | 22 | HCLOUD_EXPORTER_REQUEST_TIMEOUT 23 | : Request timeout as duration, defaults to `10s` 24 | 25 | HCLOUD_EXPORTER_TOKEN 26 | : Access token for the HetznerCloud API 27 | 28 | HCLOUD_EXPORTER_COLLECTOR_FLOATING_IPS 29 | : Enable collector for floating IPs, defaults to `true` 30 | 31 | HCLOUD_EXPORTER_COLLECTOR_IMAGES 32 | : Enable collector for images, defaults to `true` 33 | 34 | HCLOUD_EXPORTER_COLLECTOR_PRICING 35 | : Enable collector for pricing, defaults to `true` 36 | 37 | HCLOUD_EXPORTER_COLLECTOR_SERVERS 38 | : Enable collector for servers, defaults to `true` 39 | 40 | HCLOUD_EXPORTER_COLLECTOR_SERVER_METRICS 41 | : Enable collector for server metrics, defaults to `false` 42 | 43 | HCLOUD_EXPORTER_COLLECTOR_LOAD_BALANCERS 44 | : Enable collector for load balancers, defaults to `true` 45 | 46 | HCLOUD_EXPORTER_COLLECTOR_SSH_KEYS 47 | : Enable collector for SSH keys, defaults to `true` 48 | 49 | HCLOUD_EXPORTER_COLLECTOR_VOLUMES 50 | : Enable collector for volumes, defaults to `false` 51 | -------------------------------------------------------------------------------- /docs/partials/metrics.md: -------------------------------------------------------------------------------- 1 | hcloud_floating_ip_active{id, server, location, type, ip} 2 | : If 1 the floating IP is used by a server, 0 otherwise 3 | 4 | hcloud_image_active{id, name, type, server, flavor, version} 5 | : If 1 the image is used by a server, 0 otherwise 6 | 7 | hcloud_image_created_timestamp{id, name, type, server, flavor, version} 8 | : Timestamp when the image have been created 9 | 10 | hcloud_image_deprecated_timestamp{id, name, type, server, flavor, version} 11 | : Timestamp when the image will be deprecated, 0 if not deprecated 12 | 13 | hcloud_image_disk_bytes{id, name, type, server, flavor, version} 14 | : Size if the disk for the image in bytes 15 | 16 | hcloud_image_size_bytes{id, name, type, server, flavor, version} 17 | : Size of the image in bytes 18 | 19 | hcloud_pricing_floating_ip{currency, vat, type, location} 20 | : The cost of one floating IP per month 21 | 22 | hcloud_pricing_image{currency, vat} 23 | : The cost of an image per GB/month 24 | 25 | hcloud_pricing_loadbalancer_type{currency, vat, type, location} 26 | : The costs of a load balancer by type and location per month 27 | 28 | hcloud_pricing_loadbalancer_type_traffic{currency, vat, type, location} 29 | : The costs of additional traffic per TB for a load balancer by type and location per month 30 | 31 | hcloud_pricing_primary_ip{currency, vat, type, location} 32 | : The cost of one primary IP per month 33 | 34 | hcloud_pricing_server_backup{} 35 | : Will increase base server costs by specific percentage if server backups are enabled 36 | 37 | hcloud_pricing_server_type{currency, vat, type, location} 38 | : The costs of a server by type and location per month 39 | 40 | hcloud_pricing_server_type_traffic{currency, vat, type, location} 41 | : The costs of additional traffic per TB for a server by type and location per month 42 | 43 | hcloud_pricing_volume{currency, vat} 44 | : The cost of a volume per GB/month 45 | 46 | hcloud_request_duration_seconds{collector} 47 | : Histogram of latencies for requests to the api per collector 48 | 49 | hcloud_request_failures_total{collector} 50 | : Total number of failed requests to the api per collector 51 | 52 | hcloud_server_backup{id, name, datacenter} 53 | : If 1 server backups are enabled, 0 otherwise 54 | 55 | hcloud_server_cores{id, name, datacenter} 56 | : Server number of cores 57 | 58 | hcloud_server_created_timestamp{id, name, datacenter} 59 | : Timestamp when the server have been created 60 | 61 | hcloud_server_disk_bytes{id, name, datacenter} 62 | : Server disk in bytes 63 | 64 | hcloud_server_included_traffic_bytes{id, name, datacenter} 65 | : Included traffic for the server in bytes 66 | 67 | hcloud_server_incoming_traffic_bytes{id, name, datacenter} 68 | : Ingoing traffic to the server in bytes 69 | 70 | hcloud_server_memory_bytes{id, name, datacenter} 71 | : Server memory in bytes 72 | 73 | hcloud_server_metrics_cpu{id, name, datacenter} 74 | : Server CPU usage metric 75 | 76 | hcloud_server_metrics_disk_read_bps{id, name, datacenter, disk} 77 | : Server disk write bytes/s metric 78 | 79 | hcloud_server_metrics_disk_read_iops{id, name, datacenter, disk} 80 | : Server disk read iop/s metric 81 | 82 | hcloud_server_metrics_disk_write_bps{id, name, datacenter, disk} 83 | : Server disk write bytes/s metric 84 | 85 | hcloud_server_metrics_disk_write_iops{id, name, datacenter, disk} 86 | : Server disk write iop/s metric 87 | 88 | hcloud_server_metrics_network_in_bps{id, name, datacenter, interface} 89 | : Server network incoming bytes/s metric 90 | 91 | hcloud_server_metrics_network_in_pps{id, name, datacenter, interface} 92 | : Server network incoming packets/s metric 93 | 94 | hcloud_server_metrics_network_out_bps{id, name, datacenter, interface} 95 | : Server network outgoing bytes/s metric 96 | 97 | hcloud_server_metrics_network_out_pps{id, name, datacenter, interface} 98 | : Server network outgoing packets/s metric 99 | 100 | hcloud_server_outgoing_traffic_bytes{id, name, datacenter} 101 | : Outgoing traffic from the server in bytes 102 | 103 | hcloud_server_price_hourly{id, name, datacenter, vat} 104 | : Price of the server billed hourly in € 105 | 106 | hcloud_server_price_monthly{id, name, datacenter, vat} 107 | : Price of the server billed monthly in € 108 | 109 | hcloud_server_running{id, name, datacenter} 110 | : If 1 the server is running, 0 otherwise 111 | 112 | hcloud_ssh_key{id, name, fingerprint} 113 | : Information about SSH keys in your HetznerCloud project 114 | 115 | hcloud_volume_created{id, server, location, name} 116 | : Timestamp when the volume have been created 117 | 118 | hcloud_volume_protection{id, server, location, name} 119 | : If 1 the volume is protected, 0 otherwise 120 | 121 | hcloud_volume_size{id, server, location, name} 122 | : Size of the volume in GB 123 | 124 | hcloud_volume_status{id, server, location, name} 125 | : If 1 the volume is availabel, 0 otherwise 126 | -------------------------------------------------------------------------------- /docs/static/syntax.css: -------------------------------------------------------------------------------- 1 | /* Background */ 2 | .chroma { 3 | color: #f8f8f2; 4 | background-color: #272822 5 | } 6 | 7 | /* Error */ 8 | .chroma .err { 9 | color: #960050; 10 | background-color: #1e0010 11 | } 12 | 13 | /* LineTableTD */ 14 | .chroma .lntd { 15 | vertical-align: top; 16 | padding: 0; 17 | margin: 0; 18 | border: 0; 19 | } 20 | 21 | /* LineTable */ 22 | .chroma .lntable { 23 | border-spacing: 0; 24 | padding: 0; 25 | margin: 0; 26 | border: 0; 27 | width: auto; 28 | overflow: auto; 29 | display: block; 30 | } 31 | 32 | /* LineHighlight */ 33 | .chroma .hl { 34 | display: block; 35 | width: 100%; 36 | background-color: #ffffcc 37 | } 38 | 39 | /* LineNumbersTable */ 40 | .chroma .lnt { 41 | margin-right: 0.4em; 42 | padding: 0 0.4em 0 0.4em; 43 | } 44 | 45 | /* LineNumbers */ 46 | .chroma .ln { 47 | margin-right: 0.4em; 48 | padding: 0 0.4em 0 0.4em; 49 | } 50 | 51 | /* Keyword */ 52 | .chroma .k { 53 | color: #66d9ef 54 | } 55 | 56 | /* KeywordConstant */ 57 | .chroma .kc { 58 | color: #66d9ef 59 | } 60 | 61 | /* KeywordDeclaration */ 62 | .chroma .kd { 63 | color: #66d9ef 64 | } 65 | 66 | /* KeywordNamespace */ 67 | .chroma .kn { 68 | color: #f92672 69 | } 70 | 71 | /* KeywordPseudo */ 72 | .chroma .kp { 73 | color: #66d9ef 74 | } 75 | 76 | /* KeywordReserved */ 77 | .chroma .kr { 78 | color: #66d9ef 79 | } 80 | 81 | /* KeywordType */ 82 | .chroma .kt { 83 | color: #66d9ef 84 | } 85 | 86 | /* NameAttribute */ 87 | .chroma .na { 88 | color: #a6e22e 89 | } 90 | 91 | /* NameClass */ 92 | .chroma .nc { 93 | color: #a6e22e 94 | } 95 | 96 | /* NameConstant */ 97 | .chroma .no { 98 | color: #66d9ef 99 | } 100 | 101 | /* NameDecorator */ 102 | .chroma .nd { 103 | color: #a6e22e 104 | } 105 | 106 | /* NameException */ 107 | .chroma .ne { 108 | color: #a6e22e 109 | } 110 | 111 | /* NameFunction */ 112 | .chroma .nf { 113 | color: #a6e22e 114 | } 115 | 116 | /* NameOther */ 117 | .chroma .nx { 118 | color: #a6e22e 119 | } 120 | 121 | /* NameTag */ 122 | .chroma .nt { 123 | color: #f92672 124 | } 125 | 126 | /* Literal */ 127 | .chroma .l { 128 | color: #ae81ff 129 | } 130 | 131 | /* LiteralDate */ 132 | .chroma .ld { 133 | color: #e6db74 134 | } 135 | 136 | /* LiteralString */ 137 | .chroma .s { 138 | color: #e6db74 139 | } 140 | 141 | /* LiteralStringAffix */ 142 | .chroma .sa { 143 | color: #e6db74 144 | } 145 | 146 | /* LiteralStringBacktick */ 147 | .chroma .sb { 148 | color: #e6db74 149 | } 150 | 151 | /* LiteralStringChar */ 152 | .chroma .sc { 153 | color: #e6db74 154 | } 155 | 156 | /* LiteralStringDelimiter */ 157 | .chroma .dl { 158 | color: #e6db74 159 | } 160 | 161 | /* LiteralStringDoc */ 162 | .chroma .sd { 163 | color: #e6db74 164 | } 165 | 166 | /* LiteralStringDouble */ 167 | .chroma .s2 { 168 | color: #e6db74 169 | } 170 | 171 | /* LiteralStringEscape */ 172 | .chroma .se { 173 | color: #ae81ff 174 | } 175 | 176 | /* LiteralStringHeredoc */ 177 | .chroma .sh { 178 | color: #e6db74 179 | } 180 | 181 | /* LiteralStringInterpol */ 182 | .chroma .si { 183 | color: #e6db74 184 | } 185 | 186 | /* LiteralStringOther */ 187 | .chroma .sx { 188 | color: #e6db74 189 | } 190 | 191 | /* LiteralStringRegex */ 192 | .chroma .sr { 193 | color: #e6db74 194 | } 195 | 196 | /* LiteralStringSingle */ 197 | .chroma .s1 { 198 | color: #e6db74 199 | } 200 | 201 | /* LiteralStringSymbol */ 202 | .chroma .ss { 203 | color: #e6db74 204 | } 205 | 206 | /* LiteralNumber */ 207 | .chroma .m { 208 | color: #ae81ff 209 | } 210 | 211 | /* LiteralNumberBin */ 212 | .chroma .mb { 213 | color: #ae81ff 214 | } 215 | 216 | /* LiteralNumberFloat */ 217 | .chroma .mf { 218 | color: #ae81ff 219 | } 220 | 221 | /* LiteralNumberHex */ 222 | .chroma .mh { 223 | color: #ae81ff 224 | } 225 | 226 | /* LiteralNumberInteger */ 227 | .chroma .mi { 228 | color: #ae81ff 229 | } 230 | 231 | /* LiteralNumberIntegerLong */ 232 | .chroma .il { 233 | color: #ae81ff 234 | } 235 | 236 | /* LiteralNumberOct */ 237 | .chroma .mo { 238 | color: #ae81ff 239 | } 240 | 241 | /* Operator */ 242 | .chroma .o { 243 | color: #f92672 244 | } 245 | 246 | /* OperatorWord */ 247 | .chroma .ow { 248 | color: #f92672 249 | } 250 | 251 | /* Comment */ 252 | .chroma .c { 253 | color: #75715e 254 | } 255 | 256 | /* CommentHashbang */ 257 | .chroma .ch { 258 | color: #75715e 259 | } 260 | 261 | /* CommentMultiline */ 262 | .chroma .cm { 263 | color: #75715e 264 | } 265 | 266 | /* CommentSingle */ 267 | .chroma .c1 { 268 | color: #75715e 269 | } 270 | 271 | /* CommentSpecial */ 272 | .chroma .cs { 273 | color: #75715e 274 | } 275 | 276 | /* CommentPreproc */ 277 | .chroma .cp { 278 | color: #75715e 279 | } 280 | 281 | /* CommentPreprocFile */ 282 | .chroma .cpf { 283 | color: #75715e 284 | } 285 | 286 | /* GenericDeleted */ 287 | .chroma .gd { 288 | color: #f92672 289 | } 290 | 291 | /* GenericEmph */ 292 | .chroma .ge { 293 | font-style: italic 294 | } 295 | 296 | /* GenericInserted */ 297 | .chroma .gi { 298 | color: #a6e22e 299 | } 300 | 301 | /* GenericStrong */ 302 | .chroma .gs { 303 | font-weight: bold 304 | } 305 | 306 | /* GenericSubheading */ 307 | .chroma .gu { 308 | color: #75715e 309 | } 310 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "cachix": { 4 | "inputs": { 5 | "devenv": [ 6 | "devenv" 7 | ], 8 | "flake-compat": [ 9 | "devenv" 10 | ], 11 | "git-hooks": [ 12 | "devenv" 13 | ], 14 | "nixpkgs": "nixpkgs" 15 | }, 16 | "locked": { 17 | "lastModified": 1744206633, 18 | "narHash": "sha256-pb5aYkE8FOoa4n123slgHiOf1UbNSnKe5pEZC+xXD5g=", 19 | "owner": "cachix", 20 | "repo": "cachix", 21 | "rev": "8a60090640b96f9df95d1ab99e5763a586be1404", 22 | "type": "github" 23 | }, 24 | "original": { 25 | "owner": "cachix", 26 | "ref": "latest", 27 | "repo": "cachix", 28 | "type": "github" 29 | } 30 | }, 31 | "devenv": { 32 | "inputs": { 33 | "cachix": "cachix", 34 | "flake-compat": "flake-compat", 35 | "git-hooks": "git-hooks", 36 | "nix": "nix", 37 | "nixpkgs": "nixpkgs_3" 38 | }, 39 | "locked": { 40 | "lastModified": 1747543288, 41 | "narHash": "sha256-W+E2xxaKSQtafQX7KqNwfFm10RrqnSLzKEoA2iZ+VbM=", 42 | "owner": "cachix", 43 | "repo": "devenv", 44 | "rev": "3a8a52386bde1cf14fc2f4c4df80f91417348480", 45 | "type": "github" 46 | }, 47 | "original": { 48 | "owner": "cachix", 49 | "repo": "devenv", 50 | "type": "github" 51 | } 52 | }, 53 | "flake-compat": { 54 | "flake": false, 55 | "locked": { 56 | "lastModified": 1733328505, 57 | "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", 58 | "owner": "edolstra", 59 | "repo": "flake-compat", 60 | "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", 61 | "type": "github" 62 | }, 63 | "original": { 64 | "owner": "edolstra", 65 | "repo": "flake-compat", 66 | "type": "github" 67 | } 68 | }, 69 | "flake-compat_2": { 70 | "flake": false, 71 | "locked": { 72 | "lastModified": 1696426674, 73 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 74 | "owner": "edolstra", 75 | "repo": "flake-compat", 76 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 77 | "type": "github" 78 | }, 79 | "original": { 80 | "owner": "edolstra", 81 | "repo": "flake-compat", 82 | "type": "github" 83 | } 84 | }, 85 | "flake-parts": { 86 | "inputs": { 87 | "nixpkgs-lib": [ 88 | "devenv", 89 | "nix", 90 | "nixpkgs" 91 | ] 92 | }, 93 | "locked": { 94 | "lastModified": 1712014858, 95 | "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", 96 | "owner": "hercules-ci", 97 | "repo": "flake-parts", 98 | "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", 99 | "type": "github" 100 | }, 101 | "original": { 102 | "owner": "hercules-ci", 103 | "repo": "flake-parts", 104 | "type": "github" 105 | } 106 | }, 107 | "flake-parts_2": { 108 | "inputs": { 109 | "nixpkgs-lib": "nixpkgs-lib" 110 | }, 111 | "locked": { 112 | "lastModified": 1743550720, 113 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", 114 | "owner": "hercules-ci", 115 | "repo": "flake-parts", 116 | "rev": "c621e8422220273271f52058f618c94e405bb0f5", 117 | "type": "github" 118 | }, 119 | "original": { 120 | "owner": "hercules-ci", 121 | "repo": "flake-parts", 122 | "type": "github" 123 | } 124 | }, 125 | "git-hooks": { 126 | "inputs": { 127 | "flake-compat": [ 128 | "devenv" 129 | ], 130 | "gitignore": "gitignore", 131 | "nixpkgs": [ 132 | "devenv", 133 | "nixpkgs" 134 | ] 135 | }, 136 | "locked": { 137 | "lastModified": 1746537231, 138 | "narHash": "sha256-Wb2xeSyOsCoTCTj7LOoD6cdKLEROyFAArnYoS+noCWo=", 139 | "owner": "cachix", 140 | "repo": "git-hooks.nix", 141 | "rev": "fa466640195d38ec97cf0493d6d6882bc4d14969", 142 | "type": "github" 143 | }, 144 | "original": { 145 | "owner": "cachix", 146 | "repo": "git-hooks.nix", 147 | "type": "github" 148 | } 149 | }, 150 | "gitignore": { 151 | "inputs": { 152 | "nixpkgs": [ 153 | "devenv", 154 | "git-hooks", 155 | "nixpkgs" 156 | ] 157 | }, 158 | "locked": { 159 | "lastModified": 1709087332, 160 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 161 | "owner": "hercules-ci", 162 | "repo": "gitignore.nix", 163 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 164 | "type": "github" 165 | }, 166 | "original": { 167 | "owner": "hercules-ci", 168 | "repo": "gitignore.nix", 169 | "type": "github" 170 | } 171 | }, 172 | "gitignore_2": { 173 | "inputs": { 174 | "nixpkgs": [ 175 | "pre-commit-hooks-nix", 176 | "nixpkgs" 177 | ] 178 | }, 179 | "locked": { 180 | "lastModified": 1709087332, 181 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 182 | "owner": "hercules-ci", 183 | "repo": "gitignore.nix", 184 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 185 | "type": "github" 186 | }, 187 | "original": { 188 | "owner": "hercules-ci", 189 | "repo": "gitignore.nix", 190 | "type": "github" 191 | } 192 | }, 193 | "libgit2": { 194 | "flake": false, 195 | "locked": { 196 | "lastModified": 1697646580, 197 | "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=", 198 | "owner": "libgit2", 199 | "repo": "libgit2", 200 | "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5", 201 | "type": "github" 202 | }, 203 | "original": { 204 | "owner": "libgit2", 205 | "repo": "libgit2", 206 | "type": "github" 207 | } 208 | }, 209 | "nix": { 210 | "inputs": { 211 | "flake-compat": [ 212 | "devenv" 213 | ], 214 | "flake-parts": "flake-parts", 215 | "libgit2": "libgit2", 216 | "nixpkgs": "nixpkgs_2", 217 | "nixpkgs-23-11": [ 218 | "devenv" 219 | ], 220 | "nixpkgs-regression": [ 221 | "devenv" 222 | ], 223 | "pre-commit-hooks": [ 224 | "devenv" 225 | ] 226 | }, 227 | "locked": { 228 | "lastModified": 1745930071, 229 | "narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=", 230 | "owner": "domenkozar", 231 | "repo": "nix", 232 | "rev": "b455edf3505f1bf0172b39a735caef94687d0d9c", 233 | "type": "github" 234 | }, 235 | "original": { 236 | "owner": "domenkozar", 237 | "ref": "devenv-2.24", 238 | "repo": "nix", 239 | "type": "github" 240 | } 241 | }, 242 | "nixpkgs": { 243 | "locked": { 244 | "lastModified": 1733212471, 245 | "narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=", 246 | "owner": "NixOS", 247 | "repo": "nixpkgs", 248 | "rev": "55d15ad12a74eb7d4646254e13638ad0c4128776", 249 | "type": "github" 250 | }, 251 | "original": { 252 | "owner": "NixOS", 253 | "ref": "nixos-unstable", 254 | "repo": "nixpkgs", 255 | "type": "github" 256 | } 257 | }, 258 | "nixpkgs-lib": { 259 | "locked": { 260 | "lastModified": 1743296961, 261 | "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", 262 | "owner": "nix-community", 263 | "repo": "nixpkgs.lib", 264 | "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", 265 | "type": "github" 266 | }, 267 | "original": { 268 | "owner": "nix-community", 269 | "repo": "nixpkgs.lib", 270 | "type": "github" 271 | } 272 | }, 273 | "nixpkgs_2": { 274 | "locked": { 275 | "lastModified": 1717432640, 276 | "narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=", 277 | "owner": "NixOS", 278 | "repo": "nixpkgs", 279 | "rev": "88269ab3044128b7c2f4c7d68448b2fb50456870", 280 | "type": "github" 281 | }, 282 | "original": { 283 | "owner": "NixOS", 284 | "ref": "release-24.05", 285 | "repo": "nixpkgs", 286 | "type": "github" 287 | } 288 | }, 289 | "nixpkgs_3": { 290 | "locked": { 291 | "lastModified": 1746807397, 292 | "narHash": "sha256-zU2z0jlkJGWLhdNr/8AJSxqK8XD0IlQgHp3VZcP56Aw=", 293 | "owner": "cachix", 294 | "repo": "devenv-nixpkgs", 295 | "rev": "c5208b594838ea8e6cca5997fbf784b7cca1ca90", 296 | "type": "github" 297 | }, 298 | "original": { 299 | "owner": "cachix", 300 | "ref": "rolling", 301 | "repo": "devenv-nixpkgs", 302 | "type": "github" 303 | } 304 | }, 305 | "nixpkgs_4": { 306 | "locked": { 307 | "lastModified": 1747542820, 308 | "narHash": "sha256-GaOZntlJ6gPPbbkTLjbd8BMWaDYafhuuYRNrxCGnPJw=", 309 | "owner": "NixOS", 310 | "repo": "nixpkgs", 311 | "rev": "292fa7d4f6519c074f0a50394dbbe69859bb6043", 312 | "type": "github" 313 | }, 314 | "original": { 315 | "owner": "NixOS", 316 | "ref": "nixos-unstable", 317 | "repo": "nixpkgs", 318 | "type": "github" 319 | } 320 | }, 321 | "nixpkgs_5": { 322 | "locked": { 323 | "lastModified": 1730768919, 324 | "narHash": "sha256-8AKquNnnSaJRXZxc5YmF/WfmxiHX6MMZZasRP6RRQkE=", 325 | "owner": "NixOS", 326 | "repo": "nixpkgs", 327 | "rev": "a04d33c0c3f1a59a2c1cb0c6e34cd24500e5a1dc", 328 | "type": "github" 329 | }, 330 | "original": { 331 | "owner": "NixOS", 332 | "ref": "nixpkgs-unstable", 333 | "repo": "nixpkgs", 334 | "type": "github" 335 | } 336 | }, 337 | "pre-commit-hooks-nix": { 338 | "inputs": { 339 | "flake-compat": "flake-compat_2", 340 | "gitignore": "gitignore_2", 341 | "nixpkgs": "nixpkgs_5" 342 | }, 343 | "locked": { 344 | "lastModified": 1747372754, 345 | "narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=", 346 | "owner": "cachix", 347 | "repo": "pre-commit-hooks.nix", 348 | "rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46", 349 | "type": "github" 350 | }, 351 | "original": { 352 | "owner": "cachix", 353 | "repo": "pre-commit-hooks.nix", 354 | "type": "github" 355 | } 356 | }, 357 | "root": { 358 | "inputs": { 359 | "devenv": "devenv", 360 | "flake-parts": "flake-parts_2", 361 | "nixpkgs": "nixpkgs_4", 362 | "pre-commit-hooks-nix": "pre-commit-hooks-nix" 363 | } 364 | } 365 | }, 366 | "root": "root", 367 | "version": 7 368 | } 369 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Nix flake for development"; 3 | 4 | inputs = { 5 | nixpkgs = { 6 | url = "github:NixOS/nixpkgs/nixos-unstable"; 7 | }; 8 | 9 | devenv = { 10 | url = "github:cachix/devenv"; 11 | }; 12 | 13 | pre-commit-hooks-nix = { 14 | url = "github:cachix/pre-commit-hooks.nix"; 15 | }; 16 | 17 | flake-parts = { 18 | url = "github:hercules-ci/flake-parts"; 19 | }; 20 | }; 21 | 22 | outputs = inputs@{ flake-parts, ... }: 23 | flake-parts.lib.mkFlake { inherit inputs; } { 24 | imports = [ 25 | inputs.devenv.flakeModule 26 | inputs.pre-commit-hooks-nix.flakeModule 27 | ]; 28 | 29 | systems = [ 30 | "x86_64-linux" 31 | "aarch64-linux" 32 | "x86_64-darwin" 33 | "aarch64-darwin" 34 | ]; 35 | 36 | perSystem = { config, self', inputs', pkgs, system, ... }: { 37 | imports = [ 38 | { 39 | _module.args.pkgs = import inputs.nixpkgs { 40 | inherit system; 41 | config.allowUnfree = true; 42 | }; 43 | } 44 | ]; 45 | 46 | pre-commit = { 47 | settings = { 48 | hooks = { 49 | nixpkgs-fmt = { 50 | enable = true; 51 | }; 52 | golangci-lint = { 53 | enable = true; 54 | }; 55 | }; 56 | }; 57 | }; 58 | 59 | devenv = { 60 | shells = { 61 | default = { 62 | languages = { 63 | go = { 64 | enable = true; 65 | package = pkgs.go_1_23; 66 | }; 67 | }; 68 | 69 | packages = with pkgs; [ 70 | bingo 71 | gnumake 72 | nixpkgs-fmt 73 | ]; 74 | 75 | env = { 76 | CGO_ENABLED = "0"; 77 | }; 78 | }; 79 | }; 80 | }; 81 | }; 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/promhippie/hcloud_exporter 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/go-chi/chi/v5 v5.2.1 7 | github.com/hetznercloud/hcloud-go/v2 v2.21.0 8 | github.com/joho/godotenv v1.5.1 9 | github.com/oklog/run v1.1.0 10 | github.com/prometheus/client_golang v1.22.0 11 | github.com/prometheus/exporter-toolkit v0.14.0 12 | github.com/stretchr/testify v1.10.0 13 | github.com/urfave/cli/v3 v3.3.3 14 | ) 15 | 16 | require ( 17 | github.com/beorn7/perks v1.0.1 // indirect 18 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 19 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/jpillora/backoff v1.0.0 // indirect 22 | github.com/mdlayher/socket v0.4.1 // indirect 23 | github.com/mdlayher/vsock v1.2.1 // indirect 24 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 25 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect 26 | github.com/pmezard/go-difflib v1.0.0 // indirect 27 | github.com/prometheus/client_model v0.6.1 // indirect 28 | github.com/prometheus/common v0.62.0 // indirect 29 | github.com/prometheus/procfs v0.15.1 // indirect 30 | golang.org/x/crypto v0.36.0 // indirect 31 | golang.org/x/net v0.38.0 // indirect 32 | golang.org/x/oauth2 v0.24.0 // indirect 33 | golang.org/x/sync v0.12.0 // indirect 34 | golang.org/x/sys v0.31.0 // indirect 35 | golang.org/x/text v0.23.0 // indirect 36 | google.golang.org/protobuf v1.36.5 // indirect 37 | gopkg.in/yaml.v2 v2.4.0 // indirect 38 | gopkg.in/yaml.v3 v3.0.1 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 4 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 6 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= 10 | github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 11 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 12 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 13 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 14 | github.com/hetznercloud/hcloud-go/v2 v2.21.0 h1:wUpQT+fgAxIcdMtFvuCJ78ziqc/VARubpOQPQyj4Q84= 15 | github.com/hetznercloud/hcloud-go/v2 v2.21.0/go.mod h1:WSM7w+9tT86sJTNcF8a/oHljC3HUmQfcLxYsgx6PpSc= 16 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 17 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 18 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 19 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 20 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 21 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 22 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 23 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 24 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 25 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 26 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 27 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 28 | github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= 29 | github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 30 | github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= 31 | github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= 32 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 33 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 34 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= 35 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 36 | github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= 37 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= 38 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 39 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 40 | github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 41 | github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 42 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 43 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 44 | github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= 45 | github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= 46 | github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= 47 | github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA= 48 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 49 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 50 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 51 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 52 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 53 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 54 | github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= 55 | github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= 56 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 57 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 58 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 59 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 60 | golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= 61 | golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 62 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 63 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 64 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 65 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 66 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 67 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 68 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 69 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 70 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 71 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 72 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 73 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 74 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 75 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 76 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 77 | -------------------------------------------------------------------------------- /hack/generate-envvars-docs.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/promhippie/hcloud_exporter/pkg/command" 13 | "github.com/promhippie/hcloud_exporter/pkg/config" 14 | "github.com/urfave/cli/v3" 15 | ) 16 | 17 | type flag struct { 18 | Flag string 19 | Default string 20 | Envs []string 21 | Help string 22 | List bool 23 | } 24 | 25 | func main() { 26 | flags := make([]flag, 0) 27 | 28 | for _, f := range command.RootFlags(config.Load()) { 29 | switch v := f.(type) { 30 | case *cli.StringFlag: 31 | flags = append(flags, flag{ 32 | Flag: v.Name, 33 | Default: v.Value, 34 | Envs: v.Sources.EnvKeys(), 35 | Help: v.Usage, 36 | List: false, 37 | }) 38 | case *cli.IntFlag: 39 | flags = append(flags, flag{ 40 | Flag: v.Name, 41 | Default: strconv.FormatInt(v.Value, 10), 42 | Envs: v.Sources.EnvKeys(), 43 | Help: v.Usage, 44 | List: false, 45 | }) 46 | case *cli.BoolFlag: 47 | flags = append(flags, flag{ 48 | Flag: v.Name, 49 | Default: fmt.Sprintf("%+v", v.Value), 50 | Envs: v.Sources.EnvKeys(), 51 | Help: v.Usage, 52 | List: false, 53 | }) 54 | case *cli.DurationFlag: 55 | flags = append(flags, flag{ 56 | Flag: v.Name, 57 | Default: v.Value.String(), 58 | Envs: v.Sources.EnvKeys(), 59 | Help: v.Usage, 60 | List: false, 61 | }) 62 | case *cli.StringSliceFlag: 63 | flags = append(flags, flag{ 64 | Flag: v.Name, 65 | Default: strings.Join(v.Value, ", "), 66 | Envs: v.Sources.EnvKeys(), 67 | Help: v.Usage, 68 | List: true, 69 | }) 70 | default: 71 | fmt.Printf("unknown type: %s\n", v) 72 | os.Exit(1) 73 | } 74 | } 75 | 76 | f, err := os.Create("docs/partials/envvars.md") 77 | 78 | if err != nil { 79 | fmt.Printf("failed to create file") 80 | os.Exit(1) 81 | } 82 | 83 | defer f.Close() 84 | 85 | last := flags[len(flags)-1] 86 | for _, row := range flags { 87 | f.WriteString( 88 | strings.Join( 89 | row.Envs, 90 | ", ", 91 | ) + "\n", 92 | ) 93 | 94 | f.WriteString(fmt.Sprintf( 95 | ": %s", 96 | row.Help, 97 | )) 98 | 99 | if row.List { 100 | f.WriteString( 101 | ", comma-separated list", 102 | ) 103 | } 104 | 105 | if row.Default != "" { 106 | f.WriteString(fmt.Sprintf( 107 | ", defaults to `%s`", 108 | row.Default, 109 | )) 110 | } 111 | 112 | f.WriteString("\n") 113 | 114 | if row.Flag != last.Flag { 115 | f.WriteString("\n") 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /hack/generate-metrics-docs.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "log/slog" 9 | "os" 10 | "reflect" 11 | "sort" 12 | "strings" 13 | 14 | "github.com/prometheus/client_golang/prometheus" 15 | "github.com/promhippie/hcloud_exporter/pkg/config" 16 | "github.com/promhippie/hcloud_exporter/pkg/exporter" 17 | ) 18 | 19 | type metric struct { 20 | Name string 21 | Help string 22 | Labels []string 23 | } 24 | 25 | func main() { 26 | collectors := make([]*prometheus.Desc, 0) 27 | 28 | collectors = append( 29 | collectors, 30 | exporter.NewFloatingIPCollector(slog.Default(), nil, nil, nil, config.Load().Target).Metrics()..., 31 | ) 32 | 33 | collectors = append( 34 | collectors, 35 | exporter.NewImageCollector(slog.Default(), nil, nil, nil, config.Load().Target).Metrics()..., 36 | ) 37 | 38 | collectors = append( 39 | collectors, 40 | exporter.NewPricingCollector(slog.Default(), nil, nil, nil, config.Load().Target).Metrics()..., 41 | ) 42 | 43 | collectors = append( 44 | collectors, 45 | exporter.NewServerCollector(slog.Default(), nil, nil, nil, config.Load().Target).Metrics()..., 46 | ) 47 | 48 | collectors = append( 49 | collectors, 50 | exporter.NewServerMetricsCollector(slog.Default(), nil, nil, nil, config.Load().Target).Metrics()..., 51 | ) 52 | 53 | collectors = append( 54 | collectors, 55 | exporter.NewSSHKeyCollector(slog.Default(), nil, nil, nil, config.Load().Target).Metrics()..., 56 | ) 57 | 58 | collectors = append( 59 | collectors, 60 | exporter.NewVolumeCollector(slog.Default(), nil, nil, nil, config.Load().Target).Metrics()..., 61 | ) 62 | 63 | metrics := make([]metric, 0) 64 | 65 | metrics = append(metrics, metric{ 66 | Name: "hcloud_request_duration_seconds", 67 | Help: "Histogram of latencies for requests to the api per collector", 68 | Labels: []string{"collector"}, 69 | }) 70 | 71 | metrics = append(metrics, metric{ 72 | Name: "hcloud_request_failures_total", 73 | Help: "Total number of failed requests to the api per collector", 74 | Labels: []string{"collector"}, 75 | }) 76 | 77 | for _, desc := range collectors { 78 | m := metric{ 79 | Name: reflect.ValueOf(desc).Elem().FieldByName("fqName").String(), 80 | Help: reflect.ValueOf(desc).Elem().FieldByName("help").String(), 81 | Labels: make([]string, 0), 82 | } 83 | 84 | labels := reflect.Indirect( 85 | reflect.ValueOf(desc).Elem().FieldByName("variableLabels"), 86 | ).FieldByName("names") 87 | 88 | for i := 0; i < labels.Len(); i++ { 89 | m.Labels = append(m.Labels, labels.Index(i).String()) 90 | } 91 | 92 | metrics = append(metrics, m) 93 | } 94 | 95 | sort.SliceStable(metrics, func(i, j int) bool { 96 | return metrics[i].Name < metrics[j].Name 97 | }) 98 | 99 | f, err := os.Create("docs/partials/metrics.md") 100 | 101 | if err != nil { 102 | fmt.Printf("failed to create file") 103 | os.Exit(1) 104 | } 105 | 106 | defer f.Close() 107 | 108 | last := metrics[len(metrics)-1] 109 | for _, m := range metrics { 110 | f.WriteString(fmt.Sprintf( 111 | "%s{%s}\n", 112 | m.Name, 113 | strings.Join( 114 | m.Labels, 115 | ", ", 116 | ), 117 | )) 118 | 119 | f.WriteString(fmt.Sprintf( 120 | ": %s\n", 121 | m.Help, 122 | )) 123 | 124 | if m.Name != last.Name { 125 | f.WriteString("\n") 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pkg/action/helper.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | // boolP returns a boolean pointer. 4 | func boolP(i bool) *bool { 5 | return &i 6 | } 7 | 8 | // stringP returns a string pointer. 9 | func stringP(i string) *string { 10 | return &i 11 | } 12 | 13 | // slceP returns a slice pointer. 14 | func sliceP(i []string) *[]string { 15 | return &i 16 | } 17 | -------------------------------------------------------------------------------- /pkg/action/metrics.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "fmt" 5 | "log/slog" 6 | 7 | "github.com/prometheus/client_golang/prometheus" 8 | "github.com/prometheus/client_golang/prometheus/collectors" 9 | "github.com/promhippie/hcloud_exporter/pkg/version" 10 | ) 11 | 12 | var ( 13 | registry = prometheus.NewRegistry() 14 | namespace = "hcloud" 15 | ) 16 | 17 | var ( 18 | requestDuration = prometheus.NewHistogramVec( 19 | prometheus.HistogramOpts{ 20 | Namespace: namespace, 21 | Name: "request_duration_seconds", 22 | Help: "Histogram of latencies for requests to the api per collector.", 23 | Buckets: []float64{0.001, 0.01, 0.1, 0.5, 1.0, 2.0, 5.0, 10.0}, 24 | }, 25 | []string{"collector"}, 26 | ) 27 | 28 | requestFailures = prometheus.NewCounterVec( 29 | prometheus.CounterOpts{ 30 | Namespace: namespace, 31 | Name: "request_failures_total", 32 | Help: "Total number of failed requests to the api per collector.", 33 | }, 34 | []string{"collector"}, 35 | ) 36 | ) 37 | 38 | func init() { 39 | registry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{ 40 | Namespace: namespace, 41 | })) 42 | 43 | registry.MustRegister(collectors.NewGoCollector()) 44 | registry.MustRegister(version.Collector(namespace)) 45 | 46 | registry.MustRegister(requestDuration) 47 | registry.MustRegister(requestFailures) 48 | } 49 | 50 | type promLogger struct { 51 | logger *slog.Logger 52 | } 53 | 54 | func (pl promLogger) Println(v ...interface{}) { 55 | pl.logger.Error(fmt.Sprintln(v...)) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/action/server.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log/slog" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "time" 11 | 12 | "github.com/go-chi/chi/v5" 13 | "github.com/hetznercloud/hcloud-go/v2/hcloud" 14 | "github.com/oklog/run" 15 | "github.com/prometheus/client_golang/prometheus/promhttp" 16 | "github.com/prometheus/exporter-toolkit/web" 17 | "github.com/promhippie/hcloud_exporter/pkg/config" 18 | "github.com/promhippie/hcloud_exporter/pkg/exporter" 19 | "github.com/promhippie/hcloud_exporter/pkg/middleware" 20 | "github.com/promhippie/hcloud_exporter/pkg/version" 21 | ) 22 | 23 | // Server handles the server sub-command. 24 | func Server(cfg *config.Config, logger *slog.Logger) error { 25 | logger.Info("Launching HetznerCloud Exporter", 26 | "version", version.String, 27 | "revision", version.Revision, 28 | "date", version.Date, 29 | "go", version.Go, 30 | ) 31 | 32 | token, err := config.Value(cfg.Target.Token) 33 | 34 | if err != nil { 35 | logger.Error("Failed to load token from file", 36 | "err", err, 37 | ) 38 | 39 | return err 40 | } 41 | 42 | client := hcloud.NewClient( 43 | hcloud.WithToken( 44 | token, 45 | ), 46 | hcloud.WithApplication( 47 | "hcloud_exporter", 48 | version.String, 49 | ), 50 | ) 51 | 52 | var gr run.Group 53 | 54 | { 55 | server := &http.Server{ 56 | Addr: cfg.Server.Addr, 57 | Handler: handler(cfg, logger, client), 58 | ReadTimeout: 5 * time.Second, 59 | WriteTimeout: cfg.Server.Timeout, 60 | } 61 | 62 | gr.Add(func() error { 63 | logger.Info("Starting metrics server", 64 | "address", cfg.Server.Addr, 65 | ) 66 | 67 | return web.ListenAndServe( 68 | server, 69 | &web.FlagConfig{ 70 | WebListenAddresses: sliceP([]string{cfg.Server.Addr}), 71 | WebSystemdSocket: boolP(false), 72 | WebConfigFile: stringP(cfg.Server.Web), 73 | }, 74 | logger, 75 | ) 76 | }, func(reason error) { 77 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 78 | defer cancel() 79 | 80 | if err := server.Shutdown(ctx); err != nil { 81 | logger.Error("Failed to shutdown metrics gracefully", 82 | "err", err, 83 | ) 84 | 85 | return 86 | } 87 | 88 | logger.Info("Metrics shutdown gracefully", 89 | "reason", reason, 90 | ) 91 | }) 92 | } 93 | 94 | { 95 | stop := make(chan os.Signal, 1) 96 | 97 | gr.Add(func() error { 98 | signal.Notify(stop, os.Interrupt) 99 | 100 | <-stop 101 | 102 | return nil 103 | }, func(_ error) { 104 | close(stop) 105 | }) 106 | } 107 | 108 | return gr.Run() 109 | } 110 | 111 | func handler(cfg *config.Config, logger *slog.Logger, client *hcloud.Client) *chi.Mux { 112 | mux := chi.NewRouter() 113 | mux.Use(middleware.Recoverer(logger)) 114 | mux.Use(middleware.RealIP) 115 | mux.Use(middleware.Timeout) 116 | mux.Use(middleware.Cache) 117 | 118 | if cfg.Server.Pprof { 119 | mux.Mount("/debug", middleware.Profiler()) 120 | } 121 | 122 | if cfg.Collector.FloatingIPs { 123 | logger.Debug("Floating IP collector registered") 124 | 125 | registry.MustRegister(exporter.NewFloatingIPCollector( 126 | logger, 127 | client, 128 | requestFailures, 129 | requestDuration, 130 | cfg.Target, 131 | )) 132 | } 133 | 134 | if cfg.Collector.Images { 135 | logger.Debug("Image collector registered") 136 | 137 | registry.MustRegister(exporter.NewImageCollector( 138 | logger, 139 | client, 140 | requestFailures, 141 | requestDuration, 142 | cfg.Target, 143 | )) 144 | } 145 | 146 | if cfg.Collector.Pricing { 147 | logger.Debug("Pricing collector registered") 148 | 149 | registry.MustRegister(exporter.NewPricingCollector( 150 | logger, 151 | client, 152 | requestFailures, 153 | requestDuration, 154 | cfg.Target, 155 | )) 156 | } 157 | 158 | if cfg.Collector.Servers { 159 | logger.Debug("Server collector registered") 160 | 161 | registry.MustRegister(exporter.NewServerCollector( 162 | logger, 163 | client, 164 | requestFailures, 165 | requestDuration, 166 | cfg.Target, 167 | )) 168 | } 169 | 170 | if cfg.Collector.ServerMetrics { 171 | logger.Debug("Server metrics collector registered") 172 | 173 | registry.MustRegister(exporter.NewServerMetricsCollector( 174 | logger, 175 | client, 176 | requestFailures, 177 | requestDuration, 178 | cfg.Target, 179 | )) 180 | } 181 | 182 | if cfg.Collector.LoadBalancers { 183 | logger.Debug("Load balancer collector registered") 184 | 185 | registry.MustRegister(exporter.NewLoadBalancerCollector( 186 | logger, 187 | client, 188 | requestFailures, 189 | requestDuration, 190 | cfg.Target, 191 | )) 192 | } 193 | 194 | if cfg.Collector.SSHKeys { 195 | logger.Debug("SSH key collector registered") 196 | 197 | registry.MustRegister(exporter.NewSSHKeyCollector( 198 | logger, 199 | client, 200 | requestFailures, 201 | requestDuration, 202 | cfg.Target, 203 | )) 204 | } 205 | 206 | if cfg.Collector.Volumes { 207 | logger.Debug("Volumes collector registered") 208 | 209 | registry.MustRegister(exporter.NewVolumeCollector( 210 | logger, 211 | client, 212 | requestFailures, 213 | requestDuration, 214 | cfg.Target, 215 | )) 216 | } 217 | 218 | reg := promhttp.HandlerFor( 219 | registry, 220 | promhttp.HandlerOpts{ 221 | ErrorLog: promLogger{logger}, 222 | }, 223 | ) 224 | 225 | mux.NotFound(func(w http.ResponseWriter, r *http.Request) { 226 | http.Redirect(w, r, cfg.Server.Path, http.StatusMovedPermanently) 227 | }) 228 | 229 | mux.Route("/", func(root chi.Router) { 230 | root.Get(cfg.Server.Path, func(w http.ResponseWriter, r *http.Request) { 231 | reg.ServeHTTP(w, r) 232 | }) 233 | 234 | root.Get("/healthz", func(w http.ResponseWriter, _ *http.Request) { 235 | w.Header().Set("Content-Type", "text/plain") 236 | w.WriteHeader(http.StatusOK) 237 | 238 | io.WriteString(w, http.StatusText(http.StatusOK)) 239 | }) 240 | 241 | root.Get("/readyz", func(w http.ResponseWriter, _ *http.Request) { 242 | w.Header().Set("Content-Type", "text/plain") 243 | w.WriteHeader(http.StatusOK) 244 | 245 | io.WriteString(w, http.StatusText(http.StatusOK)) 246 | }) 247 | }) 248 | 249 | return mux 250 | } 251 | -------------------------------------------------------------------------------- /pkg/command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/promhippie/hcloud_exporter/pkg/action" 10 | "github.com/promhippie/hcloud_exporter/pkg/config" 11 | "github.com/promhippie/hcloud_exporter/pkg/version" 12 | "github.com/urfave/cli/v3" 13 | ) 14 | 15 | // Run parses the command line arguments and executes the program. 16 | func Run() error { 17 | cfg := config.Load() 18 | 19 | app := &cli.Command{ 20 | Name: "hcloud_exporter", 21 | Version: version.String, 22 | Usage: "HetznerCloud Exporter", 23 | Authors: []any{ 24 | "Thomas Boerger ", 25 | }, 26 | Flags: RootFlags(cfg), 27 | Commands: []*cli.Command{ 28 | Health(cfg), 29 | }, 30 | Action: func(_ context.Context, _ *cli.Command) error { 31 | logger := setupLogger(cfg) 32 | 33 | if cfg.Target.Token == "" { 34 | logger.Error("Missing required hcloud.token") 35 | return fmt.Errorf("missing required hcloud.token") 36 | } 37 | 38 | return action.Server(cfg, logger) 39 | }, 40 | } 41 | 42 | cli.HelpFlag = &cli.BoolFlag{ 43 | Name: "help", 44 | Aliases: []string{"h"}, 45 | Usage: "Show the help, so what you see now", 46 | } 47 | 48 | cli.VersionFlag = &cli.BoolFlag{ 49 | Name: "version", 50 | Aliases: []string{"v"}, 51 | Usage: "Print the current version of that tool", 52 | } 53 | 54 | return app.Run(context.Background(), os.Args) 55 | } 56 | 57 | // RootFlags defines the available root flags. 58 | func RootFlags(cfg *config.Config) []cli.Flag { 59 | return []cli.Flag{ 60 | &cli.StringFlag{ 61 | Name: "log.level", 62 | Value: "info", 63 | Usage: "Only log messages with given severity", 64 | Sources: cli.EnvVars("HCLOUD_EXPORTER_LOG_LEVEL"), 65 | Destination: &cfg.Logs.Level, 66 | }, 67 | &cli.BoolFlag{ 68 | Name: "log.pretty", 69 | Value: false, 70 | Usage: "Enable pretty messages for logging", 71 | Sources: cli.EnvVars("HCLOUD_EXPORTER_LOG_PRETTY"), 72 | Destination: &cfg.Logs.Pretty, 73 | }, 74 | &cli.StringFlag{ 75 | Name: "web.address", 76 | Value: "0.0.0.0:9501", 77 | Usage: "Address to bind the metrics server", 78 | Sources: cli.EnvVars("HCLOUD_EXPORTER_WEB_ADDRESS"), 79 | Destination: &cfg.Server.Addr, 80 | }, 81 | &cli.StringFlag{ 82 | Name: "web.path", 83 | Value: "/metrics", 84 | Usage: "Path to bind the metrics server", 85 | Sources: cli.EnvVars("HCLOUD_EXPORTER_WEB_PATH"), 86 | Destination: &cfg.Server.Path, 87 | }, 88 | &cli.BoolFlag{ 89 | Name: "web.debug", 90 | Value: false, 91 | Usage: "Enable pprof debugging for server", 92 | Sources: cli.EnvVars("HCLOUD_EXPORTER_WEB_PPROF"), 93 | Destination: &cfg.Server.Pprof, 94 | }, 95 | &cli.DurationFlag{ 96 | Name: "web.timeout", 97 | Value: 10 * time.Second, 98 | Usage: "Server metrics endpoint timeout", 99 | Sources: cli.EnvVars("HCLOUD_EXPORTER_WEB_TIMEOUT"), 100 | Destination: &cfg.Server.Timeout, 101 | }, 102 | &cli.StringFlag{ 103 | Name: "web.config", 104 | Value: "", 105 | Usage: "Path to web-config file", 106 | Sources: cli.EnvVars("HCLOUD_EXPORTER_WEB_CONFIG"), 107 | Destination: &cfg.Server.Web, 108 | }, 109 | &cli.DurationFlag{ 110 | Name: "request.timeout", 111 | Value: 10 * time.Second, 112 | Usage: "Request timeout as duration", 113 | Sources: cli.EnvVars("HCLOUD_EXPORTER_REQUEST_TIMEOUT"), 114 | Destination: &cfg.Target.Timeout, 115 | }, 116 | &cli.StringFlag{ 117 | Name: "hcloud.token", 118 | Value: "", 119 | Usage: "Access token for the HetznerCloud API", 120 | Sources: cli.EnvVars("HCLOUD_EXPORTER_TOKEN"), 121 | Destination: &cfg.Target.Token, 122 | }, 123 | &cli.BoolFlag{ 124 | Name: "collector.floating-ips", 125 | Value: true, 126 | Usage: "Enable collector for floating IPs", 127 | Sources: cli.EnvVars("HCLOUD_EXPORTER_COLLECTOR_FLOATING_IPS"), 128 | Destination: &cfg.Collector.FloatingIPs, 129 | }, 130 | &cli.BoolFlag{ 131 | Name: "collector.images", 132 | Value: true, 133 | Usage: "Enable collector for images", 134 | Sources: cli.EnvVars("HCLOUD_EXPORTER_COLLECTOR_IMAGES"), 135 | Destination: &cfg.Collector.Images, 136 | }, 137 | &cli.BoolFlag{ 138 | Name: "collector.pricing", 139 | Value: true, 140 | Usage: "Enable collector for pricing", 141 | Sources: cli.EnvVars("HCLOUD_EXPORTER_COLLECTOR_PRICING"), 142 | Destination: &cfg.Collector.Pricing, 143 | }, 144 | &cli.BoolFlag{ 145 | Name: "collector.servers", 146 | Value: true, 147 | Usage: "Enable collector for servers", 148 | Sources: cli.EnvVars("HCLOUD_EXPORTER_COLLECTOR_SERVERS"), 149 | Destination: &cfg.Collector.Servers, 150 | }, 151 | &cli.BoolFlag{ 152 | Name: "collector.server-metrics", 153 | Value: false, 154 | Usage: "Enable collector for server metrics", 155 | Sources: cli.EnvVars("HCLOUD_EXPORTER_COLLECTOR_SERVER_METRICS"), 156 | Destination: &cfg.Collector.ServerMetrics, 157 | }, 158 | &cli.BoolFlag{ 159 | Name: "collector.load-balancers", 160 | Value: true, 161 | Usage: "Enable collector for load balancers", 162 | Sources: cli.EnvVars("HCLOUD_EXPORTER_COLLECTOR_LOAD_BALANCERS"), 163 | Destination: &cfg.Collector.LoadBalancers, 164 | }, 165 | &cli.BoolFlag{ 166 | Name: "collector.ssh-keys", 167 | Value: true, 168 | Usage: "Enable collector for SSH keys", 169 | Sources: cli.EnvVars("HCLOUD_EXPORTER_COLLECTOR_SSH_KEYS"), 170 | Destination: &cfg.Collector.SSHKeys, 171 | }, 172 | &cli.BoolFlag{ 173 | Name: "collector.volumes", 174 | Value: false, 175 | Usage: "Enable collector for volumes", 176 | Sources: cli.EnvVars("HCLOUD_EXPORTER_COLLECTOR_VOLUMES"), 177 | Destination: &cfg.Collector.Volumes, 178 | }, 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /pkg/command/health.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/promhippie/hcloud_exporter/pkg/config" 9 | "github.com/urfave/cli/v3" 10 | ) 11 | 12 | // Health provides the sub-command to perform a health check. 13 | func Health(cfg *config.Config) *cli.Command { 14 | return &cli.Command{ 15 | Name: "health", 16 | Usage: "Perform health checks", 17 | Flags: HealthFlags(cfg), 18 | Action: func(_ context.Context, _ *cli.Command) error { 19 | logger := setupLogger(cfg) 20 | 21 | resp, err := http.Get( 22 | fmt.Sprintf( 23 | "http://%s/healthz", 24 | cfg.Server.Addr, 25 | ), 26 | ) 27 | 28 | if err != nil { 29 | logger.Error("Failed to request health check", 30 | "err", err, 31 | ) 32 | 33 | return err 34 | } 35 | 36 | defer resp.Body.Close() 37 | 38 | if resp.StatusCode != 200 { 39 | logger.Error("Health check seems to be in bad state", 40 | "err", err, 41 | "code", resp.StatusCode, 42 | ) 43 | 44 | return err 45 | } 46 | 47 | logger.Debug("Health check seems to be fine", 48 | "code", resp.StatusCode, 49 | ) 50 | 51 | return nil 52 | }, 53 | } 54 | } 55 | 56 | // HealthFlags defines the available health flags. 57 | func HealthFlags(cfg *config.Config) []cli.Flag { 58 | return []cli.Flag{ 59 | &cli.StringFlag{ 60 | Name: "web.address", 61 | Value: "0.0.0.0:9501", 62 | Usage: "Address to bind the metrics server", 63 | Sources: cli.EnvVars("HCLOUD_EXPORTER_WEB_ADDRESS"), 64 | Destination: &cfg.Server.Addr, 65 | }, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/command/setup.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | "strings" 7 | 8 | "github.com/promhippie/hcloud_exporter/pkg/config" 9 | ) 10 | 11 | func setupLogger(cfg *config.Config) *slog.Logger { 12 | if cfg.Logs.Pretty { 13 | return slog.New( 14 | slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ 15 | Level: loggerLevel(cfg), 16 | }), 17 | ) 18 | } 19 | 20 | return slog.New( 21 | slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ 22 | Level: loggerLevel(cfg), 23 | }), 24 | ) 25 | } 26 | 27 | func loggerLevel(cfg *config.Config) slog.Leveler { 28 | switch strings.ToLower(cfg.Logs.Level) { 29 | case "error": 30 | return slog.LevelError 31 | case "warn": 32 | return slog.LevelWarn 33 | case "info": 34 | return slog.LevelInfo 35 | case "debug": 36 | return slog.LevelDebug 37 | } 38 | 39 | return slog.LevelInfo 40 | } 41 | -------------------------------------------------------------------------------- /pkg/command/setup_test.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/promhippie/hcloud_exporter/pkg/config" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSetupLogger(t *testing.T) { 11 | logger := setupLogger(config.Load()) 12 | assert.NotNil(t, logger) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // Server defines the general server configuration. 12 | type Server struct { 13 | Addr string 14 | Path string 15 | Timeout time.Duration 16 | Web string 17 | Pprof bool 18 | } 19 | 20 | // Logs defines the level and color for log configuration. 21 | type Logs struct { 22 | Level string 23 | Pretty bool 24 | } 25 | 26 | // Target defines the target specific configuration. 27 | type Target struct { 28 | Token string 29 | Timeout time.Duration 30 | } 31 | 32 | // Collector defines the collector specific configuration. 33 | type Collector struct { 34 | FloatingIPs bool 35 | Images bool 36 | Pricing bool 37 | Servers bool 38 | ServerMetrics bool 39 | LoadBalancers bool 40 | SSHKeys bool 41 | Volumes bool 42 | } 43 | 44 | // Config is a combination of all available configurations. 45 | type Config struct { 46 | Server Server 47 | Logs Logs 48 | Target Target 49 | Collector Collector 50 | } 51 | 52 | // Load initializes a default configuration struct. 53 | func Load() *Config { 54 | return &Config{} 55 | } 56 | 57 | // Value returns the config value based on a DSN. 58 | func Value(val string) (string, error) { 59 | if strings.HasPrefix(val, "file://") { 60 | content, err := os.ReadFile( 61 | strings.TrimPrefix(val, "file://"), 62 | ) 63 | 64 | if err != nil { 65 | return "", fmt.Errorf("failed to parse secret file: %w", err) 66 | } 67 | 68 | return string(content), nil 69 | } 70 | 71 | if strings.HasPrefix(val, "base64://") { 72 | content, err := base64.StdEncoding.DecodeString( 73 | strings.TrimPrefix(val, "base64://"), 74 | ) 75 | 76 | if err != nil { 77 | return "", fmt.Errorf("failed to parse base64 value: %w", err) 78 | } 79 | 80 | return string(content), nil 81 | } 82 | 83 | return val, nil 84 | } 85 | -------------------------------------------------------------------------------- /pkg/exporter/floating_ip.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/hetznercloud/hcloud-go/v2/hcloud" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/promhippie/hcloud_exporter/pkg/config" 12 | ) 13 | 14 | // FloatingIPCollector collects metrics about the floating IPs. 15 | type FloatingIPCollector struct { 16 | client *hcloud.Client 17 | logger *slog.Logger 18 | failures *prometheus.CounterVec 19 | duration *prometheus.HistogramVec 20 | config config.Target 21 | 22 | Active *prometheus.Desc 23 | } 24 | 25 | // NewFloatingIPCollector returns a new FloatingIPCollector. 26 | func NewFloatingIPCollector(logger *slog.Logger, client *hcloud.Client, failures *prometheus.CounterVec, duration *prometheus.HistogramVec, cfg config.Target) *FloatingIPCollector { 27 | if failures != nil { 28 | failures.WithLabelValues("floating_ip").Add(0) 29 | } 30 | 31 | labels := []string{"id", "server", "location", "type", "ip"} 32 | return &FloatingIPCollector{ 33 | client: client, 34 | logger: logger.With("collector", "floating-ip"), 35 | failures: failures, 36 | duration: duration, 37 | config: cfg, 38 | 39 | Active: prometheus.NewDesc( 40 | "hcloud_floating_ip_active", 41 | "If 1 the floating IP is used by a server, 0 otherwise", 42 | labels, 43 | nil, 44 | ), 45 | } 46 | } 47 | 48 | // Metrics simply returns the list metric descriptors for generating a documentation. 49 | func (c *FloatingIPCollector) Metrics() []*prometheus.Desc { 50 | return []*prometheus.Desc{ 51 | c.Active, 52 | } 53 | } 54 | 55 | // Describe sends the super-set of all possible descriptors of metrics collected by this Collector. 56 | func (c *FloatingIPCollector) Describe(ch chan<- *prometheus.Desc) { 57 | ch <- c.Active 58 | } 59 | 60 | // Collect is called by the Prometheus registry when collecting metrics. 61 | func (c *FloatingIPCollector) Collect(ch chan<- prometheus.Metric) { 62 | ctx, cancel := context.WithTimeout(context.Background(), c.config.Timeout) 63 | defer cancel() 64 | 65 | now := time.Now() 66 | ips, err := c.client.FloatingIP.All(ctx) 67 | 68 | if err != nil { 69 | c.logger.Error("Failed to fetch floating IPs", 70 | "err", err, 71 | ) 72 | 73 | c.failures.WithLabelValues("floating_ip").Inc() 74 | return 75 | } 76 | 77 | c.logger.Debug("Fetched floating IPs", 78 | "count", len(ips), 79 | ) 80 | 81 | for _, ip := range ips { 82 | var ( 83 | active float64 84 | name string 85 | ) 86 | 87 | if ip.Server != nil { 88 | active = 1.0 89 | name = ip.Server.Name 90 | } 91 | 92 | labels := []string{ 93 | strconv.FormatInt(ip.ID, 10), 94 | name, 95 | ip.HomeLocation.Name, 96 | string(ip.Type), 97 | ip.IP.String(), 98 | } 99 | 100 | ch <- prometheus.MustNewConstMetric( 101 | c.Active, 102 | prometheus.GaugeValue, 103 | active, 104 | labels..., 105 | ) 106 | } 107 | 108 | c.logger.Debug("Processed floating IP collector", 109 | "duration", time.Since(now), 110 | ) 111 | 112 | c.duration.WithLabelValues("floating_ip").Observe(time.Since(now).Seconds()) 113 | } 114 | -------------------------------------------------------------------------------- /pkg/exporter/image.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/hetznercloud/hcloud-go/v2/hcloud" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/promhippie/hcloud_exporter/pkg/config" 12 | ) 13 | 14 | // ImageCollector collects metrics about the images. 15 | type ImageCollector struct { 16 | client *hcloud.Client 17 | logger *slog.Logger 18 | failures *prometheus.CounterVec 19 | duration *prometheus.HistogramVec 20 | config config.Target 21 | 22 | Active *prometheus.Desc 23 | ImageSize *prometheus.Desc 24 | DiskSize *prometheus.Desc 25 | Created *prometheus.Desc 26 | Deprecated *prometheus.Desc 27 | } 28 | 29 | // NewImageCollector returns a new ImageCollector. 30 | func NewImageCollector(logger *slog.Logger, client *hcloud.Client, failures *prometheus.CounterVec, duration *prometheus.HistogramVec, cfg config.Target) *ImageCollector { 31 | if failures != nil { 32 | failures.WithLabelValues("image").Add(0) 33 | } 34 | 35 | labels := []string{"id", "name", "type", "server", "flavor", "version"} 36 | return &ImageCollector{ 37 | client: client, 38 | logger: logger.With("collector", "image"), 39 | failures: failures, 40 | duration: duration, 41 | config: cfg, 42 | 43 | Active: prometheus.NewDesc( 44 | "hcloud_image_active", 45 | "If 1 the image is used by a server, 0 otherwise", 46 | labels, 47 | nil, 48 | ), 49 | ImageSize: prometheus.NewDesc( 50 | "hcloud_image_size_bytes", 51 | "Size of the image in bytes", 52 | labels, 53 | nil, 54 | ), 55 | DiskSize: prometheus.NewDesc( 56 | "hcloud_image_disk_bytes", 57 | "Size if the disk for the image in bytes", 58 | labels, 59 | nil, 60 | ), 61 | Created: prometheus.NewDesc( 62 | "hcloud_image_created_timestamp", 63 | "Timestamp when the image have been created", 64 | labels, 65 | nil, 66 | ), 67 | Deprecated: prometheus.NewDesc( 68 | "hcloud_image_deprecated_timestamp", 69 | "Timestamp when the image will be deprecated, 0 if not deprecated", 70 | labels, 71 | nil, 72 | ), 73 | } 74 | } 75 | 76 | // Metrics simply returns the list metric descriptors for generating a documentation. 77 | func (c *ImageCollector) Metrics() []*prometheus.Desc { 78 | return []*prometheus.Desc{ 79 | c.Active, 80 | c.ImageSize, 81 | c.DiskSize, 82 | c.Created, 83 | c.Deprecated, 84 | } 85 | } 86 | 87 | // Describe sends the super-set of all possible descriptors of metrics collected by this Collector. 88 | func (c *ImageCollector) Describe(ch chan<- *prometheus.Desc) { 89 | ch <- c.Active 90 | ch <- c.ImageSize 91 | ch <- c.DiskSize 92 | ch <- c.Created 93 | ch <- c.Deprecated 94 | } 95 | 96 | // Collect is called by the Prometheus registry when collecting metrics. 97 | func (c *ImageCollector) Collect(ch chan<- prometheus.Metric) { 98 | ctx, cancel := context.WithTimeout(context.Background(), c.config.Timeout) 99 | defer cancel() 100 | 101 | now := time.Now() 102 | images, err := c.client.Image.All(ctx) 103 | 104 | if err != nil { 105 | c.logger.Error("Failed to fetch images", 106 | "err", err, 107 | ) 108 | 109 | c.failures.WithLabelValues("image").Inc() 110 | return 111 | } 112 | 113 | c.logger.Debug("Fetched images", 114 | "count", len(images), 115 | ) 116 | 117 | for _, image := range images { 118 | var ( 119 | active float64 120 | name string 121 | deprecated float64 122 | ) 123 | 124 | if image.CreatedFrom != nil { 125 | name = image.CreatedFrom.Name 126 | } 127 | 128 | if image.BoundTo != nil && image.BoundTo.Name != "" { 129 | active = 1.0 130 | name = image.BoundTo.Name 131 | } 132 | 133 | labels := []string{ 134 | strconv.FormatInt(image.ID, 10), 135 | image.Name, 136 | string(image.Type), 137 | name, 138 | image.OSFlavor, 139 | image.OSVersion, 140 | } 141 | 142 | ch <- prometheus.MustNewConstMetric( 143 | c.Active, 144 | prometheus.GaugeValue, 145 | active, 146 | labels..., 147 | ) 148 | 149 | ch <- prometheus.MustNewConstMetric( 150 | c.ImageSize, 151 | prometheus.GaugeValue, 152 | float64(image.ImageSize*1024*1024), 153 | labels..., 154 | ) 155 | 156 | ch <- prometheus.MustNewConstMetric( 157 | c.DiskSize, 158 | prometheus.GaugeValue, 159 | float64(image.DiskSize*1024*1024), 160 | labels..., 161 | ) 162 | 163 | ch <- prometheus.MustNewConstMetric( 164 | c.Created, 165 | prometheus.GaugeValue, 166 | float64(image.Created.Unix()), 167 | labels..., 168 | ) 169 | 170 | if !image.Deprecated.IsZero() { 171 | deprecated = float64(image.Deprecated.Unix()) 172 | } 173 | 174 | ch <- prometheus.MustNewConstMetric( 175 | c.Deprecated, 176 | prometheus.GaugeValue, 177 | deprecated, 178 | labels..., 179 | ) 180 | } 181 | 182 | c.logger.Debug("Processed image collector", 183 | "duration", time.Since(now), 184 | ) 185 | 186 | c.duration.WithLabelValues("image").Observe(time.Since(now).Seconds()) 187 | } 188 | -------------------------------------------------------------------------------- /pkg/exporter/server.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/hetznercloud/hcloud-go/v2/hcloud" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/promhippie/hcloud_exporter/pkg/config" 12 | ) 13 | 14 | // ServerCollector collects metrics about the servers. 15 | type ServerCollector struct { 16 | client *hcloud.Client 17 | logger *slog.Logger 18 | failures *prometheus.CounterVec 19 | duration *prometheus.HistogramVec 20 | config config.Target 21 | 22 | Running *prometheus.Desc 23 | Created *prometheus.Desc 24 | IncludedTraffic *prometheus.Desc 25 | OutgoingTraffic *prometheus.Desc 26 | IngoingTraffic *prometheus.Desc 27 | Cores *prometheus.Desc 28 | Memory *prometheus.Desc 29 | Disk *prometheus.Desc 30 | Backup *prometheus.Desc 31 | PriceHourly *prometheus.Desc 32 | PriceMonthly *prometheus.Desc 33 | } 34 | 35 | // NewServerCollector returns a new ServerCollector. 36 | func NewServerCollector(logger *slog.Logger, client *hcloud.Client, failures *prometheus.CounterVec, duration *prometheus.HistogramVec, cfg config.Target) *ServerCollector { 37 | if failures != nil { 38 | failures.WithLabelValues("server").Add(0) 39 | } 40 | 41 | labels := []string{"id", "name", "datacenter"} 42 | pricingLabels := append(labels, "vat") 43 | return &ServerCollector{ 44 | client: client, 45 | logger: logger.With("collector", "server"), 46 | failures: failures, 47 | duration: duration, 48 | config: cfg, 49 | 50 | Running: prometheus.NewDesc( 51 | "hcloud_server_running", 52 | "If 1 the server is running, 0 otherwise", 53 | labels, 54 | nil, 55 | ), 56 | Created: prometheus.NewDesc( 57 | "hcloud_server_created_timestamp", 58 | "Timestamp when the server have been created", 59 | labels, 60 | nil, 61 | ), 62 | IncludedTraffic: prometheus.NewDesc( 63 | "hcloud_server_included_traffic_bytes", 64 | "Included traffic for the server in bytes", 65 | labels, 66 | nil, 67 | ), 68 | OutgoingTraffic: prometheus.NewDesc( 69 | "hcloud_server_outgoing_traffic_bytes", 70 | "Outgoing traffic from the server in bytes", 71 | labels, 72 | nil, 73 | ), 74 | IngoingTraffic: prometheus.NewDesc( 75 | "hcloud_server_incoming_traffic_bytes", 76 | "Ingoing traffic to the server in bytes", 77 | labels, 78 | nil, 79 | ), 80 | Cores: prometheus.NewDesc( 81 | "hcloud_server_cores", 82 | "Server number of cores", 83 | labels, 84 | nil, 85 | ), 86 | Memory: prometheus.NewDesc( 87 | "hcloud_server_memory_bytes", 88 | "Server memory in bytes", 89 | labels, 90 | nil, 91 | ), 92 | Disk: prometheus.NewDesc( 93 | "hcloud_server_disk_bytes", 94 | "Server disk in bytes", 95 | labels, 96 | nil, 97 | ), 98 | Backup: prometheus.NewDesc( 99 | "hcloud_server_backup", 100 | "If 1 server backups are enabled, 0 otherwise", 101 | labels, 102 | nil, 103 | ), 104 | PriceHourly: prometheus.NewDesc( 105 | "hcloud_server_price_hourly", 106 | "Price of the server billed hourly in €", 107 | pricingLabels, 108 | nil, 109 | ), 110 | PriceMonthly: prometheus.NewDesc( 111 | "hcloud_server_price_monthly", 112 | "Price of the server billed monthly in €", 113 | pricingLabels, 114 | nil, 115 | ), 116 | } 117 | } 118 | 119 | // Metrics simply returns the list metric descriptors for generating a documentation. 120 | func (c *ServerCollector) Metrics() []*prometheus.Desc { 121 | return []*prometheus.Desc{ 122 | c.Running, 123 | c.Created, 124 | c.IncludedTraffic, 125 | c.OutgoingTraffic, 126 | c.IngoingTraffic, 127 | c.Cores, 128 | c.Memory, 129 | c.Disk, 130 | c.Backup, 131 | c.PriceHourly, 132 | c.PriceMonthly, 133 | } 134 | } 135 | 136 | // Describe sends the super-set of all possible descriptors of metrics collected by this Collector. 137 | func (c *ServerCollector) Describe(ch chan<- *prometheus.Desc) { 138 | ch <- c.Running 139 | ch <- c.Created 140 | ch <- c.IncludedTraffic 141 | ch <- c.OutgoingTraffic 142 | ch <- c.IngoingTraffic 143 | ch <- c.Cores 144 | ch <- c.Memory 145 | ch <- c.Disk 146 | ch <- c.Backup 147 | ch <- c.PriceHourly 148 | ch <- c.PriceMonthly 149 | } 150 | 151 | // Collect is called by the Prometheus registry when collecting metrics. 152 | func (c *ServerCollector) Collect(ch chan<- prometheus.Metric) { 153 | ctx, cancel := context.WithTimeout(context.Background(), c.config.Timeout) 154 | defer cancel() 155 | 156 | now := time.Now() 157 | servers, err := c.client.Server.All(ctx) 158 | 159 | if err != nil { 160 | c.logger.Error("Failed to fetch servers", 161 | "err", err, 162 | ) 163 | 164 | c.failures.WithLabelValues("server").Inc() 165 | return 166 | } 167 | 168 | c.logger.Debug("Fetched servers", 169 | "count", len(servers), 170 | ) 171 | 172 | for _, server := range servers { 173 | var ( 174 | running float64 175 | ) 176 | 177 | labels := []string{ 178 | strconv.FormatInt(server.ID, 10), 179 | server.Name, 180 | server.Datacenter.Name, 181 | } 182 | 183 | if server.Status == "running" { 184 | running = 1.0 185 | } 186 | 187 | ch <- prometheus.MustNewConstMetric( 188 | c.Running, 189 | prometheus.GaugeValue, 190 | running, 191 | labels..., 192 | ) 193 | 194 | ch <- prometheus.MustNewConstMetric( 195 | c.Created, 196 | prometheus.GaugeValue, 197 | float64(server.Created.Unix()), 198 | labels..., 199 | ) 200 | 201 | ch <- prometheus.MustNewConstMetric( 202 | c.IncludedTraffic, 203 | prometheus.GaugeValue, 204 | float64(server.IncludedTraffic), 205 | labels..., 206 | ) 207 | 208 | ch <- prometheus.MustNewConstMetric( 209 | c.OutgoingTraffic, 210 | prometheus.GaugeValue, 211 | float64(server.OutgoingTraffic), 212 | labels..., 213 | ) 214 | 215 | ch <- prometheus.MustNewConstMetric( 216 | c.IngoingTraffic, 217 | prometheus.GaugeValue, 218 | float64(server.IngoingTraffic), 219 | labels..., 220 | ) 221 | 222 | ch <- prometheus.MustNewConstMetric( 223 | c.Cores, 224 | prometheus.GaugeValue, 225 | float64(server.ServerType.Cores), 226 | labels..., 227 | ) 228 | 229 | ch <- prometheus.MustNewConstMetric( 230 | c.Memory, 231 | prometheus.GaugeValue, 232 | float64(server.ServerType.Memory*1024*1024), 233 | labels..., 234 | ) 235 | 236 | ch <- prometheus.MustNewConstMetric( 237 | c.Disk, 238 | prometheus.GaugeValue, 239 | float64(server.ServerType.Disk*1024*1024), 240 | labels..., 241 | ) 242 | 243 | backupEnabled := 0.0 244 | if server.BackupWindow != "" { 245 | backupEnabled = 1.0 246 | } 247 | 248 | ch <- prometheus.MustNewConstMetric( 249 | c.Backup, 250 | prometheus.GaugeValue, 251 | backupEnabled, 252 | labels..., 253 | ) 254 | 255 | labelsNet := append(labels, "net") 256 | labelsGross := append(labels, "gross") 257 | 258 | for _, pricing := range server.ServerType.Pricings { 259 | if server.Datacenter.Location.Name == pricing.Location.Name { 260 | if net, err := strconv.ParseFloat(pricing.Hourly.Net, 64); err != nil { 261 | c.logger.Error("Failed to parse hourly server type net costs", 262 | "name", server.Name, 263 | "err", err, 264 | ) 265 | 266 | c.failures.WithLabelValues("server").Inc() 267 | } else { 268 | ch <- prometheus.MustNewConstMetric( 269 | c.PriceHourly, 270 | prometheus.GaugeValue, 271 | net, 272 | labelsNet..., 273 | ) 274 | } 275 | 276 | if gross, err := strconv.ParseFloat(pricing.Hourly.Gross, 64); err != nil { 277 | c.logger.Error("Failed to parse hourly server type gross costs", 278 | "name", server.Name, 279 | "err", err, 280 | ) 281 | 282 | c.failures.WithLabelValues("server").Inc() 283 | } else { 284 | ch <- prometheus.MustNewConstMetric( 285 | c.PriceHourly, 286 | prometheus.GaugeValue, 287 | gross, 288 | labelsGross..., 289 | ) 290 | } 291 | 292 | if net, err := strconv.ParseFloat(pricing.Monthly.Net, 64); err != nil { 293 | c.logger.Error("Failed to parse monthly server type net costs", 294 | "name", server.Name, 295 | "err", err, 296 | ) 297 | 298 | c.failures.WithLabelValues("server").Inc() 299 | } else { 300 | ch <- prometheus.MustNewConstMetric( 301 | c.PriceMonthly, 302 | prometheus.GaugeValue, 303 | net, 304 | labelsNet..., 305 | ) 306 | } 307 | 308 | if gross, err := strconv.ParseFloat(pricing.Monthly.Gross, 64); err != nil { 309 | c.logger.Error("Failed to parse monthly server type gross costs", 310 | "name", server.Name, 311 | "err", err, 312 | ) 313 | 314 | c.failures.WithLabelValues("server").Inc() 315 | } else { 316 | ch <- prometheus.MustNewConstMetric( 317 | c.PriceMonthly, 318 | prometheus.GaugeValue, 319 | gross, 320 | labelsGross..., 321 | ) 322 | } 323 | } 324 | } 325 | } 326 | 327 | c.logger.Debug("Processed server collector", 328 | "duration", time.Since(now), 329 | ) 330 | 331 | c.duration.WithLabelValues("server").Observe(time.Since(now).Seconds()) 332 | } 333 | -------------------------------------------------------------------------------- /pkg/exporter/server_metrics.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "strconv" 7 | "sync" 8 | "time" 9 | 10 | "github.com/hetznercloud/hcloud-go/v2/hcloud" 11 | "github.com/prometheus/client_golang/prometheus" 12 | "github.com/promhippie/hcloud_exporter/pkg/config" 13 | ) 14 | 15 | // ServerMetricsCollector collects the servers metrics. 16 | type ServerMetricsCollector struct { 17 | client *hcloud.Client 18 | logger *slog.Logger 19 | failures *prometheus.CounterVec 20 | duration *prometheus.HistogramVec 21 | config config.Target 22 | 23 | CPU *prometheus.Desc 24 | DiskReadIops *prometheus.Desc 25 | DiskWriteIops *prometheus.Desc 26 | DiskReadBps *prometheus.Desc 27 | DiskWriteBps *prometheus.Desc 28 | NetworkInPps *prometheus.Desc 29 | NetworkOutPps *prometheus.Desc 30 | NetworkInBps *prometheus.Desc 31 | NetworkOutBps *prometheus.Desc 32 | } 33 | 34 | // NewServerMetricsCollector returns a new ServerMetricsCollector. 35 | func NewServerMetricsCollector(logger *slog.Logger, client *hcloud.Client, failures *prometheus.CounterVec, duration *prometheus.HistogramVec, cfg config.Target) *ServerMetricsCollector { 36 | if failures != nil { 37 | failures.WithLabelValues("server_metrics").Add(0) 38 | } 39 | 40 | labels := []string{"id", "name", "datacenter"} 41 | diskLabels := append(labels, "disk") 42 | networkLabels := append(labels, "interface") 43 | return &ServerMetricsCollector{ 44 | client: client, 45 | logger: logger.With("collector", "server-metrics"), 46 | failures: failures, 47 | duration: duration, 48 | config: cfg, 49 | 50 | CPU: prometheus.NewDesc( 51 | "hcloud_server_metrics_cpu", 52 | "Server CPU usage metric", 53 | labels, 54 | nil, 55 | ), 56 | 57 | DiskReadIops: prometheus.NewDesc( 58 | "hcloud_server_metrics_disk_read_iops", 59 | "Server disk read iop/s metric", 60 | diskLabels, 61 | nil, 62 | ), 63 | 64 | DiskWriteIops: prometheus.NewDesc( 65 | "hcloud_server_metrics_disk_write_iops", 66 | "Server disk write iop/s metric", 67 | diskLabels, 68 | nil, 69 | ), 70 | 71 | DiskReadBps: prometheus.NewDesc( 72 | "hcloud_server_metrics_disk_read_bps", 73 | "Server disk write bytes/s metric", 74 | diskLabels, 75 | nil, 76 | ), 77 | 78 | DiskWriteBps: prometheus.NewDesc( 79 | "hcloud_server_metrics_disk_write_bps", 80 | "Server disk write bytes/s metric", 81 | diskLabels, 82 | nil, 83 | ), 84 | 85 | NetworkInPps: prometheus.NewDesc( 86 | "hcloud_server_metrics_network_in_pps", 87 | "Server network incoming packets/s metric", 88 | networkLabels, 89 | nil, 90 | ), 91 | 92 | NetworkOutPps: prometheus.NewDesc( 93 | "hcloud_server_metrics_network_out_pps", 94 | "Server network outgoing packets/s metric", 95 | networkLabels, 96 | nil, 97 | ), 98 | 99 | NetworkInBps: prometheus.NewDesc( 100 | "hcloud_server_metrics_network_in_bps", 101 | "Server network incoming bytes/s metric", 102 | networkLabels, 103 | nil, 104 | ), 105 | 106 | NetworkOutBps: prometheus.NewDesc( 107 | "hcloud_server_metrics_network_out_bps", 108 | "Server network outgoing bytes/s metric", 109 | networkLabels, 110 | nil, 111 | ), 112 | } 113 | } 114 | 115 | // Metrics simply returns the list metric descriptors for generating a documentation. 116 | func (c *ServerMetricsCollector) Metrics() []*prometheus.Desc { 117 | return []*prometheus.Desc{ 118 | c.CPU, 119 | c.DiskReadIops, 120 | c.DiskWriteIops, 121 | c.DiskReadBps, 122 | c.DiskWriteBps, 123 | c.NetworkInPps, 124 | c.NetworkOutPps, 125 | c.NetworkInBps, 126 | c.NetworkOutBps, 127 | } 128 | } 129 | 130 | // Describe sends the super-set of all possible descriptors of metrics collected by this Collector. 131 | func (c *ServerMetricsCollector) Describe(ch chan<- *prometheus.Desc) { 132 | ch <- c.CPU 133 | ch <- c.DiskReadIops 134 | ch <- c.DiskWriteIops 135 | ch <- c.DiskReadBps 136 | ch <- c.DiskWriteBps 137 | ch <- c.NetworkInPps 138 | ch <- c.NetworkOutPps 139 | ch <- c.NetworkInBps 140 | ch <- c.NetworkOutBps 141 | } 142 | 143 | // Collect is called by the Prometheus registry when collecting metrics. 144 | func (c *ServerMetricsCollector) Collect(ch chan<- prometheus.Metric) { 145 | ctx, cancel := context.WithTimeout(context.Background(), c.config.Timeout) 146 | defer cancel() 147 | 148 | now := time.Now() 149 | servers, err := c.client.Server.AllWithOpts(ctx, hcloud.ServerListOpts{ 150 | Status: []hcloud.ServerStatus{ 151 | hcloud.ServerStatusRunning, 152 | }, 153 | }) 154 | 155 | if err != nil { 156 | c.logger.Error("Failed to fetch servers", 157 | "err", err, 158 | ) 159 | 160 | c.failures.WithLabelValues("server_metrics").Inc() 161 | return 162 | } 163 | 164 | c.logger.Debug("Fetched online servers", 165 | "count", len(servers), 166 | ) 167 | 168 | var ( 169 | wg sync.WaitGroup 170 | ) 171 | 172 | for _, server := range servers { 173 | labels := []string{ 174 | strconv.FormatInt(server.ID, 10), 175 | server.Name, 176 | server.Datacenter.Name, 177 | } 178 | 179 | wg.Add(1) 180 | 181 | go func(server *hcloud.Server) { 182 | defer wg.Done() 183 | logger := c.logger.With("server", server.Name) 184 | 185 | metrics, _, err := c.client.Server.GetMetrics( 186 | ctx, 187 | server, 188 | hcloud.ServerGetMetricsOpts{ 189 | Types: []hcloud.ServerMetricType{ 190 | hcloud.ServerMetricCPU, 191 | hcloud.ServerMetricDisk, 192 | hcloud.ServerMetricNetwork, 193 | }, 194 | Start: now, 195 | End: now, 196 | }, 197 | ) 198 | 199 | if err != nil { 200 | logger.Error("Failed to fetch server metrics", 201 | "err", err, 202 | ) 203 | 204 | c.failures.WithLabelValues("server_metrics").Inc() 205 | return 206 | } 207 | 208 | diskLabels := append(labels, "0") 209 | networkLabels := append(labels, "0") 210 | 211 | if len(metrics.TimeSeries["cpu"]) > 0 { 212 | cpuUsage, _ := strconv.ParseFloat(metrics.TimeSeries["cpu"][len(metrics.TimeSeries["cpu"])-1].Value, 64) 213 | ch <- prometheus.MustNewConstMetric( 214 | c.CPU, 215 | prometheus.GaugeValue, 216 | cpuUsage, 217 | labels..., 218 | ) 219 | } 220 | 221 | if len(metrics.TimeSeries["disk.0.iops.read"]) > 0 { 222 | diskReadIops, _ := strconv.ParseFloat(metrics.TimeSeries["disk.0.iops.read"][len(metrics.TimeSeries["disk.0.iops.read"])-1].Value, 64) 223 | ch <- prometheus.MustNewConstMetric( 224 | c.DiskReadIops, 225 | prometheus.GaugeValue, 226 | diskReadIops, 227 | diskLabels..., 228 | ) 229 | } 230 | 231 | if len(metrics.TimeSeries["disk.0.iops.write"]) > 0 { 232 | diskWriteIops, _ := strconv.ParseFloat(metrics.TimeSeries["disk.0.iops.write"][len(metrics.TimeSeries["disk.0.iops.write"])-1].Value, 64) 233 | ch <- prometheus.MustNewConstMetric( 234 | c.DiskWriteIops, 235 | prometheus.GaugeValue, 236 | diskWriteIops, 237 | diskLabels..., 238 | ) 239 | } 240 | 241 | if len(metrics.TimeSeries["disk.0.bandwidth.read"]) > 0 { 242 | diskReadBps, _ := strconv.ParseFloat(metrics.TimeSeries["disk.0.bandwidth.read"][len(metrics.TimeSeries["disk.0.bandwidth.read"])-1].Value, 64) 243 | ch <- prometheus.MustNewConstMetric( 244 | c.DiskReadBps, 245 | prometheus.GaugeValue, 246 | diskReadBps, 247 | diskLabels..., 248 | ) 249 | } 250 | 251 | if len(metrics.TimeSeries["disk.0.bandwidth.write"]) > 0 { 252 | diskWriteBps, _ := strconv.ParseFloat(metrics.TimeSeries["disk.0.bandwidth.write"][len(metrics.TimeSeries["disk.0.bandwidth.write"])-1].Value, 64) 253 | ch <- prometheus.MustNewConstMetric( 254 | c.DiskWriteBps, 255 | prometheus.GaugeValue, 256 | diskWriteBps, 257 | diskLabels..., 258 | ) 259 | } 260 | 261 | if len(metrics.TimeSeries["network.0.pps.in"]) > 0 { 262 | networkInPps, _ := strconv.ParseFloat(metrics.TimeSeries["network.0.pps.in"][len(metrics.TimeSeries["network.0.pps.in"])-1].Value, 64) 263 | ch <- prometheus.MustNewConstMetric( 264 | c.NetworkInPps, 265 | prometheus.GaugeValue, 266 | networkInPps, 267 | networkLabels..., 268 | ) 269 | } 270 | 271 | if len(metrics.TimeSeries["network.0.pps.out"]) > 0 { 272 | networkOutPps, _ := strconv.ParseFloat(metrics.TimeSeries["network.0.pps.out"][len(metrics.TimeSeries["network.0.pps.out"])-1].Value, 64) 273 | ch <- prometheus.MustNewConstMetric( 274 | c.NetworkOutPps, 275 | prometheus.GaugeValue, 276 | networkOutPps, 277 | networkLabels..., 278 | ) 279 | } 280 | 281 | if len(metrics.TimeSeries["network.0.bandwidth.in"]) > 0 { 282 | networkInBps, _ := strconv.ParseFloat(metrics.TimeSeries["network.0.bandwidth.in"][len(metrics.TimeSeries["network.0.bandwidth.in"])-1].Value, 64) 283 | ch <- prometheus.MustNewConstMetric( 284 | c.NetworkInBps, 285 | prometheus.GaugeValue, 286 | networkInBps, 287 | networkLabels..., 288 | ) 289 | } 290 | 291 | if len(metrics.TimeSeries["network.0.bandwidth.out"]) > 0 { 292 | networkOutBps, _ := strconv.ParseFloat(metrics.TimeSeries["network.0.bandwidth.out"][len(metrics.TimeSeries["network.0.bandwidth.out"])-1].Value, 64) 293 | ch <- prometheus.MustNewConstMetric( 294 | c.NetworkOutBps, 295 | prometheus.GaugeValue, 296 | networkOutBps, 297 | networkLabels..., 298 | ) 299 | } 300 | }(server) 301 | } 302 | 303 | wg.Wait() 304 | 305 | c.logger.Debug("Processed server metrics collector", 306 | "duration", time.Since(now), 307 | ) 308 | 309 | c.duration.WithLabelValues("server_metrics").Observe(time.Since(now).Seconds()) 310 | } 311 | -------------------------------------------------------------------------------- /pkg/exporter/ssh_key.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/hetznercloud/hcloud-go/v2/hcloud" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/promhippie/hcloud_exporter/pkg/config" 12 | ) 13 | 14 | // SSHKeyCollector collects metrics about the SSH keys. 15 | type SSHKeyCollector struct { 16 | client *hcloud.Client 17 | logger *slog.Logger 18 | failures *prometheus.CounterVec 19 | duration *prometheus.HistogramVec 20 | config config.Target 21 | 22 | Key *prometheus.Desc 23 | } 24 | 25 | // NewSSHKeyCollector returns a new SSHKeyCollector. 26 | func NewSSHKeyCollector(logger *slog.Logger, client *hcloud.Client, failures *prometheus.CounterVec, duration *prometheus.HistogramVec, cfg config.Target) *SSHKeyCollector { 27 | if failures != nil { 28 | failures.WithLabelValues("ssh_key").Add(0) 29 | } 30 | 31 | labels := []string{"id", "name", "fingerprint"} 32 | return &SSHKeyCollector{ 33 | client: client, 34 | logger: logger.With("collector", "ssh-key"), 35 | failures: failures, 36 | duration: duration, 37 | config: cfg, 38 | 39 | Key: prometheus.NewDesc( 40 | "hcloud_ssh_key", 41 | "Information about SSH keys in your HetznerCloud project", 42 | labels, 43 | nil, 44 | ), 45 | } 46 | } 47 | 48 | // Metrics simply returns the list metric descriptors for generating a documentation. 49 | func (c *SSHKeyCollector) Metrics() []*prometheus.Desc { 50 | return []*prometheus.Desc{ 51 | c.Key, 52 | } 53 | } 54 | 55 | // Describe sends the super-set of all possible descriptors of metrics collected by this Collector. 56 | func (c *SSHKeyCollector) Describe(ch chan<- *prometheus.Desc) { 57 | ch <- c.Key 58 | } 59 | 60 | // Collect is called by the Prometheus registry when collecting metrics. 61 | func (c *SSHKeyCollector) Collect(ch chan<- prometheus.Metric) { 62 | ctx, cancel := context.WithTimeout(context.Background(), c.config.Timeout) 63 | defer cancel() 64 | 65 | now := time.Now() 66 | keys, err := c.client.SSHKey.All(ctx) 67 | 68 | if err != nil { 69 | c.logger.Error("Failed to fetch SSH keys", 70 | "err", err, 71 | ) 72 | 73 | c.failures.WithLabelValues("ssh_key").Inc() 74 | return 75 | } 76 | 77 | c.logger.Debug("Fetched SSH keys", 78 | "count", len(keys), 79 | ) 80 | 81 | for _, key := range keys { 82 | labels := []string{ 83 | strconv.FormatInt(key.ID, 10), 84 | key.Name, 85 | key.Fingerprint, 86 | } 87 | 88 | ch <- prometheus.MustNewConstMetric( 89 | c.Key, 90 | prometheus.GaugeValue, 91 | 1.0, 92 | labels..., 93 | ) 94 | } 95 | 96 | c.logger.Debug("Processed SSH key collector", 97 | "duration", time.Since(now), 98 | ) 99 | 100 | c.duration.WithLabelValues("ssh_key").Observe(time.Since(now).Seconds()) 101 | } 102 | -------------------------------------------------------------------------------- /pkg/exporter/volume.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/hetznercloud/hcloud-go/v2/hcloud" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/promhippie/hcloud_exporter/pkg/config" 12 | ) 13 | 14 | // VolumeCollector collects metrics about the volumes. 15 | type VolumeCollector struct { 16 | client *hcloud.Client 17 | logger *slog.Logger 18 | failures *prometheus.CounterVec 19 | duration *prometheus.HistogramVec 20 | config config.Target 21 | 22 | Status *prometheus.Desc 23 | Size *prometheus.Desc 24 | Protection *prometheus.Desc 25 | Created *prometheus.Desc 26 | } 27 | 28 | // NewVolumeCollector returns a new VolumeCollector. 29 | func NewVolumeCollector(logger *slog.Logger, client *hcloud.Client, failures *prometheus.CounterVec, duration *prometheus.HistogramVec, cfg config.Target) *VolumeCollector { 30 | if failures != nil { 31 | failures.WithLabelValues("volume").Add(0) 32 | } 33 | 34 | labels := []string{"id", "server", "location", "name"} 35 | return &VolumeCollector{ 36 | client: client, 37 | logger: logger.With("collector", "volume"), 38 | failures: failures, 39 | duration: duration, 40 | config: cfg, 41 | 42 | Status: prometheus.NewDesc( 43 | "hcloud_volume_status", 44 | "If 1 the volume is availabel, 0 otherwise", 45 | labels, 46 | nil, 47 | ), 48 | Size: prometheus.NewDesc( 49 | "hcloud_volume_size", 50 | "Size of the volume in GB", 51 | labels, 52 | nil, 53 | ), 54 | Protection: prometheus.NewDesc( 55 | "hcloud_volume_protection", 56 | "If 1 the volume is protected, 0 otherwise", 57 | labels, 58 | nil, 59 | ), 60 | Created: prometheus.NewDesc( 61 | "hcloud_volume_created", 62 | "Timestamp when the volume have been created", 63 | labels, 64 | nil, 65 | ), 66 | } 67 | } 68 | 69 | // Metrics simply returns the list metric descriptors for generating a documentation. 70 | func (c *VolumeCollector) Metrics() []*prometheus.Desc { 71 | return []*prometheus.Desc{ 72 | c.Status, 73 | c.Size, 74 | c.Protection, 75 | c.Created, 76 | } 77 | } 78 | 79 | // Describe sends the super-set of all possible descriptors of metrics collected by this Collector. 80 | func (c *VolumeCollector) Describe(ch chan<- *prometheus.Desc) { 81 | ch <- c.Status 82 | ch <- c.Size 83 | ch <- c.Protection 84 | ch <- c.Created 85 | } 86 | 87 | // Collect is called by the Prometheus registry when collecting metrics. 88 | func (c *VolumeCollector) Collect(ch chan<- prometheus.Metric) { 89 | ctx, cancel := context.WithTimeout(context.Background(), c.config.Timeout) 90 | defer cancel() 91 | 92 | now := time.Now() 93 | volumes, err := c.client.Volume.All(ctx) 94 | 95 | if err != nil { 96 | c.logger.Error("Failed to fetch volumes", 97 | "err", err, 98 | ) 99 | 100 | c.failures.WithLabelValues("volume").Inc() 101 | return 102 | } 103 | 104 | c.logger.Debug("Fetched volumes", 105 | "count", len(volumes), 106 | ) 107 | 108 | for _, volume := range volumes { 109 | var ( 110 | status float64 111 | protection float64 112 | name string 113 | ) 114 | 115 | if volume.Server != nil { 116 | name = volume.Server.Name 117 | } 118 | 119 | labels := []string{ 120 | strconv.FormatInt(volume.ID, 10), 121 | name, 122 | volume.Location.Name, 123 | volume.Name, 124 | } 125 | 126 | if volume.Status == "available" { 127 | status = 1.0 128 | } 129 | 130 | if volume.Protection.Delete { 131 | protection = 1.0 132 | } 133 | 134 | ch <- prometheus.MustNewConstMetric( 135 | c.Status, 136 | prometheus.GaugeValue, 137 | status, 138 | labels..., 139 | ) 140 | 141 | ch <- prometheus.MustNewConstMetric( 142 | c.Size, 143 | prometheus.GaugeValue, 144 | float64(volume.Size), 145 | labels..., 146 | ) 147 | 148 | ch <- prometheus.MustNewConstMetric( 149 | c.Protection, 150 | prometheus.GaugeValue, 151 | protection, 152 | labels..., 153 | ) 154 | 155 | ch <- prometheus.MustNewConstMetric( 156 | c.Created, 157 | prometheus.GaugeValue, 158 | float64(volume.Created.Unix()), 159 | labels..., 160 | ) 161 | } 162 | 163 | c.logger.Debug("Processed volume collector", 164 | "duration", time.Since(now), 165 | ) 166 | 167 | c.duration.WithLabelValues("volume").Observe(time.Since(now).Seconds()) 168 | } 169 | -------------------------------------------------------------------------------- /pkg/middleware/cache.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | // Cache writes required cache headers to all requests. 9 | func Cache(next http.Handler) http.Handler { 10 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 11 | w.Header().Set("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") 12 | w.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") 13 | w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) 14 | 15 | next.ServeHTTP(w, r) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/middleware/profiler.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/go-chi/chi/v5/middleware" 7 | ) 8 | 9 | // Profiler just wraps the go-chi profiler middleware. 10 | func Profiler() http.Handler { 11 | return middleware.Profiler() 12 | } 13 | -------------------------------------------------------------------------------- /pkg/middleware/realip.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/go-chi/chi/v5/middleware" 7 | ) 8 | 9 | // RealIP just wraps the go-chi realip middleware. 10 | func RealIP(next http.Handler) http.Handler { 11 | return middleware.RealIP(next) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/middleware/recoverer.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "log/slog" 5 | "net/http" 6 | "runtime/debug" 7 | ) 8 | 9 | // Recoverer initializes a recoverer middleware. 10 | func Recoverer(logger *slog.Logger) func(next http.Handler) http.Handler { 11 | return func(next http.Handler) http.Handler { 12 | fn := func(w http.ResponseWriter, r *http.Request) { 13 | defer func() { 14 | if rvr := recover(); rvr != nil { 15 | logger.Error(rvr.(string), 16 | "trace", string(debug.Stack()), 17 | ) 18 | 19 | http.Error( 20 | w, 21 | http.StatusText(http.StatusInternalServerError), 22 | http.StatusInternalServerError, 23 | ) 24 | } 25 | }() 26 | 27 | next.ServeHTTP(w, r) 28 | } 29 | 30 | return http.HandlerFunc(fn) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/middleware/timeout.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | // Timeout just copies the go-chi timeout middleware. 10 | func Timeout(next http.Handler) http.Handler { 11 | fn := func(w http.ResponseWriter, r *http.Request) { 12 | ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second) 13 | 14 | defer func() { 15 | cancel() 16 | if ctx.Err() == context.DeadlineExceeded { 17 | w.WriteHeader(http.StatusGatewayTimeout) 18 | } 19 | }() 20 | 21 | next.ServeHTTP(w, r.WithContext(ctx)) 22 | } 23 | 24 | return http.HandlerFunc(fn) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/version/collector.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | ) 6 | 7 | // Collector simply exports the version information for Prometheus. 8 | func Collector(ns string) *prometheus.GaugeVec { 9 | info := prometheus.NewGaugeVec( 10 | prometheus.GaugeOpts{ 11 | Namespace: ns, 12 | Name: "build_info", 13 | Help: "A metric with a constant '1' value labeled by version, revision and goversion from which it was built.", 14 | }, 15 | []string{"version", "revision", "goversion"}, 16 | ) 17 | 18 | info.WithLabelValues(String, Revision, Go).Set(1) 19 | return info 20 | } 21 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | var ( 8 | // String gets defined by the build system. 9 | String = "0.0.0-dev" 10 | 11 | // Revision indicates the commit this binary was built from. 12 | Revision string 13 | 14 | // Date indicates the date this binary was built. 15 | Date string 16 | 17 | // Go running this binary. 18 | Go = runtime.Version() 19 | ) 20 | -------------------------------------------------------------------------------- /reflex.conf: -------------------------------------------------------------------------------- 1 | # backend 2 | -r '^(cmd|pkg)/.*\.go$' -s -- sh -c 'make bin/hcloud_exporter-debug && bin/hcloud_exporter-debug --log.pretty --log.level debug' 3 | -------------------------------------------------------------------------------- /revive.toml: -------------------------------------------------------------------------------- 1 | ignoreGeneratedHeader = false 2 | severity = "warning" 3 | confidence = 0.8 4 | errorCode = 0 5 | warningCode = 0 6 | 7 | [rule.blank-imports] 8 | [rule.context-as-argument] 9 | [rule.context-keys-type] 10 | [rule.dot-imports] 11 | [rule.error-return] 12 | [rule.error-strings] 13 | [rule.error-naming] 14 | [rule.exported] 15 | [rule.if-return] 16 | [rule.increment-decrement] 17 | [rule.var-naming] 18 | [rule.var-declaration] 19 | [rule.range] 20 | [rule.receiver-naming] 21 | [rule.time-naming] 22 | [rule.unexported-return] 23 | [rule.indent-error-flow] 24 | [rule.errorf] 25 | [rule.empty-block] 26 | [rule.superfluous-else] 27 | [rule.unused-parameter] 28 | [rule.unreachable-code] 29 | [rule.redefines-builtin-id] 30 | 31 | [rule.package-comments] 32 | Disabled = true 33 | 34 | --------------------------------------------------------------------------------