Merge upstream Prometheus

This commit is contained in:
Peter Štibraný 2022-06-09 11:29:19 +02:00
commit 9d51bf50db
210 changed files with 4053 additions and 2112 deletions

View file

@ -38,6 +38,7 @@ jobs:
GO111MODULE: "on"
- run: go test ./tsdb/ -test.tsdb-isolation=false
- run: make -C documentation/examples/remote_storage
- run: make -C documentation/examples
- prometheus/check_proto:
version: "3.15.8"
- prometheus/store_artifact:

View file

@ -1,57 +0,0 @@
---
name: Bug report
about: Create a report to help us improve.
title: ''
assignees: ''
---
<!--
Please do *NOT* ask support questions in Github issues.
If your issue is not a feature request or bug report use our
community support.
https://prometheus.io/community/
There is also commercial support available.
https://prometheus.io/support-training/
-->
**What did you do?**
**What did you expect to see?**
**What did you see instead? Under which circumstances?**
**Environment**
* System information:
insert output of `uname -srm` here
* Prometheus version:
insert output of `prometheus --version` here
* Alertmanager version:
insert output of `alertmanager --version` here (if relevant to the issue)
* Prometheus configuration file:
```
insert configuration here
```
* Alertmanager configuration file:
```
insert configuration here (if relevant to the issue)
```
* Logs:
```
insert Prometheus and Alertmanager logs relevant to the issue here
```

74
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View file

@ -0,0 +1,74 @@
---
name: Bug report
description: Create a report to help us improve.
body:
- type: markdown
attributes:
value: |
Thank you for opening a bug report for Prometheus.
Please do *NOT* ask support questions in Github issues.
If your issue is not a feature request or bug report use our [community support](https://prometheus.io/community/).
There is also [commercial support](https://prometheus.io/support-training/) available.
- type: textarea
attributes:
label: What did you do?
description: Please provide steps for us to reproduce this issue.
validations:
required: true
- type: textarea
attributes:
label: What did you expect to see?
- type: textarea
attributes:
label: What did you see instead? Under which circumstances?
validations:
required: true
- type: markdown
attributes:
value: |
## Environment
- type: input
attributes:
label: System information
description: insert output of `uname -srm` here, or operating system version
placeholder: e.g. Linux 5.16.15 x86_64
- type: textarea
attributes:
label: Prometheus version
description: Insert output of `prometheus --version` here.
render: text
placeholder: |
e.g. prometheus, version 2.23.0 (branch: HEAD, revision: 26d89b4b0776fe4cd5a3656dfa520f119a375273)
build user: root@37609b3a0a21
build date: 20201126-10:56:17
go version: go1.15.5
platform: linux/amd64
- type: textarea
attributes:
label: Prometheus configuration file
description: Insert relevant configuration here. Don't forget to remove secrets.
render: yaml
- type: textarea
attributes:
label: Alertmanager version
description: Insert output of `alertmanager --version` here (if relevant to the issue).
render: text
placeholder: |
e.g. alertmanager, version 0.22.2 (branch: HEAD, revision: 44f8adc06af5101ad64bd8b9c8b18273f2922051)
build user: root@b595c7f32520
build date: 20210602-07:50:37
go version: go1.16.4
platform: linux/amd64
- type: textarea
attributes:
label: Alertmanager configuration file
description: Insert relevant configuration here. Don't forget to remove secrets.
render: yaml
- type: textarea
attributes:
label: Logs
description: Insert Prometheus and Alertmanager logs relevant to the issue here.
render: text

View file

@ -4,6 +4,10 @@ updates:
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "gomod"
directory: "/documentation/examples/remote_storage"
schedule:
interval: "monthly"
- package-ecosystem: "npm"
directory: "/web/ui"
schedule:

View file

@ -21,7 +21,10 @@ jobs:
uses: actions/setup-go@v2
with:
go-version: 1.18.x
- name: Install snmp_exporter/generator dependencies
run: sudo apt-get update && sudo apt-get -y install libsnmp-dev
if: github.repository == 'prometheus/snmp_exporter'
- name: Lint
uses: golangci/golangci-lint-action@v3.1.0
uses: golangci/golangci-lint-action@v3.2.0
with:
version: v1.45.2

View file

@ -16,6 +16,7 @@ linters:
- misspell
issues:
max-same-issues: 0
exclude-rules:
- path: _test.go
linters:
@ -29,6 +30,7 @@ linters-settings:
- sync/atomic: "Use go.uber.org/atomic instead of sync/atomic"
- github.com/stretchr/testify/assert: "Use github.com/stretchr/testify/require instead of github.com/stretchr/testify/assert"
- github.com/go-kit/kit/log: "Use github.com/go-kit/log instead of github.com/go-kit/kit/log"
- io/ioutil: "Use corresponding 'os' or 'io' functions instead."
- regexp: "Use github.com/grafana/regexp instead of regexp"
errcheck:
exclude: scripts/errcheck_excludes.txt

View file

@ -14,6 +14,7 @@ rules:
document-start: disable
indentation:
spaces: consistent
indent-sequences: consistent
key-duplicates:
ignore: |
config/testdata/section_key_dup.bad.yml

View file

@ -1,28 +1,49 @@
## 2.35.0-rc1 / 2022-04-14
# Changelog
* [ENHANCEMENT] Update package `uber.go/auomaxprocs` to support `cgroups2` #10584
* [BUGFIX] Tracing/GRPC: Set TLS credentials only when insecure is false. #10592
* [BUGFIX] Agent: Fix ID collision when loading a WAL with multiple segments. #10587
* [BUGFIX] Revoke storing target and metadata cache in context. This can fix a memory link introduced in `2.35.0-rc0` #10590
## 2.36.0 / 2022-05-30
## 2.35.0-rc0 / 2022-04-08
* [FEATURE] Add lowercase and uppercase relabel action. #10641
* [FEATURE] SD: Add IONOS Cloud integration. #10514
* [FEATURE] SD: Add Vultr integration. #10714
* [FEATURE] SD: Add Linode SD failure count metric. #10673
* [FEATURE] Add prometheus_ready metric. #10682
* [ENHANCEMENT] Add stripDomain to template function. #10475
* [ENHANCEMENT] UI: Enable active search through dropped targets. #10668
* [ENHANCEMENT] promtool: support matchers when querying label values. #10727
* [ENHANCEMENT] Add agent mode identifier. #9638
* [BUGFIX] Changing TotalQueryableSamples from int to int64. #10549
* [BUGFIX] tsdb/agent: Ignore duplicate exemplars. #10595
* [BUGFIX] TSDB: Fix chunk overflow appending samples at a variable rate. #10607
* [BUGFIX] Stop rule manager before TSDB is stopped. #10680
## 2.35.0 / 2022-04-21
This Prometheus release is built with go1.18, which contains two noticeable changes related to TLS:
1. [TLS 1.0 and 1.1 disabled by default client-side](https://go.dev/doc/go1.18#tls10).
Prometheus users can override this with the `min_version` parameter of [tls_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config).
2. [Certificates signed with the SHA-1 hash function are rejected](https://go.dev/doc/go1.18#sha1). This doesn't apply to self-signed root certificates.
* [CHANGE] TSDB: Delete `*.tmp` WAL files when Prometheus starts. #10317
* [CHANGE] promtool: Add new flag `--lint` (enabled by default) for the commands `check rules` and `check config`, resulting in a new exit code (`3`) for linter errors. #10435
* [FEATURE] Support for automatically setting the variable `GOMAXPROCS` to the container CPU limit. Enable with the flag `--enable-feature=auto-gomaxprocs` #10498
* [FEATURE] Support for automatically setting the variable `GOMAXPROCS` to the container CPU limit. Enable with the flag `--enable-feature=auto-gomaxprocs`. #10498
* [FEATURE] PromQL: Extend statistics with total and peak number of samples in a query. Additionally, per-step statistics are available with --enable-feature=promql-per-step-stats and using `stats=all` in the query API.
Enable with the flag `--enable-feature=per-step-stats` #10369
Enable with the flag `--enable-feature=per-step-stats`. #10369
* [ENHANCEMENT] Prometheus is built with Go 1.18. #10501
* [ENHANCEMENT] TSDB: more efficient sorting of postings read from WAL at startup. #10500
* [ENHANCEMENT] Azure SD: Add metric to track Azure SD failures #10476
* [ENHANCEMENT] Azure SD: Add metric to track Azure SD failures. #10476
* [ENHANCEMENT] Azure SD: Add an optional `resource_group` configuration. #10365
* [ENHANCEMENT] Kubernetes SD: Support `discovery.k8s.io/v1` `EndpointSlice` (previously only `discovery.k8s.io/v1beta1` `EndpointSlice` was supported). #9570
* [ENHANCEMENT] Kubernetes SD: Allow attaching node metadata to discovered pods. #10080
* [ENHANCEMENT] OAuth2: Support for using a proxy URL to fetch OAuth2 tokens. #10492
* [ENHANCEMENT] Configuration: Add the ability to disable HTTP2. #10492
* [ENHANCEMENT] Config: Support overriding minimum TLS version. #10610
* [BUGFIX] Kubernetes SD: Explicitly include gcp auth from k8s.io. #10516
* [BUGFIX] Fix OpenMetrics parser to sort uppercase labels correctly. #10510
* [BUGFIX] UI: Fix scrape interval and duration tooltip not showing on target page. #10545
* [BUGFIX] Tracing/GRPC: Set TLS credentials only when insecure is false. #10592
* [BUGFIX] Agent: Fix ID collision when loading a WAL with multiple segments. #10587
* [BUGFIX] Remote-write: Fix a deadlock between Batch and flushing the queue. #10608
## 2.34.0 / 2022-03-15
@ -357,7 +378,7 @@ Alertmanager API v2 was released in Alertmanager v0.16.0 (released in January
2019).
* [FEATURE] **experimental** API: Accept remote_write requests. Behind the --enable-feature=remote-write-receiver flag. #8424
* [FEATURE] **experimental** PromQL: Add '@ <timestamp>' modifier. Behind the --enable-feature=promql-at-modifier flag. #8121 #8436 #8425
* [FEATURE] **experimental** PromQL: Add `@ <timestamp>` modifier. Behind the --enable-feature=promql-at-modifier flag. #8121 #8436 #8425
* [ENHANCEMENT] Add optional name property to testgroup for better test failure output. #8440
* [ENHANCEMENT] Add warnings into React Panel on the Graph page. #8427
* [ENHANCEMENT] TSDB: Increase the number of buckets for the compaction duration metric. #8342
@ -402,12 +423,12 @@ Alertmanager API v2 was released in Alertmanager v0.16.0 (released in January
* [CHANGE] UI: Make the React UI default. #8142
* [CHANGE] Remote write: The following metrics were removed/renamed in remote write. #6815
- `prometheus_remote_storage_succeeded_samples_total` was removed and `prometheus_remote_storage_samples_total` was introduced for all the samples attempted to send.
- `prometheus_remote_storage_sent_bytes_total` was removed and replaced with `prometheus_remote_storage_samples_bytes_total` and `prometheus_remote_storage_metadata_bytes_total`.
- `prometheus_remote_storage_failed_samples_total` -> `prometheus_remote_storage_samples_failed_total` .
- `prometheus_remote_storage_retried_samples_total` -> `prometheus_remote_storage_samples_retried_total`.
- `prometheus_remote_storage_dropped_samples_total` -> `prometheus_remote_storage_samples_dropped_total`.
- `prometheus_remote_storage_pending_samples` -> `prometheus_remote_storage_samples_pending`.
* `prometheus_remote_storage_succeeded_samples_total` was removed and `prometheus_remote_storage_samples_total` was introduced for all the samples attempted to send.
* `prometheus_remote_storage_sent_bytes_total` was removed and replaced with `prometheus_remote_storage_samples_bytes_total` and `prometheus_remote_storage_metadata_bytes_total`.
* `prometheus_remote_storage_failed_samples_total` -> `prometheus_remote_storage_samples_failed_total` .
* `prometheus_remote_storage_retried_samples_total` -> `prometheus_remote_storage_samples_retried_total`.
* `prometheus_remote_storage_dropped_samples_total` -> `prometheus_remote_storage_samples_dropped_total`.
* `prometheus_remote_storage_pending_samples` -> `prometheus_remote_storage_samples_pending`.
* [CHANGE] Remote: Do not collect non-initialized timestamp metrics. #8060
* [FEATURE] [EXPERIMENTAL] Remote write: Allow metric metadata to be propagated via remote write. The following new metrics were introduced: `prometheus_remote_storage_metadata_total`, `prometheus_remote_storage_metadata_failed_total`, `prometheus_remote_storage_metadata_retried_total`, `prometheus_remote_storage_metadata_bytes_total`. #6815
* [ENHANCEMENT] Remote write: Added a metric `prometheus_remote_storage_max_samples_per_send` for remote write. #8102
@ -583,7 +604,6 @@ This release changes WAL compression from opt-in to default. WAL compression wil
* [BUGFIX] Remote Write: Fixed blocked resharding edge case. #7122
* [BUGFIX] Remote Write: Fixed remote write not updating on relabel configs change. #7073
## 2.17.2 / 2020-04-20
* [BUGFIX] Federation: Register federation metrics #7081
@ -1149,15 +1169,14 @@ This release includes multiple bugfixes and features. Further, the WAL implement
* [BUGFIX] tsdb: Cleanup and do not retry failing compactions.
* [BUGFIX] tsdb: Close WAL while shutting down.
## 2.0.0 / 2017-11-08
This release includes a completely rewritten storage, huge performance
improvements, but also many backwards incompatible changes. For more
information, read the announcement blog post and migration guide.
https://prometheus.io/blog/2017/11/08/announcing-prometheus-2-0/
https://prometheus.io/docs/prometheus/2.0/migration/
<https://prometheus.io/blog/2017/11/08/announcing-prometheus-2-0/>
<https://prometheus.io/docs/prometheus/2.0/migration/>
* [CHANGE] Completely rewritten storage layer, with WAL. This is not backwards compatible with 1.x storage, and many flags have changed/disappeared.
* [CHANGE] New staleness behavior. Series now marked stale after target scrapes no longer return them, and soon after targets disappear from service discovery.
@ -1567,7 +1586,7 @@ This release contains multiple breaking changes to the configuration schema.
This version contains a breaking change to the query language. Please read
the documentation on the grouping behavior of vector matching:
https://prometheus.io/docs/querying/operators/#vector-matching
<https://prometheus.io/docs/querying/operators/#vector-matching>
* [FEATURE] Add experimental Microsoft Azure service discovery
* [FEATURE] Add `ignoring` modifier for binary operations
@ -1693,7 +1712,7 @@ BREAKING CHANGES:
with InfluxDB 0.9.x. 0.8.x versions of InfluxDB are not supported anymore.
* Escape sequences in double- and single-quoted string literals in rules or query
expressions are now interpreted like escape sequences in Go string literals
(https://golang.org/ref/spec#String_literals).
(<https://golang.org/ref/spec#String_literals>).
Future breaking changes / deprecated features:
@ -1829,6 +1848,7 @@ All changes:
* [CLEANUP] Resolve relative paths during configuration loading.
## 0.15.1 / 2015-07-27
* [BUGFIX] Fix vector matching behavior when there is a mix of equality and
non-equality matchers in a vector selector and one matcher matches no series.
* [ENHANCEMENT] Allow overriding `GOARCH` and `GOOS` in Makefile.INCLUDE.
@ -1946,6 +1966,7 @@ All changes:
* [CLEANUP] Use new v1 HTTP API for querying and graphing.
## 0.14.0 / 2015-06-01
* [CHANGE] Configuration format changed and switched to YAML.
(See the provided [migration tool](https://github.com/prometheus/migrate/releases).)
* [ENHANCEMENT] Redesign of state-preserving target discovery.
@ -1971,9 +1992,11 @@ All changes:
* [ENHANCEMENT] Limit retrievable samples to the storage's retention window.
## 0.13.4 / 2015-05-23
* [BUGFIX] Fix a race while checkpointing fingerprint mappings.
## 0.13.3 / 2015-05-11
* [BUGFIX] Handle fingerprint collisions properly.
* [CHANGE] Comments in rules file must start with `#`. (The undocumented `//`
and `/*...*/` comment styles are no longer supported.)
@ -1984,6 +2007,7 @@ All changes:
* [ENHANCEMENT] Terminate running queries during shutdown.
## 0.13.2 / 2015-05-05
* [MAINTENANCE] Updated vendored dependencies to their newest versions.
* [MAINTENANCE] Include rule_checker and console templates in release tarball.
* [BUGFIX] Sort NaN as the lowest value.
@ -1994,10 +2018,12 @@ All changes:
* [BUGFIX] Show correct error on wrong DNS response.
## 0.13.1 / 2015-04-09
* [BUGFIX] Treat memory series with zero chunks correctly in series maintenance.
* [ENHANCEMENT] Improve readability of usage text even more.
## 0.13.0 / 2015-04-08
* [ENHANCEMENT] Double-delta encoding for chunks, saving typically 40% of
space, both in RAM and on disk.
* [ENHANCEMENT] Redesign of chunk persistence queuing, increasing performance
@ -2025,6 +2051,7 @@ All changes:
* [MAINTENANCE] Updated vendored dependencies to their newest versions.
## 0.12.0 / 2015-03-04
* [CHANGE] Use client_golang v0.3.1. THIS CHANGES FINGERPRINTING AND INVALIDATES
ALL PERSISTED FINGERPRINTS. You have to wipe your storage to use this or
later versions. There is a version guard in place that will prevent you to
@ -2040,6 +2067,7 @@ All changes:
* [CHANGE] Makefile uses Go 1.4.2.
## 0.11.1 / 2015-02-27
* [BUGFIX] Make series maintenance complete again. (Ever since 0.9.0rc4,
or commit 0851945, series would not be archived, chunk descriptors would
not be evicted, and stale head chunks would never be closed. This happened
@ -2052,6 +2080,7 @@ All changes:
* [ENHANCEMENT] Limit the number of 'dirty' series counted during checkpointing.
## 0.11.0 / 2015-02-23
* [FEATURE] Introduce new metric type Histogram with server-side aggregation.
* [FEATURE] Add offset operator.
* [FEATURE] Add floor, ceil and round functions.
@ -2076,18 +2105,20 @@ All changes:
* [CLEANUP] Various code cleanups.
## 0.10.0 / 2015-01-26
* [CHANGE] More efficient JSON result format in query API. This requires
up-to-date versions of PromDash and prometheus_cli, too.
* [ENHANCEMENT] Excluded non-minified Bootstrap assets and the Bootstrap maps
from embedding into the binary. Those files are only used for debugging,
and then you can use -web.use-local-assets. By including fewer files, the
RAM usage during compilation is much more manageable.
* [ENHANCEMENT] Help link points to https://prometheus.github.io now.
* [ENHANCEMENT] Help link points to <https://prometheus.github.io> now.
* [FEATURE] Consoles for haproxy and cloudwatch.
* [BUGFIX] Several fixes to graphs in consoles.
* [CLEANUP] Removed a file size check that did not check anything.
## 0.9.0 / 2015-01-23
* [CHANGE] Reworked command line flags, now more consistent and taking into
account needs of the new storage backend (see below).
* [CHANGE] Metric names are dropped after certain transformations.
@ -2129,10 +2160,12 @@ All changes:
* [CLEANUP] Removed dead code.
## 0.8.0 / 2014-09-04
* [ENHANCEMENT] Stagger scrapes to spread out load.
* [BUGFIX] Correctly quote HTTP Accept header.
## 0.7.0 / 2014-08-06
* [FEATURE] Added new functions: abs(), topk(), bottomk(), drop_common_labels().
* [FEATURE] Let console templates get graph links from expressions.
* [FEATURE] Allow console templates to dynamically include other templates.
@ -2147,6 +2180,7 @@ All changes:
* [ENHANCEMENT] Dockerfile also builds Prometheus support tools now.
## 0.6.0 / 2014-06-30
* [FEATURE] Added console and alert templates support, along with various template functions.
* [PERFORMANCE] Much faster and more memory-efficient flushing to disk.
* [ENHANCEMENT] Query results are now only logged when debugging.

View file

@ -1,3 +1,3 @@
## Prometheus Community Code of Conduct
# Prometheus Community Code of Conduct
Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).

View file

@ -21,7 +21,6 @@ Prometheus uses GitHub to manage reviews of pull requests.
* Be sure to sign off on the [DCO](https://github.com/probot/dco#how-it-works).
## Steps to Contribute
Should you wish to work on an issue, please claim it first by commenting on the GitHub issue that you want to work on it. This is to prevent duplicated efforts from contributors on the same issue.
@ -33,7 +32,8 @@ You can [spin up a prebuilt dev environment](https://gitpod.io/#https://github.c
For complete instructions on how to compile see: [Building From Source](https://github.com/prometheus/prometheus#building-from-source)
For quickly compiling and testing your changes do:
```
```bash
# For building.
go build ./cmd/prometheus/
./prometheus

View file

@ -1,7 +1,9 @@
# Maintainers
Julien Pivotto (<roidelapluie@prometheus.io> / @roidelapluie) and Levi Harrison (<levi@leviharrison.dev> / @LeviHarrison) are the main/default maintainers, some parts of the codebase have other maintainers:
* `cmd`
* `promtool`: David Leadbeater (<dgl@dgl.cx> / @dgl), Jessica Grebenschikov (<jessicagreben@prometheus.io> / @jessicagreben)
* `promtool`: David Leadbeater (<dgl@dgl.cx> / @dgl)
* `discovery`
* `k8s`: Frederic Branczyk (<fbranczyk@gmail.com> / @brancz)
* `documentation`

View file

@ -17,18 +17,18 @@ displays the results, and can trigger alerts when specified conditions are obser
The features that distinguish Prometheus from other metrics and monitoring systems are:
- A **multi-dimensional** data model (time series defined by metric name and set of key/value dimensions)
- PromQL, a **powerful and flexible query language** to leverage this dimensionality
- No dependency on distributed storage; **single server nodes are autonomous**
- An HTTP **pull model** for time series collection
- **Pushing time series** is supported via an intermediary gateway for batch jobs
- Targets are discovered via **service discovery** or **static configuration**
- Multiple modes of **graphing and dashboarding support**
- Support for hierarchical and horizontal **federation**
* A **multi-dimensional** data model (time series defined by metric name and set of key/value dimensions)
* PromQL, a **powerful and flexible query language** to leverage this dimensionality
* No dependency on distributed storage; **single server nodes are autonomous**
* An HTTP **pull model** for time series collection
* **Pushing time series** is supported via an intermediary gateway for batch jobs
* Targets are discovered via **service discovery** or **static configuration**
* Multiple modes of **graphing and dashboarding support**
* Support for hierarchical and horizontal **federation**
## Architecture overview
![](https://cdn.jsdelivr.net/gh/prometheus/prometheus@c34257d069c630685da35bcef084632ffd5d6209/documentation/images/architecture.svg)
![Architecture overview](https://cdn.jsdelivr.net/gh/prometheus/prometheus@c34257d069c630685da35bcef084632ffd5d6209/documentation/images/architecture.svg)
## Install
@ -49,40 +49,47 @@ Docker images are available on [Quay.io](https://quay.io/repository/prometheus/p
You can launch a Prometheus container for trying it out with
$ docker run --name prometheus -d -p 127.0.0.1:9090:9090 prom/prometheus
```bash
docker run --name prometheus -d -p 127.0.0.1:9090:9090 prom/prometheus
```
Prometheus will now be reachable at http://localhost:9090/.
Prometheus will now be reachable at <http://localhost:9090/>.
### Building from source
To build Prometheus from source code, You need:
* Go [version 1.16 or greater](https://golang.org/doc/install).
* Go [version 1.17 or greater](https://golang.org/doc/install).
* NodeJS [version 16 or greater](https://nodejs.org/).
* npm [version 7 or greater](https://www.npmjs.com/).
You can directly use the `go` tool to download and install the `prometheus`
and `promtool` binaries into your `GOPATH`:
$ GO111MODULE=on go install github.com/prometheus/prometheus/cmd/...
$ prometheus --config.file=your_config.yml
```bash
GO111MODULE=on go install github.com/prometheus/prometheus/cmd/...
prometheus --config.file=your_config.yml
```
*However*, when using `go install` to build Prometheus, Prometheus will expect to be able to
read its web assets from local filesystem directories under `web/ui/static` and
`web/ui/templates`. In order for these assets to be found, you will have to run Prometheus
from the root of the cloned repository. Note also that these directories do not include the
new experimental React UI unless it has been built explicitly using `make assets` or `make build`.
React UI unless it has been built explicitly using `make assets` or `make build`.
An example of the above configuration file can be found [here.](https://github.com/prometheus/prometheus/blob/main/documentation/examples/prometheus.yml)
You can also clone the repository yourself and build using `make build`, which will compile in
the web assets so that Prometheus can be run from anywhere:
$ mkdir -p $GOPATH/src/github.com/prometheus
$ cd $GOPATH/src/github.com/prometheus
$ git clone https://github.com/prometheus/prometheus.git
$ cd prometheus
$ make build
$ ./prometheus --config.file=your_config.yml
```bash
mkdir -p $GOPATH/src/github.com/prometheus
cd $GOPATH/src/github.com/prometheus
git clone https://github.com/prometheus/prometheus.git
cd prometheus
make build
./prometheus --config.file=your_config.yml
```
The Makefile provides several targets:
@ -91,7 +98,7 @@ The Makefile provides several targets:
* *test-short*: run the short tests
* *format*: format the source code
* *vet*: check the source code for common errors
* *assets*: build the new experimental React UI
* *assets*: build the React UI
### Service discovery plugins
@ -115,10 +122,12 @@ always, be extra careful when loading third party code.
The `make docker` target is designed for use in our CI system.
You can build a docker image locally with the following commands:
$ make promu
$ promu crossbuild -p linux/amd64
$ make npm_licenses
$ make common-docker-amd64
```bash
make promu
promu crossbuild -p linux/amd64
make npm_licenses
make common-docker-amd64
```
*NB* if you are on a Mac, you will need [gnu-tar](https://formulae.brew.sh/formula/gnu-tar).
@ -132,7 +141,7 @@ We are publishing our Remote Write protobuf independently at
You can use that as a library:
```shell
$ go get go.buf.build/protocolbuffers/go/prometheus/prometheus
go get go.buf.build/protocolbuffers/go/prometheus/prometheus
```
This is experimental.
@ -145,8 +154,8 @@ Prometheus v2.y.z releases, we are publishing equivalent v0.y.z tags.
Therefore, a user that would want to use Prometheus v2.35.0 as a library could do:
```
$ go get github.com/prometheus/prometheus@v0.35.0
```shell
go get github.com/prometheus/prometheus@v0.35.0
```
This solution makes it clear that we might break our internal Go APIs between
@ -155,11 +164,11 @@ zero](https://semver.org/#spec-item-4).
## React UI Development
For more information on building, running, and developing on the new React-based UI, see the React app's [README.md](web/ui/README.md).
For more information on building, running, and developing on the React-based UI, see the React app's [README.md](web/ui/README.md).
## More information
* The source code is periodically indexed, but due to an issue with versioning, the "latest" docs shown on Godoc are outdated. Instead, you can use [the docs for v2.31.1](https://pkg.go.dev/github.com/prometheus/prometheus@v1.8.2-0.20211105201321-411021ada9ab).
* Godoc documentation is available via [pkg.go.dev](https://pkg.go.dev/github.com/prometheus/prometheus). Due to peculiarities of Go Modules, v2.x.y will be displayed as v0.x.y.
* You will find a CircleCI configuration in [`.circleci/config.yml`](.circleci/config.yml).
* See the [Community page](https://prometheus.io/community) for how to reach the Prometheus developers and users on various communication channels.
@ -171,7 +180,6 @@ Refer to [CONTRIBUTING.md](https://github.com/prometheus/prometheus/blob/main/CO
Apache License 2.0, see [LICENSE](https://github.com/prometheus/prometheus/blob/main/LICENSE).
[hub]: https://hub.docker.com/r/prom/prometheus/
[circleci]: https://circleci.com/gh/prometheus/prometheus
[quay]: https://quay.io/repository/prometheus/prometheus

View file

@ -41,7 +41,9 @@ Release cadence of first pre-releases being cut is 6 weeks.
| v2.34 | 2022-02-23 | Chris Marchbanks (GitHub: @csmarchbanks) |
| v2.35 | 2022-04-06 | Augustin Husson (GitHub: @nexucis) |
| v2.36 | 2022-05-18 | Matthias Loibl (GitHub: @metalmatze) |
| v2.37 | 2022-06-29 | **searching for volunteer** |
| v2.37 LTS | 2022-06-29 | Julien Pivotto (GitHub: @roidelapluie) |
| v2.38 | 2022-08-10 | **searching for volunteer** |
| v2.39 | 2022-09-21 | **searching for volunteer** |
If you are interested in volunteering please create a pull request against the [prometheus/prometheus](https://github.com/prometheus/prometheus) repository and propose yourself for the release series of your choice.
@ -78,7 +80,10 @@ Maintaining the release branches for older minor releases happens on a best effo
A few days before a major or minor release, consider updating the dependencies.
Then create a pull request against the main branch.
Note that we use [Dependabot](.github/dependabot.yml) to continuously update most things automatically. Therefore, most dependencies should be up to date.
Check the [dependencies GitHub label](https://github.com/prometheus/prometheus/labels/dependencies) to see if there are any pending updates.
This bot currently does not manage `+incompatible` and `v0.0.0` in the version specifier for Go modules.
Note that after a dependency update, you should look out for any weirdness that
might have happened. Such weirdnesses include but are not limited to: flaky
@ -93,15 +98,17 @@ This is also a good time to consider any experimental features and feature
flags for promotion to stable or for deprecation or ultimately removal. Do any
of these in pull requests, one per feature.
#### Updating Go dependencies
#### Manually updating Go dependencies
```
This is usually only needed for `+incompatible` and `v0.0.0` non-semver updates.
```bash
make update-go-deps
git add go.mod go.sum
git commit -m "Update dependencies"
```
#### Updating React dependencies
#### Manually updating React dependencies
The React application recently moved to a monorepo system with multiple internal npm packages. Dependency upgrades are
quite sensitive for the time being.
@ -142,9 +149,9 @@ Entries in the `CHANGELOG.md` are meant to be in this order:
Tag the new release via the following commands:
```bash
$ tag="v$(< VERSION)"
$ git tag -s "${tag}" -m "${tag}"
$ git push origin "${tag}"
tag="v$(< VERSION)"
git tag -s "${tag}" -m "${tag}"
git push origin "${tag}"
```
Go modules versioning requires strict use of semver. Because we do not commit to
@ -154,9 +161,9 @@ the Prometheus server, we use major version zero releases for the libraries.
Tag the new library release via the following commands:
```bash
$ tag="v$(sed s/2/0/ < VERSION)"
$ git tag -s "${tag}" -m "${tag}"
$ git push origin "${tag}"
tag="v$(sed s/2/0/ < VERSION)"
git tag -s "${tag}" -m "${tag}"
git push origin "${tag}"
```
Optionally, you can use this handy `.gitconfig` alias.

View file

@ -3,4 +3,4 @@
The Prometheus security policy, including how to report vulnerabilities, can be
found here:
https://prometheus.io/docs/operating/security/
<https://prometheus.io/docs/operating/security/>

View file

@ -1 +1 @@
2.35.0-rc1
2.36.0

View file

@ -16,6 +16,7 @@ package main
import (
"context"
"errors"
"fmt"
"math"
"math/bits"
@ -38,7 +39,6 @@ import (
"github.com/grafana/regexp"
conntrack "github.com/mwitkow/go-conntrack"
"github.com/oklog/run"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/prometheus/common/promlog"
@ -387,6 +387,9 @@ func main() {
serverOnlyFlag(a, "query.max-samples", "Maximum number of samples a single query can load into memory. Note that queries will fail if they try to load more samples than this into memory, so this also limits the number of samples a query can return.").
Default("50000000").IntVar(&cfg.queryMaxSamples)
a.Flag("scrape.discovery-reload-interval", "Interval used by scrape manager to throttle target groups updates.").
Hidden().Default("5s").SetValue(&cfg.scrape.DiscoveryReloadInterval)
a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
Default("").StringsVar(&cfg.featureList)
@ -394,7 +397,7 @@ func main() {
_, err := a.Parse(os.Args[1:])
if err != nil {
fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Error parsing commandline arguments"))
fmt.Fprintln(os.Stderr, fmt.Errorf("Error parsing commandline arguments: %w", err))
a.Usage(os.Args[1:])
os.Exit(2)
}
@ -402,7 +405,7 @@ func main() {
logger := promlog.New(&cfg.promlogConfig)
if err := cfg.setFeatureListOptions(logger); err != nil {
fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Error parsing feature list"))
fmt.Fprintln(os.Stderr, fmt.Errorf("Error parsing feature list: %w", err))
os.Exit(1)
}
@ -423,13 +426,13 @@ func main() {
cfg.web.ExternalURL, err = computeExternalURL(cfg.prometheusURL, cfg.web.ListenAddress)
if err != nil {
fmt.Fprintln(os.Stderr, errors.Wrapf(err, "parse external URL %q", cfg.prometheusURL))
fmt.Fprintln(os.Stderr, fmt.Errorf("parse external URL %q: %w", cfg.prometheusURL, err))
os.Exit(2)
}
cfg.web.CORSOrigin, err = compileCORSRegexString(cfg.corsRegexString)
if err != nil {
fmt.Fprintln(os.Stderr, errors.Wrapf(err, "could not compile CORS regex string %q", cfg.corsRegexString))
fmt.Fprintln(os.Stderr, fmt.Errorf("could not compile CORS regex string %q: %w", cfg.corsRegexString, err))
os.Exit(2)
}
@ -522,7 +525,14 @@ func main() {
klogv2.ClampLevel(6)
klogv2.SetLogger(log.With(logger, "component", "k8s_client_runtime"))
level.Info(logger).Log("msg", "Starting Prometheus", "version", version.Info())
modeAppName := "Prometheus Server"
mode := "server"
if agentMode {
modeAppName = "Prometheus Agent"
mode = "agent"
}
level.Info(logger).Log("msg", "Starting "+modeAppName, "mode", mode, "version", version.Info())
if bits.UintSize < 64 {
level.Warn(logger).Log("msg", "This Prometheus binary has not been compiled for a 64-bit architecture. Due to virtual memory constraints of 32-bit systems, it is highly recommended to switch to a 64-bit binary of Prometheus.", "GOARCH", runtime.GOARCH)
}
@ -626,6 +636,7 @@ func main() {
cfg.web.Notifier = notifierManager
cfg.web.LookbackDelta = time.Duration(cfg.lookbackDelta)
cfg.web.IsAgent = agentMode
cfg.web.AppName = modeAppName
cfg.web.Version = &web.PrometheusVersion{
Version: version.Version,
@ -729,7 +740,7 @@ func main() {
fs, err := filepath.Glob(pat)
if err != nil {
// The only error can be a bad pattern.
return errors.Wrapf(err, "error retrieving rule files for %s", pat)
return fmt.Errorf("error retrieving rule files for %s: %w", pat, err)
}
files = append(files, fs...)
}
@ -804,6 +815,7 @@ func main() {
},
func(err error) {
close(cancel)
webHandler.SetReady(false)
},
)
}
@ -835,6 +847,19 @@ func main() {
},
)
}
if !agentMode {
// Rule manager.
g.Add(
func() error {
<-reloadReady.C
ruleManager.Run()
return nil
},
func(err error) {
ruleManager.Stop()
},
)
}
{
// Scrape manager.
g.Add(
@ -852,6 +877,8 @@ func main() {
func(err error) {
// Scrape manager needs to be stopped before closing the local TSDB
// so that it doesn't try to write samples to a closed storage.
// We should also wait for rule manager to be fully stopped to ensure
// we don't trigger any false positive alerts for rules using absent().
level.Info(logger).Log("msg", "Stopping scrape manager...")
scrapeManager.Stop()
},
@ -921,12 +948,12 @@ func main() {
}
if err := reloadConfig(cfg.configFile, cfg.enableExpandExternalLabels, cfg.tsdb.EnableExemplarStorage, logger, noStepSubqueryInterval, reloaders...); err != nil {
return errors.Wrapf(err, "error loading config from %q", cfg.configFile)
return fmt.Errorf("error loading config from %q: %w", cfg.configFile, err)
}
reloadReady.Close()
webHandler.Ready()
webHandler.SetReady(true)
level.Info(logger).Log("msg", "Server is ready to receive web requests.")
<-cancel
return nil
@ -937,18 +964,6 @@ func main() {
)
}
if !agentMode {
// Rule manager.
g.Add(
func() error {
<-reloadReady.C
ruleManager.Run()
return nil
},
func(err error) {
ruleManager.Stop()
},
)
// TSDB.
opts := cfg.tsdb.ToTSDBOptions()
cancel := make(chan struct{})
@ -968,7 +983,7 @@ func main() {
db, err := openDBWithMetrics(localStoragePath, logger, prometheus.DefaultRegisterer, &opts, localStorage.getStats())
if err != nil {
return errors.Wrapf(err, "opening storage failed")
return fmt.Errorf("opening storage failed: %w", err)
}
switch fsType := prom_runtime.Statfs(localStoragePath); fsType {
@ -1024,7 +1039,7 @@ func main() {
&opts,
)
if err != nil {
return errors.Wrap(err, "opening storage failed")
return fmt.Errorf("opening storage failed: %w", err)
}
switch fsType := prom_runtime.Statfs(localStoragePath); fsType {
@ -1062,7 +1077,7 @@ func main() {
g.Add(
func() error {
if err := webHandler.Run(ctxWeb, listener, *webConfig); err != nil {
return errors.Wrapf(err, "error starting web server")
return fmt.Errorf("error starting web server: %w", err)
}
return nil
},
@ -1172,7 +1187,7 @@ func reloadConfig(filename string, expandExternalLabels, enableExemplarStorage b
conf, err := config.LoadFile(filename, agentMode, expandExternalLabels, logger)
if err != nil {
return errors.Wrapf(err, "couldn't load configuration (--config.file=%q)", filename)
return fmt.Errorf("couldn't load configuration (--config.file=%q): %w", filename, err)
}
if enableExemplarStorage {
@ -1191,7 +1206,7 @@ func reloadConfig(filename string, expandExternalLabels, enableExemplarStorage b
timings = append(timings, rl.name, time.Since(rstart))
}
if failed {
return errors.Errorf("one or more errors occurred while applying the new configuration (--config.file=%q)", filename)
return fmt.Errorf("one or more errors occurred while applying the new configuration (--config.file=%q)", filename)
}
noStepSuqueryInterval.Set(conf.GlobalConfig.EvaluationInterval)

View file

@ -16,8 +16,9 @@ package main
import (
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
"io"
"math"
"os"
"os/exec"
@ -121,7 +122,8 @@ func TestFailedStartupExitCode(t *testing.T) {
err := prom.Run()
require.Error(t, err)
if exitError, ok := err.(*exec.ExitError); ok {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
status := exitError.Sys().(syscall.WaitStatus)
require.Equal(t, expectedExitStatus, status.ExitStatus())
} else {
@ -211,7 +213,7 @@ func TestWALSegmentSizeBounds(t *testing.T) {
stderr, err := prom.StderrPipe()
require.NoError(t, err)
go func() {
slurp, _ := ioutil.ReadAll(stderr)
slurp, _ := io.ReadAll(stderr)
t.Log(string(slurp))
}()
@ -233,7 +235,8 @@ func TestWALSegmentSizeBounds(t *testing.T) {
err = prom.Wait()
require.Error(t, err)
if exitError, ok := err.(*exec.ExitError); ok {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
status := exitError.Sys().(syscall.WaitStatus)
require.Equal(t, expectedExitStatus, status.ExitStatus())
} else {
@ -256,7 +259,7 @@ func TestMaxBlockChunkSegmentSizeBounds(t *testing.T) {
stderr, err := prom.StderrPipe()
require.NoError(t, err)
go func() {
slurp, _ := ioutil.ReadAll(stderr)
slurp, _ := io.ReadAll(stderr)
t.Log(string(slurp))
}()
@ -278,7 +281,8 @@ func TestMaxBlockChunkSegmentSizeBounds(t *testing.T) {
err = prom.Wait()
require.Error(t, err)
if exitError, ok := err.(*exec.ExitError); ok {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
status := exitError.Sys().(syscall.WaitStatus)
require.Equal(t, expectedExitStatus, status.ExitStatus())
} else {
@ -445,7 +449,7 @@ func TestModeSpecificFlags(t *testing.T) {
stderr, err := prom.StderrPipe()
require.NoError(t, err)
go func() {
slurp, _ := ioutil.ReadAll(stderr)
slurp, _ := io.ReadAll(stderr)
t.Log(string(slurp))
}()
@ -467,7 +471,8 @@ func TestModeSpecificFlags(t *testing.T) {
err = prom.Wait()
require.Error(t, err)
if exitError, ok := err.(*exec.ExitError); ok {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
status := exitError.Sys().(syscall.WaitStatus)
require.Equal(t, tc.exitStatus, status.ExitStatus())
} else {

View file

@ -17,7 +17,7 @@ import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net"
"net/http"
"net/url"
@ -235,10 +235,10 @@ func (p *queryLogTest) run(t *testing.T) {
p.skip(t)
// Setup temporary files for this test.
queryLogFile, err := ioutil.TempFile("", "query")
queryLogFile, err := os.CreateTemp("", "query")
require.NoError(t, err)
defer os.Remove(queryLogFile.Name())
p.configFile, err = ioutil.TempFile("", "config")
p.configFile, err = os.CreateTemp("", "config")
require.NoError(t, err)
defer os.Remove(p.configFile.Name())
@ -269,7 +269,7 @@ func (p *queryLogTest) run(t *testing.T) {
wg.Add(1)
defer wg.Wait()
go func() {
slurp, _ := ioutil.ReadAll(stderr)
slurp, _ := io.ReadAll(stderr)
t.Log(string(slurp))
wg.Done()
}()
@ -333,7 +333,7 @@ func (p *queryLogTest) run(t *testing.T) {
return
}
// Move the file, Prometheus should still write to the old file.
newFile, err := ioutil.TempFile("", "newLoc")
newFile, err := os.CreateTemp("", "newLoc")
require.NoError(t, err)
require.NoError(t, newFile.Close())
defer os.Remove(newFile.Name())

View file

@ -16,9 +16,8 @@ package main
import (
"archive/tar"
"compress/gzip"
"fmt"
"os"
"github.com/pkg/errors"
)
const filePerm = 0o666
@ -32,7 +31,7 @@ type tarGzFileWriter struct {
func newTarGzFileWriter(archiveName string) (*tarGzFileWriter, error) {
file, err := os.Create(archiveName)
if err != nil {
return nil, errors.Wrapf(err, "error creating archive %q", archiveName)
return nil, fmt.Errorf("error creating archive %q: %w", archiveName, err)
}
gzw := gzip.NewWriter(file)
tw := tar.NewWriter(gzw)

View file

@ -15,12 +15,13 @@ package main
import (
"context"
"errors"
"fmt"
"io"
"math"
"time"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/textparse"
@ -33,11 +34,11 @@ func getMinAndMaxTimestamps(p textparse.Parser) (int64, int64, error) {
for {
entry, err := p.Next()
if err == io.EOF {
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return 0, 0, errors.Wrap(err, "next")
return 0, 0, fmt.Errorf("next: %w", err)
}
if entry != textparse.EntrySeries {
@ -46,7 +47,7 @@ func getMinAndMaxTimestamps(p textparse.Parser) (int64, int64, error) {
_, ts, _ := p.Series()
if ts == nil {
return 0, 0, errors.Errorf("expected timestamp for series got none")
return 0, 0, fmt.Errorf("expected timestamp for series got none")
}
if *ts > maxt {
@ -118,7 +119,7 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn
// original interval later.
w, err := tsdb.NewBlockWriter(log.NewNopLogger(), outputDir, 2*blockDuration)
if err != nil {
return errors.Wrap(err, "block writer")
return fmt.Errorf("block writer: %w", err)
}
defer func() {
err = tsdb_errors.NewMulti(err, w.Close()).Err()
@ -130,11 +131,11 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn
samplesCount := 0
for {
e, err := p.Next()
if err == io.EOF {
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return errors.Wrap(err, "parse")
return fmt.Errorf("parse: %w", err)
}
if e != textparse.EntrySeries {
continue
@ -144,7 +145,7 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn
if ts == nil {
l := labels.Labels{}
p.Metric(&l)
return errors.Errorf("expected timestamp for series %v, got none", l)
return fmt.Errorf("expected timestamp for series %v, got none", l)
}
if *ts < t {
continue
@ -160,7 +161,7 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn
p.Metric(&l)
if _, err := app.Append(0, l, *ts, v); err != nil {
return errors.Wrap(err, "add sample")
return fmt.Errorf("add sample: %w", err)
}
samplesCount++
@ -172,7 +173,7 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn
// Therefore the old appender is committed and a new one is created.
// This prevents keeping too many samples lined up in an appender and thus in RAM.
if err := app.Commit(); err != nil {
return errors.Wrap(err, "commit")
return fmt.Errorf("commit: %w", err)
}
app = w.Appender(ctx)
@ -180,18 +181,18 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn
}
if err := app.Commit(); err != nil {
return errors.Wrap(err, "commit")
return fmt.Errorf("commit: %w", err)
}
block, err := w.Flush(ctx)
switch err {
case nil:
switch {
case err == nil:
if quiet {
break
}
blocks, err := db.Blocks()
if err != nil {
return errors.Wrap(err, "get blocks")
return fmt.Errorf("get blocks: %w", err)
}
for _, b := range blocks {
if b.Meta().ULID == block {
@ -200,15 +201,15 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn
break
}
}
case tsdb.ErrNoSeriesAppended:
case errors.Is(err, tsdb.ErrNoSeriesAppended):
default:
return errors.Wrap(err, "flush")
return fmt.Errorf("flush: %w", err)
}
return nil
}()
if err != nil {
return errors.Wrap(err, "process blocks")
return fmt.Errorf("process blocks: %w", err)
}
}
return nil
@ -218,7 +219,10 @@ func backfill(maxSamplesInAppender int, input []byte, outputDir string, humanRea
p := textparse.NewOpenMetricsParser(input)
maxt, mint, err := getMinAndMaxTimestamps(p)
if err != nil {
return errors.Wrap(err, "getting min and max timestamp")
return fmt.Errorf("getting min and max timestamp: %w", err)
}
return errors.Wrap(createBlocks(input, mint, maxt, int64(maxBlockDuration/time.Millisecond), maxSamplesInAppender, outputDir, humanReadable, quiet), "block creation")
if err = createBlocks(input, mint, maxt, int64(maxBlockDuration/time.Millisecond), maxSamplesInAppender, outputDir, humanReadable, quiet); err != nil {
return fmt.Errorf("block creation: %w", err)
}
return nil
}

View file

@ -15,10 +15,8 @@ package main
import (
"fmt"
"io/ioutil"
"io"
"net/http"
"github.com/pkg/errors"
)
type debugWriterConfig struct {
@ -30,7 +28,7 @@ type debugWriterConfig struct {
func debugWrite(cfg debugWriterConfig) error {
archiver, err := newTarGzFileWriter(cfg.tarballName)
if err != nil {
return errors.Wrap(err, "error creating a new archiver")
return fmt.Errorf("error creating a new archiver: %w", err)
}
for _, endPointGroup := range cfg.endPointGroups {
@ -39,28 +37,28 @@ func debugWrite(cfg debugWriterConfig) error {
fmt.Println("collecting:", url)
res, err := http.Get(url)
if err != nil {
return errors.Wrap(err, "error executing HTTP request")
return fmt.Errorf("error executing HTTP request: %w", err)
}
body, err := ioutil.ReadAll(res.Body)
body, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
return errors.Wrap(err, "error reading the response body")
return fmt.Errorf("error reading the response body: %w", err)
}
if endPointGroup.postProcess != nil {
body, err = endPointGroup.postProcess(body)
if err != nil {
return errors.Wrap(err, "error post-processing HTTP response body")
return fmt.Errorf("error post-processing HTTP response body: %w", err)
}
}
if err := archiver.write(filename, body); err != nil {
return errors.Wrap(err, "error writing into the archive")
return fmt.Errorf("error writing into the archive: %w", err)
}
}
}
if err := archiver.close(); err != nil {
return errors.Wrap(err, "error closing archive writer")
return fmt.Errorf("error closing archive writer: %w", err)
}
fmt.Printf("Compiling debug information complete, all files written in %q.\n", cfg.tarballName)

View file

@ -17,9 +17,9 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"net/http"
"net/url"
@ -33,7 +33,6 @@ import (
"github.com/go-kit/log"
"github.com/google/pprof/profile"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/api"
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/client_golang/prometheus/promhttp"
@ -96,6 +95,9 @@ func main() {
"lint",
"Linting checks to apply to the rules specified in the config. Available options are: "+strings.Join(lintOptions, ", ")+". Use --lint=none to disable linting",
).Default(lintOptionDuplicateRules).String()
checkConfigLintFatal := checkConfigCmd.Flag(
"lint-fatal",
"Make lint errors exit with exit code 3.").Default("false").Bool()
checkWebConfigCmd := checkCmd.Command("web-config", "Check if the web config files are valid or not.")
webConfigFiles := checkWebConfigCmd.Arg(
@ -112,6 +114,9 @@ func main() {
"lint",
"Linting checks to apply. Available options are: "+strings.Join(lintOptions, ", ")+". Use --lint=none to disable linting",
).Default(lintOptionDuplicateRules).String()
checkRulesLintFatal := checkRulesCmd.Flag(
"lint-fatal",
"Make lint errors exit with exit code 3.").Default("false").Bool()
checkMetricsCmd := checkCmd.Command("metrics", checkMetricsUsage)
checkMetricsExtended := checkCmd.Flag("extended", "Print extended information related to the cardinality of the metrics.").Bool()
@ -152,6 +157,7 @@ func main() {
queryLabelsName := queryLabelsCmd.Arg("name", "Label name to provide label values for.").Required().String()
queryLabelsBegin := queryLabelsCmd.Flag("start", "Start time (RFC3339 or Unix timestamp).").String()
queryLabelsEnd := queryLabelsCmd.Flag("end", "End time (RFC3339 or Unix timestamp).").String()
queryLabelsMatch := queryLabelsCmd.Flag("match", "Series selector. Can be specified multiple times.").Strings()
testCmd := app.Command("test", "Unit testing.")
testRulesCmd := testCmd.Command("rules", "Unit tests for rules.")
@ -236,13 +242,13 @@ func main() {
os.Exit(CheckSD(*sdConfigFile, *sdJobName, *sdTimeout))
case checkConfigCmd.FullCommand():
os.Exit(CheckConfig(*agentMode, *checkConfigSyntaxOnly, newLintConfig(*checkConfigLint), *configFiles...))
os.Exit(CheckConfig(*agentMode, *checkConfigSyntaxOnly, newLintConfig(*checkConfigLint, *checkConfigLintFatal), *configFiles...))
case checkWebConfigCmd.FullCommand():
os.Exit(CheckWebConfig(*webConfigFiles...))
case checkRulesCmd.FullCommand():
os.Exit(CheckRules(newLintConfig(*checkRulesLint), *ruleFiles...))
os.Exit(CheckRules(newLintConfig(*checkRulesLint, *checkRulesLintFatal), *ruleFiles...))
case checkMetricsCmd.FullCommand():
os.Exit(CheckMetrics(*checkMetricsExtended))
@ -266,7 +272,7 @@ func main() {
os.Exit(debugAll(*debugAllServer))
case queryLabelsCmd.FullCommand():
os.Exit(QueryLabels(*queryLabelsServer, *queryLabelsName, *queryLabelsBegin, *queryLabelsEnd, p))
os.Exit(QueryLabels(*queryLabelsServer, *queryLabelsMatch, *queryLabelsName, *queryLabelsBegin, *queryLabelsEnd, p))
case testRulesCmd.FullCommand():
os.Exit(RulesUnitTest(
@ -303,11 +309,14 @@ var lintError = fmt.Errorf("lint error")
type lintConfig struct {
all bool
duplicateRules bool
fatal bool
}
func newLintConfig(stringVal string) lintConfig {
func newLintConfig(stringVal string, fatal bool) lintConfig {
items := strings.Split(stringVal, ",")
ls := lintConfig{}
ls := lintConfig{
fatal: fatal,
}
for _, setting := range items {
switch setting {
case lintOptionAll:
@ -329,7 +338,7 @@ func (ls lintConfig) lintDuplicateRules() bool {
// CheckConfig validates configuration files.
func CheckConfig(agentMode, checkSyntaxOnly bool, lintSettings lintConfig, files ...string) int {
failed := false
hasLintErrors := false
hasErrors := false
for _, f := range files {
ruleFiles, err := checkConfig(agentMode, f, checkSyntaxOnly)
@ -352,7 +361,7 @@ func CheckConfig(agentMode, checkSyntaxOnly bool, lintSettings lintConfig, files
}
failed = true
for _, err := range errs {
hasLintErrors = hasLintErrors || errors.Is(err, lintError)
hasErrors = hasErrors || !errors.Is(err, lintError)
}
} else {
fmt.Printf(" SUCCESS: %d rules found\n", n)
@ -360,12 +369,12 @@ func CheckConfig(agentMode, checkSyntaxOnly bool, lintSettings lintConfig, files
fmt.Println()
}
}
if failed && hasLintErrors {
return lintErrExitCode
}
if failed {
if failed && hasErrors {
return failureExitCode
}
if failed && lintSettings.fatal {
return lintErrExitCode
}
return successExitCode
}
@ -414,10 +423,10 @@ func checkConfig(agentMode bool, filename string, checkSyntaxOnly bool) ([]strin
// If an explicit file was given, error if it is not accessible.
if !strings.Contains(rf, "*") {
if len(rfs) == 0 {
return nil, errors.Errorf("%q does not point to an existing file", rf)
return nil, fmt.Errorf("%q does not point to an existing file", rf)
}
if err := checkFileExists(rfs[0]); err != nil {
return nil, errors.Wrapf(err, "error checking rule file %q", rfs[0])
return nil, fmt.Errorf("error checking rule file %q: %w", rfs[0], err)
}
}
ruleFiles = append(ruleFiles, rfs...)
@ -427,7 +436,7 @@ func checkConfig(agentMode bool, filename string, checkSyntaxOnly bool) ([]strin
for _, scfg := range cfg.ScrapeConfigs {
if !checkSyntaxOnly && scfg.HTTPClientConfig.Authorization != nil {
if err := checkFileExists(scfg.HTTPClientConfig.Authorization.CredentialsFile); err != nil {
return nil, errors.Wrapf(err, "error checking authorization credentials or bearer token file %q", scfg.HTTPClientConfig.Authorization.CredentialsFile)
return nil, fmt.Errorf("error checking authorization credentials or bearer token file %q: %w", scfg.HTTPClientConfig.Authorization.CredentialsFile, err)
}
}
@ -455,7 +464,7 @@ func checkConfig(agentMode bool, filename string, checkSyntaxOnly bool) ([]strin
var targetGroups []*targetgroup.Group
targetGroups, err = checkSDFile(f)
if err != nil {
return nil, errors.Errorf("checking SD file %q: %v", file, err)
return nil, fmt.Errorf("checking SD file %q: %w", file, err)
}
if err := checkTargetGroupsForScrapeConfig(targetGroups, scfg); err != nil {
return nil, err
@ -491,7 +500,7 @@ func checkConfig(agentMode bool, filename string, checkSyntaxOnly bool) ([]strin
var targetGroups []*targetgroup.Group
targetGroups, err = checkSDFile(f)
if err != nil {
return nil, errors.Errorf("checking SD file %q: %v", file, err)
return nil, fmt.Errorf("checking SD file %q: %w", file, err)
}
if err := checkTargetGroupsForAlertmanager(targetGroups, amcfg); err != nil {
@ -514,10 +523,10 @@ func checkConfig(agentMode bool, filename string, checkSyntaxOnly bool) ([]strin
func checkTLSConfig(tlsConfig config_util.TLSConfig, checkSyntaxOnly bool) error {
if len(tlsConfig.CertFile) > 0 && len(tlsConfig.KeyFile) == 0 {
return errors.Errorf("client cert file %q specified without client key file", tlsConfig.CertFile)
return fmt.Errorf("client cert file %q specified without client key file", tlsConfig.CertFile)
}
if len(tlsConfig.KeyFile) > 0 && len(tlsConfig.CertFile) == 0 {
return errors.Errorf("client key file %q specified without client cert file", tlsConfig.KeyFile)
return fmt.Errorf("client key file %q specified without client cert file", tlsConfig.KeyFile)
}
if checkSyntaxOnly {
@ -525,10 +534,10 @@ func checkTLSConfig(tlsConfig config_util.TLSConfig, checkSyntaxOnly bool) error
}
if err := checkFileExists(tlsConfig.CertFile); err != nil {
return errors.Wrapf(err, "error checking client cert file %q", tlsConfig.CertFile)
return fmt.Errorf("error checking client cert file %q: %w", tlsConfig.CertFile, err)
}
if err := checkFileExists(tlsConfig.KeyFile); err != nil {
return errors.Wrapf(err, "error checking client key file %q", tlsConfig.KeyFile)
return fmt.Errorf("error checking client key file %q: %w", tlsConfig.KeyFile, err)
}
return nil
@ -541,7 +550,7 @@ func checkSDFile(filename string) ([]*targetgroup.Group, error) {
}
defer fd.Close()
content, err := ioutil.ReadAll(fd)
content, err := io.ReadAll(fd)
if err != nil {
return nil, err
}
@ -558,12 +567,12 @@ func checkSDFile(filename string) ([]*targetgroup.Group, error) {
return nil, err
}
default:
return nil, errors.Errorf("invalid file extension: %q", ext)
return nil, fmt.Errorf("invalid file extension: %q", ext)
}
for i, tg := range targetGroups {
if tg == nil {
return nil, errors.Errorf("nil target group item found (index %d)", i)
return nil, fmt.Errorf("nil target group item found (index %d)", i)
}
}
@ -573,7 +582,7 @@ func checkSDFile(filename string) ([]*targetgroup.Group, error) {
// CheckRules validates rule files.
func CheckRules(ls lintConfig, files ...string) int {
failed := false
hasLintErrors := false
hasErrors := false
for _, f := range files {
if n, errs := checkRules(f, ls); errs != nil {
@ -583,19 +592,19 @@ func CheckRules(ls lintConfig, files ...string) int {
}
failed = true
for _, err := range errs {
hasLintErrors = hasLintErrors || errors.Is(err, lintError)
hasErrors = hasErrors || !errors.Is(err, lintError)
}
} else {
fmt.Printf(" SUCCESS: %d rules found\n", n)
}
fmt.Println()
}
if failed && hasLintErrors {
return lintErrExitCode
}
if failed {
if failed && hasErrors {
return failureExitCode
}
if failed && ls.fatal {
return lintErrExitCode
}
return successExitCode
}
@ -930,7 +939,7 @@ func QuerySeries(url *url.URL, matchers []string, start, end string, p printer)
}
// QueryLabels queries for label values against a Prometheus server.
func QueryLabels(url *url.URL, name, start, end string, p printer) int {
func QueryLabels(url *url.URL, matchers []string, name, start, end string, p printer) int {
if url.Scheme == "" {
url.Scheme = "http"
}
@ -954,7 +963,7 @@ func QueryLabels(url *url.URL, name, start, end string, p printer) int {
// Run query against client.
api := v1.NewAPI(c)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
val, warn, err := api.LabelValues(ctx, name, []string{}, stime, etime)
val, warn, err := api.LabelValues(ctx, name, matchers, stime, etime)
cancel()
for _, v := range warn {
@ -992,14 +1001,14 @@ func parseStartTimeAndEndTime(start, end string) (time.Time, time.Time, error) {
if start != "" {
stime, err = parseTime(start)
if err != nil {
return stime, etime, errors.Wrap(err, "error parsing start time")
return stime, etime, fmt.Errorf("error parsing start time: %w", err)
}
}
if end != "" {
etime, err = parseTime(end)
if err != nil {
return stime, etime, errors.Wrap(err, "error parsing end time")
return stime, etime, fmt.Errorf("error parsing end time: %w", err)
}
}
return stime, etime, nil
@ -1013,7 +1022,7 @@ func parseTime(s string) (time.Time, error) {
if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
return t, nil
}
return time.Time{}, errors.Errorf("cannot parse %q to a valid timestamp", s)
return time.Time{}, fmt.Errorf("cannot parse %q to a valid timestamp", s)
}
type endpointsGroup struct {
@ -1039,7 +1048,7 @@ var (
}
var buf bytes.Buffer
if err := p.WriteUncompressed(&buf); err != nil {
return nil, errors.Wrap(err, "writing the profile to the buffer")
return nil, fmt.Errorf("writing the profile to the buffer: %w", err)
}
return buf.Bytes(), nil
@ -1149,13 +1158,13 @@ func importRules(url *url.URL, start, end, outputDir string, evalInterval, maxBl
} else {
etime, err = parseTime(end)
if err != nil {
return fmt.Errorf("error parsing end time: %v", err)
return fmt.Errorf("error parsing end time: %w", err)
}
}
stime, err = parseTime(start)
if err != nil {
return fmt.Errorf("error parsing start time: %v", err)
return fmt.Errorf("error parsing start time: %w", err)
}
if !stime.Before(etime) {
@ -1173,14 +1182,14 @@ func importRules(url *url.URL, start, end, outputDir string, evalInterval, maxBl
Address: url.String(),
})
if err != nil {
return fmt.Errorf("new api client error: %v", err)
return fmt.Errorf("new api client error: %w", err)
}
ruleImporter := newRuleImporter(log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)), cfg, v1.NewAPI(client))
errs := ruleImporter.loadGroups(ctx, files)
for _, err := range errs {
if err != nil {
return fmt.Errorf("rule importer parse error: %v", err)
return fmt.Errorf("rule importer parse error: %w", err)
}
}

View file

@ -20,7 +20,6 @@ import (
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/common/model"
@ -122,7 +121,7 @@ func (importer *ruleImporter) importRule(ctx context.Context, ruleExpr, ruleName
},
)
if err != nil {
return errors.Wrap(err, "query range")
return fmt.Errorf("query range: %w", err)
}
if warnings != nil {
level.Warn(importer.logger).Log("msg", "Range query returned warnings.", "warnings", warnings)
@ -136,7 +135,7 @@ func (importer *ruleImporter) importRule(ctx context.Context, ruleExpr, ruleName
// original interval later.
w, err := tsdb.NewBlockWriter(log.NewNopLogger(), importer.config.outputDir, 2*blockDuration)
if err != nil {
return errors.Wrap(err, "new block writer")
return fmt.Errorf("new block writer: %w", err)
}
var closed bool
defer func() {
@ -167,7 +166,7 @@ func (importer *ruleImporter) importRule(ctx context.Context, ruleExpr, ruleName
for _, value := range sample.Values {
if err := app.add(ctx, lb.Labels(), timestamp.FromTime(value.Timestamp.Time()), float64(value.Value)); err != nil {
return errors.Wrap(err, "add")
return fmt.Errorf("add: %w", err)
}
}
}
@ -176,7 +175,7 @@ func (importer *ruleImporter) importRule(ctx context.Context, ruleExpr, ruleName
}
if err := app.flushAndCommit(ctx); err != nil {
return errors.Wrap(err, "flush and commit")
return fmt.Errorf("flush and commit: %w", err)
}
err = tsdb_errors.NewMulti(err, w.Close()).Err()
closed = true
@ -204,7 +203,7 @@ type multipleAppender struct {
func (m *multipleAppender) add(ctx context.Context, l labels.Labels, t int64, v float64) error {
if _, err := m.appender.Append(0, l, t, v); err != nil {
return errors.Wrap(err, "multiappender append")
return fmt.Errorf("multiappender append: %w", err)
}
m.currentSampleCount++
if m.currentSampleCount >= m.maxSamplesInMemory {
@ -218,7 +217,7 @@ func (m *multipleAppender) commit(ctx context.Context) error {
return nil
}
if err := m.appender.Commit(); err != nil {
return errors.Wrap(err, "multiappender commit")
return fmt.Errorf("multiappender commit: %w", err)
}
m.appender = m.writer.Appender(ctx)
m.currentSampleCount = 0
@ -230,7 +229,7 @@ func (m *multipleAppender) flushAndCommit(ctx context.Context) error {
return err
}
if _, err := m.writer.Flush(ctx); err != nil {
return errors.Wrap(err, "multiappender flush")
return fmt.Errorf("multiappender flush: %w", err)
}
return nil
}

View file

@ -15,8 +15,8 @@ package main
import (
"context"
"io/ioutil"
"math"
"os"
"path/filepath"
"testing"
"time"
@ -188,7 +188,7 @@ func createSingleRuleTestFiles(path string) error {
labels:
testlabel11: testlabelvalue11
`
return ioutil.WriteFile(path, []byte(recordingRules), 0o777)
return os.WriteFile(path, []byte(recordingRules), 0o777)
}
func createMultiRuleTestFiles(path string) error {
@ -208,7 +208,7 @@ func createMultiRuleTestFiles(path string) error {
labels:
testlabel11: testlabelvalue13
`
return ioutil.WriteFile(path, []byte(recordingRules), 0o777)
return os.WriteFile(path, []byte(recordingRules), 0o777)
}
// TestBackfillLabels confirms that the labels in the rule file override the labels from the metrics
@ -236,7 +236,7 @@ func TestBackfillLabels(t *testing.T) {
labels:
name1: value-from-rule
`
require.NoError(t, ioutil.WriteFile(path, []byte(recordingRules), 0o777))
require.NoError(t, os.WriteFile(path, []byte(recordingRules), 0o777))
errs := ruleImporter.loadGroups(ctx, []string{path})
for _, err := range errs {
require.NoError(t, err)

View file

@ -35,7 +35,6 @@ import (
"github.com/alecthomas/units"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/tsdb"
@ -236,29 +235,29 @@ func (b *writeBenchmark) startProfiling() error {
// Start CPU profiling.
b.cpuprof, err = os.Create(filepath.Join(b.outPath, "cpu.prof"))
if err != nil {
return fmt.Errorf("bench: could not create cpu profile: %v", err)
return fmt.Errorf("bench: could not create cpu profile: %w", err)
}
if err := pprof.StartCPUProfile(b.cpuprof); err != nil {
return fmt.Errorf("bench: could not start CPU profile: %v", err)
return fmt.Errorf("bench: could not start CPU profile: %w", err)
}
// Start memory profiling.
b.memprof, err = os.Create(filepath.Join(b.outPath, "mem.prof"))
if err != nil {
return fmt.Errorf("bench: could not create memory profile: %v", err)
return fmt.Errorf("bench: could not create memory profile: %w", err)
}
runtime.MemProfileRate = 64 * 1024
// Start fatal profiling.
b.blockprof, err = os.Create(filepath.Join(b.outPath, "block.prof"))
if err != nil {
return fmt.Errorf("bench: could not create block profile: %v", err)
return fmt.Errorf("bench: could not create block profile: %w", err)
}
runtime.SetBlockProfileRate(20)
b.mtxprof, err = os.Create(filepath.Join(b.outPath, "mutex.prof"))
if err != nil {
return fmt.Errorf("bench: could not create mutex profile: %v", err)
return fmt.Errorf("bench: could not create mutex profile: %w", err)
}
runtime.SetMutexProfileFraction(20)
return nil
@ -272,14 +271,14 @@ func (b *writeBenchmark) stopProfiling() error {
}
if b.memprof != nil {
if err := pprof.Lookup("heap").WriteTo(b.memprof, 0); err != nil {
return fmt.Errorf("error writing mem profile: %v", err)
return fmt.Errorf("error writing mem profile: %w", err)
}
b.memprof.Close()
b.memprof = nil
}
if b.blockprof != nil {
if err := pprof.Lookup("block").WriteTo(b.blockprof, 0); err != nil {
return fmt.Errorf("error writing block profile: %v", err)
return fmt.Errorf("error writing block profile: %w", err)
}
b.blockprof.Close()
b.blockprof = nil
@ -287,7 +286,7 @@ func (b *writeBenchmark) stopProfiling() error {
}
if b.mtxprof != nil {
if err := pprof.Lookup("mutex").WriteTo(b.mtxprof, 0); err != nil {
return fmt.Errorf("error writing mutex profile: %v", err)
return fmt.Errorf("error writing mutex profile: %w", err)
}
b.mtxprof.Close()
b.mtxprof = nil
@ -680,7 +679,7 @@ func backfillOpenMetrics(path, outputDir string, humanReadable, quiet bool, maxB
defer inputFile.Close()
if err := os.MkdirAll(outputDir, 0o777); err != nil {
return checkErr(errors.Wrap(err, "create output dir"))
return checkErr(fmt.Errorf("create output dir: %w", err))
}
return checkErr(backfill(5000, inputFile.Bytes(), outputDir, humanReadable, quiet, maxBlockDuration))

View file

@ -15,8 +15,8 @@ package main
import (
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
@ -26,7 +26,6 @@ import (
"time"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
yaml "gopkg.in/yaml.v2"
@ -64,7 +63,7 @@ func RulesUnitTest(queryOpts promql.LazyLoaderOpts, files ...string) int {
func ruleUnitTest(filename string, queryOpts promql.LazyLoaderOpts) []error {
fmt.Println("Unit Testing: ", filename)
b, err := ioutil.ReadFile(filename)
b, err := os.ReadFile(filename)
if err != nil {
return []error{err}
}
@ -88,7 +87,7 @@ func ruleUnitTest(filename string, queryOpts promql.LazyLoaderOpts) []error {
groupOrderMap := make(map[string]int)
for i, gn := range unitTestInp.GroupEvalOrder {
if _, ok := groupOrderMap[gn]; ok {
return []error{errors.Errorf("group name repeated in evaluation order: %s", gn)}
return []error{fmt.Errorf("group name repeated in evaluation order: %s", gn)}
}
groupOrderMap[gn] = i
}
@ -196,7 +195,7 @@ func (tg *testGroup) test(evalInterval time.Duration, groupOrderMap map[string]i
if tg.TestGroupName != "" {
testGroupLog = fmt.Sprintf(" (in TestGroup %s)", tg.TestGroupName)
}
return []error{errors.Errorf("an item under alert_rule_test misses required attribute alertname at eval_time %v%s", alert.EvalTime, testGroupLog)}
return []error{fmt.Errorf("an item under alert_rule_test misses required attribute alertname at eval_time %v%s", alert.EvalTime, testGroupLog)}
}
alertEvalTimesMap[alert.EvalTime] = struct{}{}
@ -241,7 +240,7 @@ func (tg *testGroup) test(evalInterval time.Duration, groupOrderMap map[string]i
g.Eval(suite.Context(), ts)
for _, r := range g.Rules() {
if r.LastError() != nil {
evalErrs = append(evalErrs, errors.Errorf(" rule: %s, time: %s, err: %v",
evalErrs = append(evalErrs, fmt.Errorf(" rule: %s, time: %s, err: %v",
r.Name(), ts.Sub(time.Unix(0, 0).UTC()), r.LastError()))
}
}
@ -324,7 +323,7 @@ func (tg *testGroup) test(evalInterval time.Duration, groupOrderMap map[string]i
}
expString := indentLines(expAlerts.String(), " ")
gotString := indentLines(gotAlerts.String(), " ")
errs = append(errs, errors.Errorf("%s alertname: %s, time: %s, \n exp:%v, \n got:%v",
errs = append(errs, fmt.Errorf("%s alertname: %s, time: %s, \n exp:%v, \n got:%v",
testName, testcase.Alertname, testcase.EvalTime.String(), expString, gotString))
}
}
@ -339,7 +338,7 @@ Outer:
got, err := query(suite.Context(), testCase.Expr, mint.Add(time.Duration(testCase.EvalTime)),
suite.QueryEngine(), suite.Queryable())
if err != nil {
errs = append(errs, errors.Errorf(" expr: %q, time: %s, err: %s", testCase.Expr,
errs = append(errs, fmt.Errorf(" expr: %q, time: %s, err: %s", testCase.Expr,
testCase.EvalTime.String(), err.Error()))
continue
}
@ -356,9 +355,9 @@ Outer:
for _, s := range testCase.ExpSamples {
lb, err := parser.ParseMetric(s.Labels)
if err != nil {
err = errors.Wrapf(err, "labels %q", s.Labels)
errs = append(errs, errors.Errorf(" expr: %q, time: %s, err: %s", testCase.Expr,
testCase.EvalTime.String(), err.Error()))
err = fmt.Errorf("labels %q: %w", s.Labels, err)
errs = append(errs, fmt.Errorf(" expr: %q, time: %s, err: %w", testCase.Expr,
testCase.EvalTime.String(), err))
continue Outer
}
expSamples = append(expSamples, parsedSample{
@ -374,7 +373,7 @@ Outer:
return labels.Compare(gotSamples[i].Labels, gotSamples[j].Labels) <= 0
})
if !reflect.DeepEqual(expSamples, gotSamples) {
errs = append(errs, errors.Errorf(" expr: %q, time: %s,\n exp: %v\n got: %v", testCase.Expr,
errs = append(errs, fmt.Errorf(" expr: %q, time: %s,\n exp: %v\n got: %v", testCase.Expr,
testCase.EvalTime.String(), parsedSamplesString(expSamples), parsedSamplesString(gotSamples)))
}
}

View file

@ -14,8 +14,8 @@
package config
import (
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
@ -26,7 +26,6 @@ import (
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/grafana/regexp"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/sigv4"
@ -103,13 +102,13 @@ func Load(s string, expandExternalLabels bool, logger log.Logger) (*Config, erro
// LoadFile parses the given YAML file into a Config.
func LoadFile(filename string, agentMode, expandExternalLabels bool, logger log.Logger) (*Config, error) {
content, err := ioutil.ReadFile(filename)
content, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
cfg, err := Load(string(content), expandExternalLabels, logger)
if err != nil {
return nil, errors.Wrapf(err, "parsing YAML file %s", filename)
return nil, fmt.Errorf("parsing YAML file %s: %w", filename, err)
}
if agentMode {
@ -277,7 +276,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
for _, rf := range c.RuleFiles {
if !patRulePath.MatchString(rf) {
return errors.Errorf("invalid rule file path %q", rf)
return fmt.Errorf("invalid rule file path %q", rf)
}
}
// Do global overrides and validate unique names.
@ -292,7 +291,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
scfg.ScrapeInterval = c.GlobalConfig.ScrapeInterval
}
if scfg.ScrapeTimeout > scfg.ScrapeInterval {
return errors.Errorf("scrape timeout greater than scrape interval for scrape config with job name %q", scfg.JobName)
return fmt.Errorf("scrape timeout greater than scrape interval for scrape config with job name %q", scfg.JobName)
}
if scfg.ScrapeTimeout == 0 {
if c.GlobalConfig.ScrapeTimeout > scfg.ScrapeInterval {
@ -303,7 +302,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
if _, ok := jobNames[scfg.JobName]; ok {
return errors.Errorf("found multiple scrape configs with job name %q", scfg.JobName)
return fmt.Errorf("found multiple scrape configs with job name %q", scfg.JobName)
}
jobNames[scfg.JobName] = struct{}{}
}
@ -314,7 +313,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
// Skip empty names, we fill their name with their config hash in remote write code.
if _, ok := rwNames[rwcfg.Name]; ok && rwcfg.Name != "" {
return errors.Errorf("found multiple remote write configs with job name %q", rwcfg.Name)
return fmt.Errorf("found multiple remote write configs with job name %q", rwcfg.Name)
}
rwNames[rwcfg.Name] = struct{}{}
}
@ -325,7 +324,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
// Skip empty names, we fill their name with their config hash in remote read code.
if _, ok := rrNames[rrcfg.Name]; ok && rrcfg.Name != "" {
return errors.Errorf("found multiple remote read configs with job name %q", rrcfg.Name)
return fmt.Errorf("found multiple remote read configs with job name %q", rrcfg.Name)
}
rrNames[rrcfg.Name] = struct{}{}
}
@ -364,10 +363,10 @@ func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
for _, l := range gc.ExternalLabels {
if !model.LabelName(l.Name).IsValid() {
return errors.Errorf("%q is not a valid label name", l.Name)
return fmt.Errorf("%q is not a valid label name", l.Name)
}
if !model.LabelValue(l.Value).IsValid() {
return errors.Errorf("%q is not a valid label value", l.Value)
return fmt.Errorf("%q is not a valid label value", l.Value)
}
}
@ -742,7 +741,7 @@ func checkStaticTargets(configs discovery.Configs) error {
func CheckTargetAddress(address model.LabelValue) error {
// For now check for a URL, we may want to expand this later.
if strings.Contains(string(address), "/") {
return errors.Errorf("%q is not a valid hostname", address)
return fmt.Errorf("%q is not a valid hostname", address)
}
return nil
}
@ -811,7 +810,7 @@ func validateHeadersForTracing(headers map[string]string) error {
return errors.New("custom authorization header configuration is not yet supported")
}
if _, ok := reservedHeaders[strings.ToLower(header)]; ok {
return errors.Errorf("%s is a reserved header. It must not be changed", header)
return fmt.Errorf("%s is a reserved header. It must not be changed", header)
}
}
return nil
@ -823,7 +822,7 @@ func validateHeaders(headers map[string]string) error {
return errors.New("authorization header must be changed via the basic_auth, authorization, oauth2, or sigv4 parameter")
}
if _, ok := reservedHeaders[strings.ToLower(header)]; ok {
return errors.Errorf("%s is a reserved header. It must not be changed", header)
return fmt.Errorf("%s is a reserved header. It must not be changed", header)
}
}
return nil

View file

@ -14,8 +14,8 @@
package config
import (
"crypto/tls"
"encoding/json"
"io/ioutil"
"net/url"
"os"
"path/filepath"
@ -40,6 +40,7 @@ import (
"github.com/prometheus/prometheus/discovery/file"
"github.com/prometheus/prometheus/discovery/hetzner"
"github.com/prometheus/prometheus/discovery/http"
"github.com/prometheus/prometheus/discovery/ionos"
"github.com/prometheus/prometheus/discovery/kubernetes"
"github.com/prometheus/prometheus/discovery/linode"
"github.com/prometheus/prometheus/discovery/marathon"
@ -50,6 +51,7 @@ import (
"github.com/prometheus/prometheus/discovery/targetgroup"
"github.com/prometheus/prometheus/discovery/triton"
"github.com/prometheus/prometheus/discovery/uyuni"
"github.com/prometheus/prometheus/discovery/vultr"
"github.com/prometheus/prometheus/discovery/xds"
"github.com/prometheus/prometheus/discovery/zookeeper"
"github.com/prometheus/prometheus/model/labels"
@ -179,6 +181,9 @@ var expectedConf = &Config{
},
FollowRedirects: true,
EnableHTTP2: true,
TLSConfig: config.TLSConfig{
MinVersion: config.TLSVersion(tls.VersionTLS10),
},
},
ServiceDiscoveryConfigs: discovery.Configs{
@ -855,6 +860,17 @@ var expectedConf = &Config{
Scheme: DefaultScrapeConfig.Scheme,
HTTPClientConfig: config.DefaultHTTPClientConfig,
RelabelConfigs: []*relabel.Config{
{
Action: relabel.Uppercase,
Regex: relabel.DefaultRelabelConfig.Regex,
Replacement: relabel.DefaultRelabelConfig.Replacement,
Separator: relabel.DefaultRelabelConfig.Separator,
SourceLabels: model.LabelNames{"instance"},
TargetLabel: "instance",
},
},
ServiceDiscoveryConfigs: discovery.Configs{
&hetzner.SDConfig{
HTTPClientConfig: config.HTTPClientConfig{
@ -979,6 +995,56 @@ var expectedConf = &Config{
Entitlement: "monitoring_entitled",
Separator: ",",
RefreshInterval: model.Duration(60 * time.Second),
HTTPClientConfig: config.DefaultHTTPClientConfig,
},
},
},
{
JobName: "ionos",
HonorTimestamps: true,
ScrapeInterval: model.Duration(15 * time.Second),
ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
HTTPClientConfig: config.DefaultHTTPClientConfig,
ServiceDiscoveryConfigs: discovery.Configs{
&ionos.SDConfig{
DatacenterID: "8feda53f-15f0-447f-badf-ebe32dad2fc0",
HTTPClientConfig: config.HTTPClientConfig{
Authorization: &config.Authorization{Type: "Bearer", Credentials: "abcdef"},
FollowRedirects: true,
EnableHTTP2: true,
},
Port: 80,
RefreshInterval: model.Duration(60 * time.Second),
},
},
},
{
JobName: "vultr",
HonorTimestamps: true,
ScrapeInterval: model.Duration(15 * time.Second),
ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
HTTPClientConfig: config.DefaultHTTPClientConfig,
ServiceDiscoveryConfigs: discovery.Configs{
&vultr.SDConfig{
HTTPClientConfig: config.HTTPClientConfig{
Authorization: &config.Authorization{
Type: "Bearer",
Credentials: "abcdef",
},
FollowRedirects: true,
EnableHTTP2: true,
},
Port: 80,
RefreshInterval: model.Duration(60 * time.Second),
},
},
},
@ -1079,7 +1145,7 @@ func TestElideSecrets(t *testing.T) {
yamlConfig := string(config)
matches := secretRe.FindAllStringIndex(yamlConfig, -1)
require.Equal(t, 16, len(matches), "wrong number of secret matches found")
require.Equal(t, 18, len(matches), "wrong number of secret matches found")
require.NotContains(t, yamlConfig, "mysecret",
"yaml marshal reveals authentication credentials.")
}
@ -1194,6 +1260,30 @@ var expectedErrors = []struct {
filename: "labelmap.bad.yml",
errMsg: "\"l-$1\" is invalid 'replacement' for labelmap action",
},
{
filename: "lowercase.bad.yml",
errMsg: "relabel configuration for lowercase action requires 'target_label' value",
},
{
filename: "lowercase2.bad.yml",
errMsg: "\"42lab\" is invalid 'target_label' for lowercase action",
},
{
filename: "lowercase3.bad.yml",
errMsg: "'replacement' can not be set for lowercase action",
},
{
filename: "uppercase.bad.yml",
errMsg: "relabel configuration for uppercase action requires 'target_label' value",
},
{
filename: "uppercase2.bad.yml",
errMsg: "\"42lab\" is invalid 'target_label' for uppercase action",
},
{
filename: "uppercase3.bad.yml",
errMsg: "'replacement' can not be set for uppercase action",
},
{
filename: "rules.bad.yml",
errMsg: "invalid rule file path",
@ -1494,6 +1584,10 @@ var expectedErrors = []struct {
filename: "uyuni_no_server.bad.yml",
errMsg: "Uyuni SD configuration requires server host",
},
{
filename: "ionos_datacenter.bad.yml",
errMsg: "datacenter id can't be empty",
},
}
func TestBadConfigs(t *testing.T) {
@ -1506,7 +1600,7 @@ func TestBadConfigs(t *testing.T) {
}
func TestBadStaticConfigsJSON(t *testing.T) {
content, err := ioutil.ReadFile("testdata/static_config.bad.json")
content, err := os.ReadFile("testdata/static_config.bad.json")
require.NoError(t, err)
var tg targetgroup.Group
err = json.Unmarshal(content, &tg)
@ -1514,7 +1608,7 @@ func TestBadStaticConfigsJSON(t *testing.T) {
}
func TestBadStaticConfigsYML(t *testing.T) {
content, err := ioutil.ReadFile("testdata/static_config.bad.yml")
content, err := os.ReadFile("testdata/static_config.bad.yml")
require.NoError(t, err)
var tg targetgroup.Group
err = yaml.UnmarshalStrict(content, &tg)

View file

@ -91,6 +91,9 @@ scrape_configs:
authorization:
credentials_file: valid_token_file
tls_config:
min_version: TLS10
- job_name: service-x
basic_auth:
@ -325,6 +328,10 @@ scrape_configs:
key_file: valid_key_file
- job_name: hetzner
relabel_configs:
- action: uppercase
source_labels: [instance]
target_label: instance
hetzner_sd_configs:
- role: hcloud
authorization:
@ -360,6 +367,17 @@ scrape_configs:
username: gopher
password: hole
- job_name: ionos
ionos_sd_configs:
- datacenter_id: 8feda53f-15f0-447f-badf-ebe32dad2fc0
authorization:
credentials: abcdef
- job_name: vultr
vultr_sd_configs:
- authorization:
credentials: abcdef
alerting:
alertmanagers:
- scheme: https

View file

@ -0,0 +1,3 @@
scrape_configs:
- ionos_sd_configs:
- datacenter_id: ""

5
config/testdata/lowercase.bad.yml vendored Normal file
View file

@ -0,0 +1,5 @@
scrape_configs:
- job_name: prometheus
relabel_configs:
- action: lowercase
source_labels: [__name__]

6
config/testdata/lowercase2.bad.yml vendored Normal file
View file

@ -0,0 +1,6 @@
scrape_configs:
- job_name: prometheus
relabel_configs:
- action: lowercase
source_labels: [__name__]
target_label: 42lab

7
config/testdata/lowercase3.bad.yml vendored Normal file
View file

@ -0,0 +1,7 @@
scrape_configs:
- job_name: prometheus
relabel_configs:
- action: lowercase
source_labels: [__name__]
target_label: __name__
replacement: bar

5
config/testdata/uppercase.bad.yml vendored Normal file
View file

@ -0,0 +1,5 @@
scrape_configs:
- job_name: prometheus
relabel_configs:
- action: uppercase
source_labels: [__name__]

6
config/testdata/uppercase2.bad.yml vendored Normal file
View file

@ -0,0 +1,6 @@
scrape_configs:
- job_name: prometheus
relabel_configs:
- action: uppercase
source_labels: [__name__]
target_label: 42lab

7
config/testdata/uppercase3.bad.yml vendored Normal file
View file

@ -0,0 +1,7 @@
scrape_configs:
- job_name: prometheus
relabel_configs:
- action: uppercase
source_labels: [__name__]
target_label: __name__
replacement: bar

View file

@ -131,7 +131,7 @@ the Prometheus server will be able to see them.
### The SD interface
A Service Discovery (SD) mechanism has to discover targets and provide them to Prometheus. We expect similar targets to be grouped together, in the form of a [target group](https://pkg.go.dev/github.com/prometheus/prometheus@v1.8.2-0.20211105201321-411021ada9ab/discovery/targetgroup#Group). The SD mechanism sends the targets down to prometheus as list of target groups.
A Service Discovery (SD) mechanism has to discover targets and provide them to Prometheus. We expect similar targets to be grouped together, in the form of a [target group](https://pkg.go.dev/github.com/prometheus/prometheus/discovery/targetgroup#Group). The SD mechanism sends the targets down to prometheus as list of target groups.
An SD mechanism has to implement the `Discoverer` Interface:
```go

View file

@ -15,6 +15,7 @@ package aws
import (
"context"
"errors"
"fmt"
"net"
"strings"
@ -29,7 +30,6 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -179,7 +179,7 @@ func (d *EC2Discovery) ec2Client(ctx context.Context) (*ec2.EC2, error) {
Profile: d.cfg.Profile,
})
if err != nil {
return nil, errors.Wrap(err, "could not create aws session")
return nil, fmt.Errorf("could not create aws session: %w", err)
}
if d.cfg.RoleARN != "" {
@ -328,10 +328,11 @@ func (d *EC2Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error
}
return true
}); err != nil {
if awsErr, ok := err.(awserr.Error); ok && (awsErr.Code() == "AuthFailure" || awsErr.Code() == "UnauthorizedOperation") {
var awsErr awserr.Error
if errors.As(err, &awsErr) && (awsErr.Code() == "AuthFailure" || awsErr.Code() == "UnauthorizedOperation") {
d.ec2 = nil
}
return nil, errors.Wrap(err, "could not describe instances")
return nil, fmt.Errorf("could not describe instances: %w", err)
}
return []*targetgroup.Group{tg}, nil
}

View file

@ -15,6 +15,7 @@ package aws
import (
"context"
"errors"
"fmt"
"net"
"strings"
@ -28,7 +29,6 @@ import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/lightsail"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -152,7 +152,7 @@ func (d *LightsailDiscovery) lightsailClient() (*lightsail.Lightsail, error) {
Profile: d.cfg.Profile,
})
if err != nil {
return nil, errors.Wrap(err, "could not create aws session")
return nil, fmt.Errorf("could not create aws session: %w", err)
}
if d.cfg.RoleARN != "" {
@ -179,10 +179,11 @@ func (d *LightsailDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group,
output, err := lightsailClient.GetInstancesWithContext(ctx, input)
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && (awsErr.Code() == "AuthFailure" || awsErr.Code() == "UnauthorizedOperation") {
var awsErr awserr.Error
if errors.As(err, &awsErr) && (awsErr.Code() == "AuthFailure" || awsErr.Code() == "UnauthorizedOperation") {
d.lightsail = nil
}
return nil, errors.Wrap(err, "could not get instances")
return nil, fmt.Errorf("could not get instances: %w", err)
}
for _, inst := range output.Instances {

View file

@ -15,6 +15,7 @@ package azure
import (
"context"
"errors"
"fmt"
"net"
"net/http"
@ -29,7 +30,6 @@ import (
"github.com/Azure/go-autorest/autorest/azure"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
config_util "github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -109,7 +109,7 @@ func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Di
func validateAuthParam(param, name string) error {
if len(param) == 0 {
return errors.Errorf("azure SD configuration requires a %s", name)
return fmt.Errorf("azure SD configuration requires a %s", name)
}
return nil
}
@ -140,7 +140,7 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
if c.AuthenticationMethod != authMethodOAuth && c.AuthenticationMethod != authMethodManagedIdentity {
return errors.Errorf("unknown authentication_type %q. Supported types are %q or %q", c.AuthenticationMethod, authMethodOAuth, authMethodManagedIdentity)
return fmt.Errorf("unknown authentication_type %q. Supported types are %q or %q", c.AuthenticationMethod, authMethodOAuth, authMethodManagedIdentity)
}
return nil
@ -271,7 +271,7 @@ func newAzureResourceFromID(id string, logger log.Logger) (azureResource, error)
// /subscriptions/SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP/providers/PROVIDER/TYPE/NAME/TYPE/NAME
s := strings.Split(id, "/")
if len(s) != 9 && len(s) != 11 {
err := errors.Errorf("invalid ID '%s'. Refusing to create azureResource", id)
err := fmt.Errorf("invalid ID '%s'. Refusing to create azureResource", id)
level.Error(logger).Log("err", err)
return azureResource{}, err
}
@ -288,13 +288,13 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
client, err := createAzureClient(*d.cfg)
if err != nil {
failuresCount.Inc()
return nil, errors.Wrap(err, "could not create Azure client")
return nil, fmt.Errorf("could not create Azure client: %w", err)
}
machines, err := client.getVMs(ctx, d.cfg.ResourceGroup)
if err != nil {
failuresCount.Inc()
return nil, errors.Wrap(err, "could not get virtual machines")
return nil, fmt.Errorf("could not get virtual machines: %w", err)
}
level.Debug(d.logger).Log("msg", "Found virtual machines during Azure discovery.", "count", len(machines))
@ -303,14 +303,14 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
scaleSets, err := client.getScaleSets(ctx, d.cfg.ResourceGroup)
if err != nil {
failuresCount.Inc()
return nil, errors.Wrap(err, "could not get virtual machine scale sets")
return nil, fmt.Errorf("could not get virtual machine scale sets: %w", err)
}
for _, scaleSet := range scaleSets {
scaleSetVms, err := client.getScaleSetVMs(ctx, scaleSet)
if err != nil {
failuresCount.Inc()
return nil, errors.Wrap(err, "could not get virtual machine scale set vms")
return nil, fmt.Errorf("could not get virtual machine scale set vms: %w", err)
}
machines = append(machines, scaleSetVms...)
}
@ -396,7 +396,7 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
}
// If we made it here, we don't have a private IP which should be impossible.
// Return an empty target and error to ensure an all or nothing situation.
err = errors.Errorf("unable to find a private IP for VM %s", vm.Name)
err = fmt.Errorf("unable to find a private IP for VM %s", vm.Name)
ch <- target{labelSet: nil, err: err}
return
}
@ -412,7 +412,7 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
for tgt := range ch {
if tgt.err != nil {
failuresCount.Inc()
return nil, errors.Wrap(tgt.err, "unable to complete Azure service discovery")
return nil, fmt.Errorf("unable to complete Azure service discovery: %w", tgt.err)
}
if tgt.labelSet != nil {
tg.Targets = append(tg.Targets, tgt.labelSet)
@ -432,7 +432,7 @@ func (client *azureClient) getVMs(ctx context.Context, resourceGroup string) ([]
result, err = client.vm.List(ctx, resourceGroup)
}
if err != nil {
return nil, errors.Wrap(err, "could not list virtual machines")
return nil, fmt.Errorf("could not list virtual machines: %w", err)
}
for result.NotDone() {
for _, vm := range result.Values() {
@ -440,7 +440,7 @@ func (client *azureClient) getVMs(ctx context.Context, resourceGroup string) ([]
}
err = result.NextWithContext(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not list virtual machines")
return nil, fmt.Errorf("could not list virtual machines: %w", err)
}
}
@ -461,14 +461,14 @@ func (client *azureClient) getScaleSets(ctx context.Context, resourceGroup strin
var rtn compute.VirtualMachineScaleSetListWithLinkResultPage
rtn, err = client.vmss.ListAll(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not list virtual machine scale sets")
return nil, fmt.Errorf("could not list virtual machine scale sets: %w", err)
}
result = &rtn
} else {
var rtn compute.VirtualMachineScaleSetListResultPage
rtn, err = client.vmss.List(ctx, resourceGroup)
if err != nil {
return nil, errors.Wrap(err, "could not list virtual machine scale sets")
return nil, fmt.Errorf("could not list virtual machine scale sets: %w", err)
}
result = &rtn
}
@ -477,7 +477,7 @@ func (client *azureClient) getScaleSets(ctx context.Context, resourceGroup strin
scaleSets = append(scaleSets, result.Values()...)
err = result.NextWithContext(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not list virtual machine scale sets")
return nil, fmt.Errorf("could not list virtual machine scale sets: %w", err)
}
}
@ -489,12 +489,12 @@ func (client *azureClient) getScaleSetVMs(ctx context.Context, scaleSet compute.
// TODO do we really need to fetch the resourcegroup this way?
r, err := newAzureResourceFromID(*scaleSet.ID, nil)
if err != nil {
return nil, errors.Wrap(err, "could not parse scale set ID")
return nil, fmt.Errorf("could not parse scale set ID: %w", err)
}
result, err := client.vmssvm.List(ctx, r.ResourceGroup, *(scaleSet.Name), "", "", "")
if err != nil {
return nil, errors.Wrap(err, "could not list virtual machine scale set vms")
return nil, fmt.Errorf("could not list virtual machine scale set vms: %w", err)
}
for result.NotDone() {
for _, vm := range result.Values() {
@ -502,7 +502,7 @@ func (client *azureClient) getScaleSetVMs(ctx context.Context, scaleSet compute.
}
err = result.NextWithContext(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not list virtual machine scale set vms")
return nil, fmt.Errorf("could not list virtual machine scale set vms: %w", err)
}
}

View file

@ -15,6 +15,7 @@ package consul
import (
"context"
"errors"
"fmt"
"net"
"strconv"
@ -24,7 +25,6 @@ import (
"github.com/go-kit/log"
"github.com/go-kit/log/level"
consul "github.com/hashicorp/consul/api"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -291,7 +291,7 @@ func (d *Discovery) getDatacenter() error {
dc, ok := info["Config"]["Datacenter"].(string)
if !ok {
err := errors.Errorf("invalid value '%v' for Config.Datacenter", info["Config"]["Datacenter"])
err := fmt.Errorf("invalid value '%v' for Config.Datacenter", info["Config"]["Datacenter"])
level.Error(d.logger).Log("msg", "Error retrieving datacenter name", "err", err)
return err
}

View file

@ -15,6 +15,7 @@ package dns
import (
"context"
"errors"
"fmt"
"net"
"strings"
@ -24,7 +25,6 @@ import (
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/miekg/dns"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
@ -105,7 +105,7 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return errors.New("a port is required in DNS-SD configs for all record types except SRV")
}
default:
return errors.Errorf("invalid DNS-SD records type %s", c.Type)
return fmt.Errorf("invalid DNS-SD records type %s", c.Type)
}
return nil
}
@ -163,7 +163,7 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
wg.Add(len(d.names))
for _, name := range d.names {
go func(n string) {
if err := d.refreshOne(ctx, n, ch); err != nil && err != context.Canceled {
if err := d.refreshOne(ctx, n, ch); err != nil && !errors.Is(err, context.Canceled) {
level.Error(d.logger).Log("msg", "Error refreshing DNS targets", "err", err)
}
wg.Done()
@ -265,7 +265,7 @@ func (d *Discovery) refreshOne(ctx context.Context, name string, ch chan<- *targ
func lookupWithSearchPath(name string, qtype uint16, logger log.Logger) (*dns.Msg, error) {
conf, err := dns.ClientConfigFromFile(resolvConf)
if err != nil {
return nil, errors.Wrap(err, "could not load resolv.conf")
return nil, fmt.Errorf("could not load resolv.conf: %w", err)
}
allResponsesValid := true
@ -291,7 +291,7 @@ func lookupWithSearchPath(name string, qtype uint16, logger log.Logger) (*dns.Ms
return &dns.Msg{}, nil
}
// Outcome 3: boned.
return nil, errors.Errorf("could not resolve %q: all servers responded with errors to at least one search domain", name)
return nil, fmt.Errorf("could not resolve %q: all servers responded with errors to at least one search domain", name)
}
// lookupFromAnyServer uses all configured servers to try and resolve a specific
@ -327,7 +327,7 @@ func lookupFromAnyServer(name string, qtype uint16, conf *dns.ClientConfig, logg
}
}
return nil, errors.Errorf("could not resolve %s: no servers returned a viable answer", name)
return nil, fmt.Errorf("could not resolve %s: no servers returned a viable answer", name)
}
// askServerForName makes a request to a specific DNS server for a specific

View file

@ -18,10 +18,8 @@ import (
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/pkg/errors"
"github.com/prometheus/common/version"
)
@ -95,18 +93,18 @@ func fetchApps(ctx context.Context, server string, client *http.Client) (*Applic
return nil, err
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
if resp.StatusCode/100 != 2 {
return nil, errors.Errorf("non 2xx status '%d' response during eureka service discovery", resp.StatusCode)
return nil, fmt.Errorf("non 2xx status '%d' response during eureka service discovery", resp.StatusCode)
}
var apps Applications
err = xml.NewDecoder(resp.Body).Decode(&apps)
if err != nil {
return nil, errors.Wrapf(err, "%q", url)
return nil, fmt.Errorf("%q: %w", url, err)
}
return &apps, nil
}

View file

@ -15,6 +15,7 @@ package eureka
import (
"context"
"errors"
"net"
"net/http"
"net/url"
@ -22,7 +23,6 @@ import (
"time"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"

View file

@ -16,8 +16,9 @@ package file
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"os"
"path/filepath"
"strings"
@ -28,7 +29,6 @@ import (
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/grafana/regexp"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -99,7 +99,7 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
for _, name := range c.Files {
if !patFileSDName.MatchString(name) {
return errors.Errorf("path name %q is not valid for file discovery", name)
return fmt.Errorf("path name %q is not valid for file discovery", name)
}
}
return nil
@ -376,7 +376,7 @@ func (d *Discovery) readFile(filename string) ([]*targetgroup.Group, error) {
}
defer fd.Close()
content, err := ioutil.ReadAll(fd)
content, err := io.ReadAll(fd)
if err != nil {
return nil, err
}
@ -398,7 +398,7 @@ func (d *Discovery) readFile(filename string) ([]*targetgroup.Group, error) {
return nil, err
}
default:
panic(errors.Errorf("discovery.File.readFile: unhandled file extension %q", ext))
panic(fmt.Errorf("discovery.File.readFile: unhandled file extension %q", ext))
}
for i, tg := range targetGroups {

View file

@ -17,7 +17,6 @@ import (
"context"
"encoding/json"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
@ -73,7 +72,7 @@ func (t *testRunner) copyFile(src string) string {
func (t *testRunner) copyFileTo(src, name string) string {
t.Helper()
newf, err := ioutil.TempFile(t.dir, "")
newf, err := os.CreateTemp(t.dir, "")
require.NoError(t, err)
f, err := os.Open(src)
@ -95,7 +94,7 @@ func (t *testRunner) copyFileTo(src, name string) string {
func (t *testRunner) writeString(file, data string) {
t.Helper()
newf, err := ioutil.TempFile(t.dir, "")
newf, err := os.CreateTemp(t.dir, "")
require.NoError(t, err)
_, err = newf.WriteString(data)

View file

@ -15,6 +15,7 @@ package gce
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
@ -22,7 +23,6 @@ import (
"time"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"golang.org/x/oauth2/google"
compute "google.golang.org/api/compute/v1"
@ -132,11 +132,11 @@ func NewDiscovery(conf SDConfig, logger log.Logger) (*Discovery, error) {
var err error
d.client, err = google.DefaultClient(context.Background(), compute.ComputeReadonlyScope)
if err != nil {
return nil, errors.Wrap(err, "error setting up communication with GCE service")
return nil, fmt.Errorf("error setting up communication with GCE service: %w", err)
}
d.svc, err = compute.NewService(context.Background(), option.WithHTTPClient(d.client))
if err != nil {
return nil, errors.Wrap(err, "error setting up communication with GCE service")
return nil, fmt.Errorf("error setting up communication with GCE service: %w", err)
}
d.isvc = compute.NewInstancesService(d.svc)
@ -221,7 +221,7 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
return nil
})
if err != nil {
return nil, errors.Wrap(err, "error retrieving refresh targets from gce")
return nil, fmt.Errorf("error retrieving refresh targets from gce: %w", err)
}
return []*targetgroup.Group{tg}, nil
}

View file

@ -15,11 +15,12 @@ package hetzner
import (
"context"
"errors"
"fmt"
"time"
"github.com/go-kit/log"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -95,7 +96,7 @@ func (c *role) UnmarshalYAML(unmarshal func(interface{}) error) error {
case hetznerRoleRobot, hetznerRoleHcloud:
return nil
default:
return errors.Errorf("unknown role %q", *c)
return fmt.Errorf("unknown role %q", *c)
}
}
@ -114,6 +115,11 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return c.HTTPClientConfig.Validate()
}
// SetDirectory joins any relative file paths with dir.
func (c *SDConfig) SetDirectory(dir string) {
c.HTTPClientConfig.SetDirectory(dir)
}
// Discovery periodically performs Hetzner requests. It implements
// the Discoverer interface.
type Discovery struct {

View file

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"strconv"
@ -26,7 +25,6 @@ import (
"time"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/version"
@ -85,15 +83,15 @@ func (d *robotDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group, err
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
if resp.StatusCode/100 != 2 {
return nil, errors.Errorf("non 2xx status '%d' response during hetzner service discovery with role robot", resp.StatusCode)
return nil, fmt.Errorf("non 2xx status '%d' response during hetzner service discovery with role robot", resp.StatusCode)
}
b, err := ioutil.ReadAll(resp.Body)
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View file

@ -16,9 +16,9 @@ package http
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -27,7 +27,6 @@ import (
"github.com/go-kit/log"
"github.com/grafana/regexp"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -157,21 +156,21 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
return nil, err
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
failuresCount.Inc()
return nil, errors.Errorf("server returned HTTP status %s", resp.Status)
return nil, fmt.Errorf("server returned HTTP status %s", resp.Status)
}
if !matchContentType.MatchString(strings.TrimSpace(resp.Header.Get("Content-Type"))) {
failuresCount.Inc()
return nil, errors.Errorf("unsupported content type %q", resp.Header.Get("Content-Type"))
return nil, fmt.Errorf("unsupported content type %q", resp.Header.Get("Content-Type"))
}
b, err := ioutil.ReadAll(resp.Body)
b, err := io.ReadAll(resp.Body)
if err != nil {
failuresCount.Inc()
return nil, err

View file

@ -26,6 +26,7 @@ import (
_ "github.com/prometheus/prometheus/discovery/gce" // register gce
_ "github.com/prometheus/prometheus/discovery/hetzner" // register hetzner
_ "github.com/prometheus/prometheus/discovery/http" // register http
_ "github.com/prometheus/prometheus/discovery/ionos" // register ionos
_ "github.com/prometheus/prometheus/discovery/kubernetes" // register kubernetes
_ "github.com/prometheus/prometheus/discovery/linode" // register linode
_ "github.com/prometheus/prometheus/discovery/marathon" // register marathon
@ -35,6 +36,7 @@ import (
_ "github.com/prometheus/prometheus/discovery/scaleway" // register scaleway
_ "github.com/prometheus/prometheus/discovery/triton" // register triton
_ "github.com/prometheus/prometheus/discovery/uyuni" // register uyuni
_ "github.com/prometheus/prometheus/discovery/vultr" // register vultr
_ "github.com/prometheus/prometheus/discovery/xds" // register xds
_ "github.com/prometheus/prometheus/discovery/zookeeper" // register zookeeper
)

111
discovery/ionos/ionos.go Normal file
View file

@ -0,0 +1,111 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ionos
import (
"time"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/discovery"
"github.com/prometheus/prometheus/discovery/refresh"
)
const (
// metaLabelPrefix is the meta prefix used for all meta labels in this
// discovery.
metaLabelPrefix = model.MetaLabelPrefix + "ionos_"
metaLabelSeparator = ","
)
func init() {
discovery.RegisterConfig(&SDConfig{})
}
// Discovery periodically performs IONOS Cloud target discovery. It implements
// the Discoverer interface.
type Discovery struct{}
// NewDiscovery returns a new refresh.Discovery for IONOS Cloud.
func NewDiscovery(conf *SDConfig, logger log.Logger) (*refresh.Discovery, error) {
if conf.ionosEndpoint == "" {
conf.ionosEndpoint = "https://api.ionos.com"
}
d, err := newServerDiscovery(conf, logger)
if err != nil {
return nil, err
}
return refresh.NewDiscovery(
logger,
"ionos",
time.Duration(conf.RefreshInterval),
d.refresh,
), nil
}
// DefaultSDConfig is the default IONOS Cloud service discovery configuration.
var DefaultSDConfig = SDConfig{
HTTPClientConfig: config.DefaultHTTPClientConfig,
RefreshInterval: model.Duration(60 * time.Second),
Port: 80,
}
// SDConfig configuration to use for IONOS Cloud Discovery.
type SDConfig struct {
// DatacenterID: IONOS Cloud data center ID used to discover targets.
DatacenterID string `yaml:"datacenter_id"`
HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
RefreshInterval model.Duration `yaml:"refresh_interval"`
Port int `yaml:"port"`
ionosEndpoint string // For tests only.
}
// Name returns the name of the IONOS Cloud service discovery.
func (c SDConfig) Name() string {
return "ionos"
}
// NewDiscoverer returns a new discovery.Discoverer for IONOS Cloud.
func (c SDConfig) NewDiscoverer(options discovery.DiscovererOptions) (discovery.Discoverer, error) {
return NewDiscovery(&c, options.Logger)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultSDConfig
type plain SDConfig
err := unmarshal((*plain)(c))
if err != nil {
return err
}
if c.DatacenterID == "" {
return errors.New("datacenter id can't be empty")
}
return c.HTTPClientConfig.Validate()
}
// SetDirectory joins any relative file paths with dir.
func (c *SDConfig) SetDirectory(dir string) {
c.HTTPClientConfig.SetDirectory(dir)
}

166
discovery/ionos/server.go Normal file
View file

@ -0,0 +1,166 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ionos
import (
"context"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/go-kit/log"
ionoscloud "github.com/ionos-cloud/sdk-go/v6"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/version"
"github.com/prometheus/prometheus/discovery/refresh"
"github.com/prometheus/prometheus/discovery/targetgroup"
"github.com/prometheus/prometheus/util/strutil"
)
const (
serverLabelPrefix = metaLabelPrefix + "server_"
serverAvailabilityZoneLabel = serverLabelPrefix + "availability_zone"
serverBootCDROMIDLabel = serverLabelPrefix + "boot_cdrom_id"
serverBootImageIDLabel = serverLabelPrefix + "boot_image_id"
serverBootVolumeIDLabel = serverLabelPrefix + "boot_volume_id"
serverCPUFamilyLabel = serverLabelPrefix + "cpu_family"
serverIDLabel = serverLabelPrefix + "id"
serverIPLabel = serverLabelPrefix + "ip"
serverLifecycleLabel = serverLabelPrefix + "lifecycle"
serverNameLabel = serverLabelPrefix + "name"
serverNICIPLabelPrefix = serverLabelPrefix + "nic_ip_"
serverServersIDLabel = serverLabelPrefix + "servers_id"
serverStateLabel = serverLabelPrefix + "state"
serverTypeLabel = serverLabelPrefix + "type"
nicDefaultName = "unnamed"
)
type serverDiscovery struct {
*refresh.Discovery
client *ionoscloud.APIClient
port int
datacenterID string
}
func newServerDiscovery(conf *SDConfig, logger log.Logger) (*serverDiscovery, error) {
d := &serverDiscovery{
port: conf.Port,
datacenterID: conf.DatacenterID,
}
rt, err := config.NewRoundTripperFromConfig(conf.HTTPClientConfig, "ionos_sd")
if err != nil {
return nil, err
}
// Username, password and token are set via http client config.
cfg := ionoscloud.NewConfiguration("", "", "", conf.ionosEndpoint)
cfg.HTTPClient = &http.Client{
Transport: rt,
Timeout: time.Duration(conf.RefreshInterval),
}
cfg.UserAgent = fmt.Sprintf("Prometheus/%s", version.Version)
d.client = ionoscloud.NewAPIClient(cfg)
return d, nil
}
func (d *serverDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
api := d.client.ServersApi
servers, _, err := api.DatacentersServersGet(ctx, d.datacenterID).
Depth(3).
Execute()
if err != nil {
return nil, err
}
var targets []model.LabelSet
for _, server := range *servers.Items {
var ips []string
ipsByNICName := make(map[string][]string)
if server.Entities != nil && server.Entities.Nics != nil {
for _, nic := range *server.Entities.Nics.Items {
nicName := nicDefaultName
if name := nic.Properties.Name; name != nil {
nicName = *name
}
nicIPs := *nic.Properties.Ips
ips = append(nicIPs, ips...)
ipsByNICName[nicName] = append(nicIPs, ipsByNICName[nicName]...)
}
}
// If a server has no IP addresses, it's being dropped from the targets.
if len(ips) == 0 {
continue
}
addr := net.JoinHostPort(ips[0], strconv.FormatUint(uint64(d.port), 10))
labels := model.LabelSet{
model.AddressLabel: model.LabelValue(addr),
serverAvailabilityZoneLabel: model.LabelValue(*server.Properties.AvailabilityZone),
serverCPUFamilyLabel: model.LabelValue(*server.Properties.CpuFamily),
serverServersIDLabel: model.LabelValue(*servers.Id),
serverIDLabel: model.LabelValue(*server.Id),
serverIPLabel: model.LabelValue(join(ips, metaLabelSeparator)),
serverLifecycleLabel: model.LabelValue(*server.Metadata.State),
serverNameLabel: model.LabelValue(*server.Properties.Name),
serverStateLabel: model.LabelValue(*server.Properties.VmState),
serverTypeLabel: model.LabelValue(*server.Properties.Type),
}
for nicName, nicIPs := range ipsByNICName {
name := serverNICIPLabelPrefix + strutil.SanitizeLabelName(nicName)
labels[model.LabelName(name)] = model.LabelValue(join(nicIPs, metaLabelSeparator))
}
if server.Properties.BootCdrom != nil {
labels[serverBootCDROMIDLabel] = model.LabelValue(*server.Properties.BootCdrom.Id)
}
if server.Properties.BootVolume != nil {
labels[serverBootVolumeIDLabel] = model.LabelValue(*server.Properties.BootVolume.Id)
}
if server.Entities != nil && server.Entities.Volumes != nil {
volumes := *server.Entities.Volumes.Items
if len(volumes) > 0 {
image := volumes[0].Properties.Image
if image != nil {
labels[serverBootImageIDLabel] = model.LabelValue(*image)
}
}
}
targets = append(targets, labels)
}
return []*targetgroup.Group{{Source: "ionos", Targets: targets}}, nil
}
// join returns strings.Join with additional separators at beginning and end.
func join(elems []string, sep string) string {
return sep + strings.Join(elems, sep) + sep
}

View file

@ -0,0 +1,115 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ionos
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
ionoscloud "github.com/ionos-cloud/sdk-go/v6"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
var (
ionosTestBearerToken = config.Secret("jwt")
ionosTestDatacenterID = "8feda53f-15f0-447f-badf-ebe32dad2fc0"
)
func TestIONOSServerRefresh(t *testing.T) {
mock := httptest.NewServer(http.HandlerFunc(mockIONOSServers))
defer mock.Close()
cfg := DefaultSDConfig
cfg.DatacenterID = ionosTestDatacenterID
cfg.HTTPClientConfig.BearerToken = ionosTestBearerToken
cfg.ionosEndpoint = mock.URL
d, err := newServerDiscovery(&cfg, nil)
require.NoError(t, err)
ctx := context.Background()
tgs, err := d.refresh(ctx)
require.NoError(t, err)
require.Equal(t, 1, len(tgs))
tg := tgs[0]
require.NotNil(t, tg)
require.NotNil(t, tg.Targets)
require.Equal(t, 2, len(tg.Targets))
for i, lbls := range []model.LabelSet{
{
"__address__": "85.215.243.177:80",
"__meta_ionos_server_availability_zone": "ZONE_2",
"__meta_ionos_server_boot_cdrom_id": "0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"__meta_ionos_server_cpu_family": "INTEL_SKYLAKE",
"__meta_ionos_server_id": "b501942c-4e08-43e6-8ec1-00e59c64e0e4",
"__meta_ionos_server_ip": ",85.215.243.177,185.56.150.9,85.215.238.118,",
"__meta_ionos_server_nic_ip_metrics": ",85.215.243.177,",
"__meta_ionos_server_nic_ip_unnamed": ",185.56.150.9,85.215.238.118,",
"__meta_ionos_server_lifecycle": "AVAILABLE",
"__meta_ionos_server_name": "prometheus-2",
"__meta_ionos_server_servers_id": "8feda53f-15f0-447f-badf-ebe32dad2fc0/servers",
"__meta_ionos_server_state": "RUNNING",
"__meta_ionos_server_type": "ENTERPRISE",
},
{
"__address__": "85.215.248.84:80",
"__meta_ionos_server_availability_zone": "ZONE_1",
"__meta_ionos_server_boot_cdrom_id": "0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"__meta_ionos_server_cpu_family": "INTEL_SKYLAKE",
"__meta_ionos_server_id": "523415e6-ff8c-4dc0-86d3-09c256039b30",
"__meta_ionos_server_ip": ",85.215.248.84,",
"__meta_ionos_server_nic_ip_unnamed": ",85.215.248.84,",
"__meta_ionos_server_lifecycle": "AVAILABLE",
"__meta_ionos_server_name": "prometheus-1",
"__meta_ionos_server_servers_id": "8feda53f-15f0-447f-badf-ebe32dad2fc0/servers",
"__meta_ionos_server_state": "RUNNING",
"__meta_ionos_server_type": "ENTERPRISE",
},
} {
t.Run(fmt.Sprintf("item %d", i), func(t *testing.T) {
require.Equal(t, lbls, tg.Targets[i])
})
}
}
func mockIONOSServers(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != fmt.Sprintf("Bearer %s", ionosTestBearerToken) {
http.Error(w, "bad token", http.StatusUnauthorized)
return
}
if r.URL.Path != fmt.Sprintf("%s/datacenters/%s/servers", ionoscloud.DefaultIonosBasePath, ionosTestDatacenterID) {
http.Error(w, fmt.Sprintf("bad url: %s", r.URL.Path), http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
server, err := os.ReadFile("testdata/servers.json")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = w.Write(server)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

615
discovery/ionos/testdata/servers.json vendored Normal file
View file

@ -0,0 +1,615 @@
{
"id": "8feda53f-15f0-447f-badf-ebe32dad2fc0/servers",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers",
"items": [
{
"id": "d6bf44ee-f7e8-4e19-8716-96fdd18cc697",
"type": "server",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/d6bf44ee-f7e8-4e19-8716-96fdd18cc697",
"metadata": {
"etag": "3bd2cf1f190fdba8502ba8c0cc79433e",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "prometheus-3",
"cores": 2,
"ram": 4096,
"availabilityZone": "AUTO",
"vmState": "RUNNING",
"bootCdrom": {
"id": "0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"type": "image",
"href": "https://api.ionos.com/cloudapi/v6/images/0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"metadata": {
"etag": "ca643281fd2a5b68002bc00ac0ecd920",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "debian-10.0.0-amd64-netinst.iso",
"description": null,
"location": "de/txl",
"size": 0.33,
"cpuHotPlug": true,
"cpuHotUnplug": false,
"ramHotPlug": true,
"ramHotUnplug": false,
"nicHotPlug": true,
"nicHotUnplug": true,
"discVirtioHotPlug": true,
"discVirtioHotUnplug": true,
"discScsiHotPlug": false,
"discScsiHotUnplug": false,
"licenceType": "LINUX",
"imageType": "CDROM",
"imageAliases": [
"debian:latest_iso",
"debian:10_iso"
],
"cloudInit": "NONE",
"public": true
}
},
"bootVolume": null,
"cpuFamily": "INTEL_SKYLAKE",
"type": "ENTERPRISE"
},
"entities": {
"cdroms": {
"id": "d6bf44ee-f7e8-4e19-8716-96fdd18cc697/cdroms",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/d6bf44ee-f7e8-4e19-8716-96fdd18cc697/cdroms",
"items": [
{
"id": "0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"type": "image",
"href": "https://api.ionos.com/cloudapi/v6/images/0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"metadata": {
"etag": "ca643281fd2a5b68002bc00ac0ecd920",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "debian-10.0.0-amd64-netinst.iso",
"description": null,
"location": "de/txl",
"size": 0.33,
"cpuHotPlug": true,
"cpuHotUnplug": false,
"ramHotPlug": true,
"ramHotUnplug": false,
"nicHotPlug": true,
"nicHotUnplug": true,
"discVirtioHotPlug": true,
"discVirtioHotUnplug": true,
"discScsiHotPlug": false,
"discScsiHotUnplug": false,
"licenceType": "LINUX",
"imageType": "CDROM",
"imageAliases": [
"debian:10_iso",
"debian:latest_iso"
],
"cloudInit": "NONE",
"public": true
}
}
]
},
"volumes": {
"id": "d6bf44ee-f7e8-4e19-8716-96fdd18cc697/volumes",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/d6bf44ee-f7e8-4e19-8716-96fdd18cc697/volumes",
"items": []
},
"nics": {
"id": "d6bf44ee-f7e8-4e19-8716-96fdd18cc697/nics",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/d6bf44ee-f7e8-4e19-8716-96fdd18cc697/nics",
"items": []
}
}
},
{
"id": "b501942c-4e08-43e6-8ec1-00e59c64e0e4",
"type": "server",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4",
"metadata": {
"etag": "fe405a5f2e041d506086ee0c2159b678",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "prometheus-2",
"cores": 1,
"ram": 1024,
"availabilityZone": "ZONE_2",
"vmState": "RUNNING",
"bootCdrom": {
"id": "0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"type": "image",
"href": "https://api.ionos.com/cloudapi/v6/images/0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"metadata": {
"etag": "ca643281fd2a5b68002bc00ac0ecd920",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "debian-10.0.0-amd64-netinst.iso",
"description": null,
"location": "de/txl",
"size": 0.33,
"cpuHotPlug": true,
"cpuHotUnplug": false,
"ramHotPlug": true,
"ramHotUnplug": false,
"nicHotPlug": true,
"nicHotUnplug": true,
"discVirtioHotPlug": true,
"discVirtioHotUnplug": true,
"discScsiHotPlug": false,
"discScsiHotUnplug": false,
"licenceType": "LINUX",
"imageType": "CDROM",
"imageAliases": [
"debian:latest_iso",
"debian:10_iso"
],
"cloudInit": "NONE",
"public": true
}
},
"bootVolume": null,
"cpuFamily": "INTEL_SKYLAKE",
"type": "ENTERPRISE"
},
"entities": {
"cdroms": {
"id": "b501942c-4e08-43e6-8ec1-00e59c64e0e4/cdroms",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/cdroms",
"items": [
{
"id": "0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"type": "image",
"href": "https://api.ionos.com/cloudapi/v6/images/0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"metadata": {
"etag": "ca643281fd2a5b68002bc00ac0ecd920",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "debian-10.0.0-amd64-netinst.iso",
"description": null,
"location": "de/txl",
"size": 0.33,
"cpuHotPlug": true,
"cpuHotUnplug": false,
"ramHotPlug": true,
"ramHotUnplug": false,
"nicHotPlug": true,
"nicHotUnplug": true,
"discVirtioHotPlug": true,
"discVirtioHotUnplug": true,
"discScsiHotPlug": false,
"discScsiHotUnplug": false,
"licenceType": "LINUX",
"imageType": "CDROM",
"imageAliases": [
"debian:10_iso",
"debian:latest_iso"
],
"cloudInit": "NONE",
"public": true
}
}
]
},
"volumes": {
"id": "b501942c-4e08-43e6-8ec1-00e59c64e0e4/volumes",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/volumes",
"items": [
{
"id": "97a58f00-e2a5-4ebb-8b9a-f694837b0548",
"type": "volume",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/volumes/97a58f00-e2a5-4ebb-8b9a-f694837b0548",
"metadata": {
"etag": "27491713a77c5453c6ae7d0f98a1beec",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "prometheus-data-1",
"type": "HDD",
"size": 10,
"availabilityZone": "AUTO",
"image": null,
"imagePassword": null,
"sshKeys": null,
"bus": "VIRTIO",
"licenceType": "UNKNOWN",
"cpuHotPlug": false,
"ramHotPlug": false,
"nicHotPlug": false,
"nicHotUnplug": false,
"discVirtioHotPlug": false,
"discVirtioHotUnplug": false,
"deviceNumber": 1,
"userData": null,
"pciSlot": 6,
"bootServer": "b501942c-4e08-43e6-8ec1-00e59c64e0e4"
}
}
]
},
"nics": {
"id": "b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics",
"items": [
{
"id": "206e9a88-097c-4c87-8544-1f5fe0b6b0f1",
"type": "nic",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics/206e9a88-097c-4c87-8544-1f5fe0b6b0f1",
"metadata": {
"etag": "be2b6fff29e7e09c1a4a5ca150876eaa",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": null,
"mac": "02:01:4b:1c:cf:57",
"ips": [
"85.215.238.118"
],
"dhcp": true,
"lan": 1,
"firewallActive": false,
"firewallType": "INGRESS",
"deviceNumber": 1,
"pciSlot": 5
},
"entities": {
"firewallrules": {
"id": "206e9a88-097c-4c87-8544-1f5fe0b6b0f1/firewallrules",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics/206e9a88-097c-4c87-8544-1f5fe0b6b0f1/firewallrules"
},
"flowlogs": {
"id": "206e9a88-097c-4c87-8544-1f5fe0b6b0f1/flowlogs",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics/206e9a88-097c-4c87-8544-1f5fe0b6b0f1/flowlogs"
}
}
},
{
"id": "8a568a32-da62-437d-8b73-f580d1a1588f",
"type": "nic",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics/8a568a32-da62-437d-8b73-f580d1a1588f",
"metadata": {
"etag": "2dca0340a0610b1dfdb2e66f6aae3ed3",
"createdDate": "2022-05-05T13:12:31Z",
"createdBy": "felix.ehrenpfort@codecentric.cloud",
"createdByUserId": "cfcb4526-fbe0-461e-9149-ffb00a83fbec",
"lastModifiedDate": "2022-05-05T13:12:31Z",
"lastModifiedBy": "felix.ehrenpfort@codecentric.cloud",
"lastModifiedByUserId": "cfcb4526-fbe0-461e-9149-ffb00a83fbec",
"state": "AVAILABLE"
},
"properties": {
"name": null,
"mac": "02:01:56:f6:47:a8",
"ips": [
"185.56.150.9"
],
"dhcp": true,
"lan": 1,
"firewallActive": false,
"firewallType": "INGRESS",
"deviceNumber": 3,
"pciSlot": 8
},
"entities": {
"firewallrules": {
"id": "8a568a32-da62-437d-8b73-f580d1a1588f/firewallrules",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics/8a568a32-da62-437d-8b73-f580d1a1588f/firewallrules"
},
"flowlogs": {
"id": "8a568a32-da62-437d-8b73-f580d1a1588f/flowlogs",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics/8a568a32-da62-437d-8b73-f580d1a1588f/flowlogs"
}
}
},
{
"id": "17ecc866-4a08-4ada-858f-7b726a5f5070",
"type": "nic",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics/17ecc866-4a08-4ada-858f-7b726a5f5070",
"metadata": {
"etag": "f6a82276c6fdb460811d3789e54e0054",
"createdDate": "2022-05-05T11:45:36Z",
"createdBy": "felix.ehrenpfort@codecentric.cloud",
"createdByUserId": "cfcb4526-fbe0-461e-9149-ffb00a83fbec",
"lastModifiedDate": "2022-05-05T11:45:36Z",
"lastModifiedBy": "felix.ehrenpfort@codecentric.cloud",
"lastModifiedByUserId": "cfcb4526-fbe0-461e-9149-ffb00a83fbec",
"state": "AVAILABLE"
},
"properties": {
"name": "metrics",
"mac": "02:01:93:80:fa:a4",
"ips": [
"85.215.243.177"
],
"dhcp": true,
"lan": 1,
"firewallActive": false,
"firewallType": "INGRESS",
"deviceNumber": 2,
"pciSlot": 7
},
"entities": {
"firewallrules": {
"id": "17ecc866-4a08-4ada-858f-7b726a5f5070/firewallrules",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics/17ecc866-4a08-4ada-858f-7b726a5f5070/firewallrules"
},
"flowlogs": {
"id": "17ecc866-4a08-4ada-858f-7b726a5f5070/flowlogs",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/b501942c-4e08-43e6-8ec1-00e59c64e0e4/nics/17ecc866-4a08-4ada-858f-7b726a5f5070/flowlogs"
}
}
}
]
}
}
},
{
"id": "523415e6-ff8c-4dc0-86d3-09c256039b30",
"type": "server",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/523415e6-ff8c-4dc0-86d3-09c256039b30",
"metadata": {
"etag": "fe405a5f2e041d506086ee0c2159b678",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "prometheus-1",
"cores": 1,
"ram": 1024,
"availabilityZone": "ZONE_1",
"vmState": "RUNNING",
"bootCdrom": {
"id": "0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"type": "image",
"href": "https://api.ionos.com/cloudapi/v6/images/0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"metadata": {
"etag": "ca643281fd2a5b68002bc00ac0ecd920",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "debian-10.0.0-amd64-netinst.iso",
"description": null,
"location": "de/txl",
"size": 0.33,
"cpuHotPlug": true,
"cpuHotUnplug": false,
"ramHotPlug": true,
"ramHotUnplug": false,
"nicHotPlug": true,
"nicHotUnplug": true,
"discVirtioHotPlug": true,
"discVirtioHotUnplug": true,
"discScsiHotPlug": false,
"discScsiHotUnplug": false,
"licenceType": "LINUX",
"imageType": "CDROM",
"imageAliases": [
"debian:latest_iso",
"debian:10_iso"
],
"cloudInit": "NONE",
"public": true
}
},
"bootVolume": null,
"cpuFamily": "INTEL_SKYLAKE",
"type": "ENTERPRISE"
},
"entities": {
"cdroms": {
"id": "523415e6-ff8c-4dc0-86d3-09c256039b30/cdroms",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/523415e6-ff8c-4dc0-86d3-09c256039b30/cdroms",
"items": [
{
"id": "0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"type": "image",
"href": "https://api.ionos.com/cloudapi/v6/images/0e4d57f9-cd78-11e9-b88c-525400f64d8d",
"metadata": {
"etag": "ca643281fd2a5b68002bc00ac0ecd920",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "debian-10.0.0-amd64-netinst.iso",
"description": null,
"location": "de/txl",
"size": 0.33,
"cpuHotPlug": true,
"cpuHotUnplug": false,
"ramHotPlug": true,
"ramHotUnplug": false,
"nicHotPlug": true,
"nicHotUnplug": true,
"discVirtioHotPlug": true,
"discVirtioHotUnplug": true,
"discScsiHotPlug": false,
"discScsiHotUnplug": false,
"licenceType": "LINUX",
"imageType": "CDROM",
"imageAliases": [
"debian:10_iso",
"debian:latest_iso"
],
"cloudInit": "NONE",
"public": true
}
}
]
},
"volumes": {
"id": "523415e6-ff8c-4dc0-86d3-09c256039b30/volumes",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/523415e6-ff8c-4dc0-86d3-09c256039b30/volumes",
"items": [
{
"id": "5dc21326-7e33-4dc7-84ac-63b02aad4624",
"type": "volume",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/volumes/5dc21326-7e33-4dc7-84ac-63b02aad4624",
"metadata": {
"etag": "27491713a77c5453c6ae7d0f98a1beec",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": "prometheus-data-2",
"type": "HDD",
"size": 10,
"availabilityZone": "AUTO",
"image": null,
"imagePassword": null,
"sshKeys": null,
"bus": "VIRTIO",
"licenceType": "UNKNOWN",
"cpuHotPlug": false,
"ramHotPlug": false,
"nicHotPlug": false,
"nicHotUnplug": false,
"discVirtioHotPlug": false,
"discVirtioHotUnplug": false,
"deviceNumber": 1,
"userData": null,
"pciSlot": 6,
"bootServer": "523415e6-ff8c-4dc0-86d3-09c256039b30"
}
}
]
},
"nics": {
"id": "523415e6-ff8c-4dc0-86d3-09c256039b30/nics",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/523415e6-ff8c-4dc0-86d3-09c256039b30/nics",
"items": [
{
"id": "684033a2-317f-4977-8a01-68263d59336c",
"type": "nic",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/523415e6-ff8c-4dc0-86d3-09c256039b30/nics/684033a2-317f-4977-8a01-68263d59336c",
"metadata": {
"etag": "fe405a5f2e041d506086ee0c2159b678",
"createdDate": "2019-09-02T11:51:53Z",
"createdBy": "System",
"createdByUserId": "[UNKNOWN]",
"lastModifiedDate": "2019-09-02T11:51:53Z",
"lastModifiedBy": "System",
"lastModifiedByUserId": "[UNKNOWN]",
"state": "AVAILABLE"
},
"properties": {
"name": null,
"mac": "02:01:17:12:27:1b",
"ips": [
"85.215.248.84"
],
"dhcp": true,
"lan": 1,
"firewallActive": false,
"firewallType": "INGRESS",
"deviceNumber": 1,
"pciSlot": 5
},
"entities": {
"firewallrules": {
"id": "684033a2-317f-4977-8a01-68263d59336c/firewallrules",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/523415e6-ff8c-4dc0-86d3-09c256039b30/nics/684033a2-317f-4977-8a01-68263d59336c/firewallrules"
},
"flowlogs": {
"id": "684033a2-317f-4977-8a01-68263d59336c/flowlogs",
"type": "collection",
"href": "https://api.ionos.com/cloudapi/v6/datacenters/8feda53f-15f0-447f-badf-ebe32dad2fc0/servers/523415e6-ff8c-4dc0-86d3-09c256039b30/nics/684033a2-317f-4977-8a01-68263d59336c/flowlogs"
}
}
}
]
}
}
}
]
}

View file

@ -15,12 +15,13 @@ package kubernetes
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
apiv1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/cache"
@ -135,7 +136,7 @@ func (e *Endpoints) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
defer e.queue.ShutDown()
if !cache.WaitForCacheSync(ctx.Done(), e.endpointsInf.HasSynced, e.serviceInf.HasSynced, e.podInf.HasSynced) {
if ctx.Err() != context.Canceled {
if !errors.Is(ctx.Err(), context.Canceled) {
level.Error(e.logger).Log("msg", "endpoints informer unable to sync cache")
}
return
@ -188,7 +189,7 @@ func convertToEndpoints(o interface{}) (*apiv1.Endpoints, error) {
return endpoints, nil
}
return nil, errors.Errorf("received unexpected object: %v", o)
return nil, fmt.Errorf("received unexpected object: %v", o)
}
func endpointsSource(ep *apiv1.Endpoints) string {

View file

@ -15,12 +15,12 @@ package kubernetes
import (
"context"
"fmt"
"net"
"strconv"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
apiv1 "k8s.io/api/core/v1"
v1 "k8s.io/api/discovery/v1"
@ -193,7 +193,7 @@ func (e *EndpointSlice) getEndpointSliceAdaptor(o interface{}) (endpointSliceAda
case *v1beta1.EndpointSlice:
return newEndpointSliceAdaptorFromV1beta1(endpointSlice), nil
default:
return nil, errors.Errorf("received unexpected object: %v", o)
return nil, fmt.Errorf("received unexpected object: %v", o)
}
}

View file

@ -644,8 +644,14 @@ func TestEndpointSliceDiscoveryWithServiceUpdate(t *testing.T) {
Targets: []model.LabelSet{
{
"__address__": "1.2.3.4:9000",
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
"__meta_kubernetes_endpointslice_port": "9000",
"__meta_kubernetes_endpointslice_port_app_protocol": "http",
"__meta_kubernetes_endpointslice_port_name": "testport",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
@ -655,6 +661,7 @@ func TestEndpointSliceDiscoveryWithServiceUpdate(t *testing.T) {
"__meta_kubernetes_endpointslice_port": "9000",
"__meta_kubernetes_endpointslice_port_name": "testport",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
"__meta_kubernetes_endpointslice_port_app_protocol": "http",
},
{
"__address__": "3.4.5.6:9000",
@ -662,6 +669,7 @@ func TestEndpointSliceDiscoveryWithServiceUpdate(t *testing.T) {
"__meta_kubernetes_endpointslice_port": "9000",
"__meta_kubernetes_endpointslice_port_name": "testport",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
"__meta_kubernetes_endpointslice_port_app_protocol": "http",
},
},
Labels: model.LabelSet{
@ -755,8 +763,14 @@ func TestEndpointSliceDiscoveryNamespaces(t *testing.T) {
Targets: []model.LabelSet{
{
"__address__": "1.2.3.4:9000",
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
"__meta_kubernetes_endpointslice_port": "9000",
"__meta_kubernetes_endpointslice_port_app_protocol": "http",
"__meta_kubernetes_endpointslice_port_name": "testport",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
@ -766,6 +780,7 @@ func TestEndpointSliceDiscoveryNamespaces(t *testing.T) {
"__meta_kubernetes_endpointslice_port": "9000",
"__meta_kubernetes_endpointslice_port_name": "testport",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
"__meta_kubernetes_endpointslice_port_app_protocol": "http",
},
{
"__address__": "3.4.5.6:9000",
@ -773,6 +788,7 @@ func TestEndpointSliceDiscoveryNamespaces(t *testing.T) {
"__meta_kubernetes_endpointslice_port": "9000",
"__meta_kubernetes_endpointslice_port_name": "testport",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
"__meta_kubernetes_endpointslice_port_app_protocol": "http",
},
},
Labels: model.LabelSet{
@ -871,8 +887,14 @@ func TestEndpointSliceDiscoveryOwnNamespace(t *testing.T) {
Targets: []model.LabelSet{
{
"__address__": "1.2.3.4:9000",
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
"__meta_kubernetes_endpointslice_port": "9000",
"__meta_kubernetes_endpointslice_port_app_protocol": "http",
"__meta_kubernetes_endpointslice_port_name": "testport",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
@ -882,6 +904,7 @@ func TestEndpointSliceDiscoveryOwnNamespace(t *testing.T) {
"__meta_kubernetes_endpointslice_port": "9000",
"__meta_kubernetes_endpointslice_port_name": "testport",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
"__meta_kubernetes_endpointslice_port_app_protocol": "http",
},
{
"__address__": "3.4.5.6:9000",
@ -889,6 +912,7 @@ func TestEndpointSliceDiscoveryOwnNamespace(t *testing.T) {
"__meta_kubernetes_endpointslice_port": "9000",
"__meta_kubernetes_endpointslice_port_name": "testport",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
"__meta_kubernetes_endpointslice_port_app_protocol": "http",
},
},
Labels: model.LabelSet{

View file

@ -15,11 +15,12 @@ package kubernetes
import (
"context"
"errors"
"fmt"
"strings"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
v1 "k8s.io/api/networking/v1"
"k8s.io/api/networking/v1beta1"
@ -78,7 +79,7 @@ func (i *Ingress) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
defer i.queue.ShutDown()
if !cache.WaitForCacheSync(ctx.Done(), i.informer.HasSynced) {
if ctx.Err() != context.Canceled {
if !errors.Is(ctx.Err(), context.Canceled) {
level.Error(i.logger).Log("msg", "ingress informer unable to sync cache")
}
return
@ -123,7 +124,7 @@ func (i *Ingress) process(ctx context.Context, ch chan<- []*targetgroup.Group) b
ia = newIngressAdaptorFromV1beta1(ingress)
default:
level.Error(i.logger).Log("msg", "converting to Ingress object failed", "err",
errors.Errorf("received unexpected object: %v", o))
fmt.Errorf("received unexpected object: %v", o))
return true
}
send(ctx, ch, i.buildIngress(ia))

View file

@ -15,8 +15,9 @@ package kubernetes
import (
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"sync"
@ -24,7 +25,6 @@ import (
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -114,7 +114,7 @@ func (c *Role) UnmarshalYAML(unmarshal func(interface{}) error) error {
case RoleNode, RolePod, RoleService, RoleEndpoint, RoleEndpointSlice, RoleIngress:
return nil
default:
return errors.Errorf("unknown Kubernetes SD role %q", *c)
return fmt.Errorf("unknown Kubernetes SD role %q", *c)
}
}
@ -178,7 +178,7 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
if c.Role == "" {
return errors.Errorf("role missing (one of: pod, service, endpoints, endpointslice, node, ingress)")
return fmt.Errorf("role missing (one of: pod, service, endpoints, endpointslice, node, ingress)")
}
err = c.HTTPClientConfig.Validate()
if err != nil {
@ -186,20 +186,20 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
if c.APIServer.URL != nil && c.KubeConfig != "" {
// Api-server and kubeconfig_file are mutually exclusive
return errors.Errorf("cannot use 'kubeconfig_file' and 'api_server' simultaneously")
return fmt.Errorf("cannot use 'kubeconfig_file' and 'api_server' simultaneously")
}
if c.KubeConfig != "" && !reflect.DeepEqual(c.HTTPClientConfig, config.DefaultHTTPClientConfig) {
// Kubeconfig_file and custom http config are mutually exclusive
return errors.Errorf("cannot use a custom HTTP client configuration together with 'kubeconfig_file'")
return fmt.Errorf("cannot use a custom HTTP client configuration together with 'kubeconfig_file'")
}
if c.APIServer.URL == nil && !reflect.DeepEqual(c.HTTPClientConfig, config.DefaultHTTPClientConfig) {
return errors.Errorf("to use custom HTTP client configuration please provide the 'api_server' URL explicitly")
return fmt.Errorf("to use custom HTTP client configuration please provide the 'api_server' URL explicitly")
}
if c.APIServer.URL != nil && c.NamespaceDiscovery.IncludeOwnNamespace {
return errors.Errorf("cannot use 'api_server' and 'namespaces.own_namespace' simultaneously")
return fmt.Errorf("cannot use 'api_server' and 'namespaces.own_namespace' simultaneously")
}
if c.KubeConfig != "" && c.NamespaceDiscovery.IncludeOwnNamespace {
return errors.Errorf("cannot use 'kubeconfig_file' and 'namespaces.own_namespace' simultaneously")
return fmt.Errorf("cannot use 'kubeconfig_file' and 'namespaces.own_namespace' simultaneously")
}
foundSelectorRoles := make(map[Role]struct{})
@ -214,12 +214,12 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
for _, selector := range c.Selectors {
if _, ok := foundSelectorRoles[selector.Role]; ok {
return errors.Errorf("duplicated selector role: %s", selector.Role)
return fmt.Errorf("duplicated selector role: %s", selector.Role)
}
foundSelectorRoles[selector.Role] = struct{}{}
if _, ok := allowedSelectors[c.Role]; !ok {
return errors.Errorf("invalid role: %q, expecting one of: pod, service, endpoints, endpointslice, node or ingress", c.Role)
return fmt.Errorf("invalid role: %q, expecting one of: pod, service, endpoints, endpointslice, node or ingress", c.Role)
}
var allowed bool
for _, role := range allowedSelectors[c.Role] {
@ -230,7 +230,7 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
if !allowed {
return errors.Errorf("%s role supports only %s selectors", c.Role, strings.Join(allowedSelectors[c.Role], ", "))
return fmt.Errorf("%s role supports only %s selectors", c.Role, strings.Join(allowedSelectors[c.Role], ", "))
}
_, err := fields.ParseSelector(selector.Field)
@ -312,7 +312,7 @@ func New(l log.Logger, conf *SDConfig) (*Discovery, error) {
}
if conf.NamespaceDiscovery.IncludeOwnNamespace {
ownNamespaceContents, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
ownNamespaceContents, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
if err != nil {
return nil, fmt.Errorf("could not determine the pod's namespace: %w", err)
}

View file

@ -16,11 +16,11 @@ package kubernetes
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/version"
@ -163,11 +163,11 @@ Loop:
func requireTargetGroups(t *testing.T, expected, res map[string]*targetgroup.Group) {
t.Helper()
b1, err := json.Marshal(expected)
b1, err := marshalTargetGroups(expected)
if err != nil {
panic(err)
}
b2, err := json.Marshal(res)
b2, err := marshalTargetGroups(res)
if err != nil {
panic(err)
}
@ -175,6 +175,22 @@ func requireTargetGroups(t *testing.T, expected, res map[string]*targetgroup.Gro
require.Equal(t, string(b1), string(b2))
}
// marshalTargetGroups serializes a set of target groups to JSON, ignoring the
// custom MarshalJSON function defined on the targetgroup.Group struct.
// marshalTargetGroups can be used for making exact comparisons between target groups
// as it will serialize all target labels.
func marshalTargetGroups(tgs map[string]*targetgroup.Group) ([]byte, error) {
type targetGroupAlias targetgroup.Group
aliases := make(map[string]*targetGroupAlias, len(tgs))
for k, v := range tgs {
tg := targetGroupAlias(*v)
aliases[k] = &tg
}
return json.Marshal(aliases)
}
type hasSynced interface {
// hasSynced returns true if all informers synced.
// This is only used in testing to determine when discoverer synced to

View file

@ -15,12 +15,13 @@ package kubernetes
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
apiv1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/cache"
@ -85,7 +86,7 @@ func (n *Node) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
defer n.queue.ShutDown()
if !cache.WaitForCacheSync(ctx.Done(), n.informer.HasSynced) {
if ctx.Err() != context.Canceled {
if !errors.Is(ctx.Err(), context.Canceled) {
level.Error(n.logger).Log("msg", "node informer unable to sync cache")
}
return
@ -136,7 +137,7 @@ func convertToNode(o interface{}) (*apiv1.Node, error) {
return node, nil
}
return nil, errors.Errorf("received unexpected object: %v", o)
return nil, fmt.Errorf("received unexpected object: %v", o)
}
func nodeSource(n *apiv1.Node) string {

View file

@ -15,13 +15,14 @@ package kubernetes
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"strings"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -118,7 +119,7 @@ func (p *Pod) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
}
if !cache.WaitForCacheSync(ctx.Done(), cacheSyncs...) {
if ctx.Err() != context.Canceled {
if !errors.Is(ctx.Err(), context.Canceled) {
level.Error(p.logger).Log("msg", "pod informer unable to sync cache")
}
return
@ -169,7 +170,7 @@ func convertToPod(o interface{}) (*apiv1.Pod, error) {
return pod, nil
}
return nil, errors.Errorf("received unexpected object: %v", o)
return nil, fmt.Errorf("received unexpected object: %v", o)
}
const (

View file

@ -15,12 +15,13 @@ package kubernetes
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
apiv1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/cache"
@ -81,7 +82,7 @@ func (s *Service) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
defer s.queue.ShutDown()
if !cache.WaitForCacheSync(ctx.Done(), s.informer.HasSynced) {
if ctx.Err() != context.Canceled {
if !errors.Is(ctx.Err(), context.Canceled) {
level.Error(s.logger).Log("msg", "service informer unable to sync cache")
}
return
@ -131,7 +132,7 @@ func convertToService(o interface{}) (*apiv1.Service, error) {
if ok {
return service, nil
}
return nil, errors.Errorf("received unexpected object: %v", o)
return nil, fmt.Errorf("received unexpected object: %v", o)
}
func serviceSource(s *apiv1.Service) string {

View file

@ -14,6 +14,7 @@
package legacymanager
import (
"errors"
"fmt"
"reflect"
"sort"
@ -248,7 +249,8 @@ func writeConfigs(structVal reflect.Value, configs discovery.Configs) error {
}
func replaceYAMLTypeError(err error, oldTyp, newTyp reflect.Type) error {
if e, ok := err.(*yaml.TypeError); ok {
var e *yaml.TypeError
if errors.As(err, &e) {
oldStr := oldTyp.String()
newStr := newTyp.String()
for i, s := range e.Errors {

View file

@ -25,6 +25,7 @@ import (
"github.com/go-kit/log"
"github.com/linode/linodego"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/version"
@ -65,15 +66,24 @@ const (
)
// DefaultSDConfig is the default Linode SD configuration.
var DefaultSDConfig = SDConfig{
var (
DefaultSDConfig = SDConfig{
TagSeparator: ",",
Port: 80,
RefreshInterval: model.Duration(60 * time.Second),
HTTPClientConfig: config.DefaultHTTPClientConfig,
}
failuresCount = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "prometheus_sd_linode_failures_total",
Help: "Number of Linode service discovery refresh failures.",
})
)
func init() {
discovery.RegisterConfig(&SDConfig{})
prometheus.MustRegister(failuresCount)
}
// SDConfig is the configuration for Linode based service discovery.
@ -211,12 +221,14 @@ func (d *Discovery) refreshData(ctx context.Context) ([]*targetgroup.Group, erro
// Gather all linode instances.
instances, err := d.client.ListInstances(ctx, &linodego.ListOptions{PageSize: 500})
if err != nil {
failuresCount.Inc()
return nil, err
}
// Gather detailed IP address info for all IPs on all linode instances.
detailedIPs, err := d.client.ListIPAddresses(ctx, &linodego.ListOptions{PageSize: 500})
if err != nil {
failuresCount.Inc()
return nil, err
}

View file

@ -16,18 +16,18 @@ package marathon
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -186,17 +186,17 @@ type authTokenFileRoundTripper struct {
// newAuthTokenFileRoundTripper adds the auth token read from the file to a request.
func newAuthTokenFileRoundTripper(tokenFile string, rt http.RoundTripper) (http.RoundTripper, error) {
// fail-fast if we can't read the file.
_, err := ioutil.ReadFile(tokenFile)
_, err := os.ReadFile(tokenFile)
if err != nil {
return nil, errors.Wrapf(err, "unable to read auth token file %s", tokenFile)
return nil, fmt.Errorf("unable to read auth token file %s: %w", tokenFile, err)
}
return &authTokenFileRoundTripper{tokenFile, rt}, nil
}
func (rt *authTokenFileRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
b, err := ioutil.ReadFile(rt.authTokenFile)
b, err := os.ReadFile(rt.authTokenFile)
if err != nil {
return nil, errors.Wrapf(err, "unable to read auth token file %s", rt.authTokenFile)
return nil, fmt.Errorf("unable to read auth token file %s: %w", rt.authTokenFile, err)
}
authToken := strings.TrimSpace(string(b))
@ -331,15 +331,15 @@ func fetchApps(ctx context.Context, client *http.Client, url string) (*appList,
return nil, err
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
if (resp.StatusCode < 200) || (resp.StatusCode >= 300) {
return nil, errors.Errorf("non 2xx status '%v' response during marathon service discovery", resp.StatusCode)
return nil, fmt.Errorf("non 2xx status '%v' response during marathon service discovery", resp.StatusCode)
}
b, err := ioutil.ReadAll(resp.Body)
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -347,7 +347,7 @@ func fetchApps(ctx context.Context, client *http.Client, url string) (*appList,
var apps appList
err = json.Unmarshal(b, &apps)
if err != nil {
return nil, errors.Wrapf(err, "%q", url)
return nil, fmt.Errorf("%q: %w", url, err)
}
return &apps, nil
}

View file

@ -54,7 +54,7 @@ func TestMarathonSDHandleError(t *testing.T) {
}
)
tgs, err := testUpdateServices(client)
if err != errTesting {
if !errors.Is(err, errTesting) {
t.Fatalf("Expected error: %s", err)
}
if len(tgs) != 0 {

View file

@ -16,9 +16,9 @@ package moby
import (
"crypto/sha1"
"encoding/base64"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
@ -63,7 +63,7 @@ func (m *SDMock) Setup() {
// HandleNodesList mocks nodes list.
func (m *SDMock) SetupHandlers() {
headers := make(map[string]string)
rawHeaders, err := ioutil.ReadFile(filepath.Join("testdata", m.directory, "headers.yml"))
rawHeaders, err := os.ReadFile(filepath.Join("testdata", m.directory, "headers.yml"))
require.NoError(m.t, err)
yaml.Unmarshal(rawHeaders, &headers)
@ -102,13 +102,13 @@ func (m *SDMock) SetupHandlers() {
f += "__" + base64.URLEncoding.EncodeToString(h.Sum(nil))[:10]
}
}
if response, err := ioutil.ReadFile(filepath.Join("testdata", m.directory, f+".json")); err == nil {
if response, err := os.ReadFile(filepath.Join("testdata", m.directory, f+".json")); err == nil {
w.Header().Add("content-type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(response)
return
}
if response, err := ioutil.ReadFile(filepath.Join("testdata", m.directory, f)); err == nil {
if response, err := os.ReadFile(filepath.Join("testdata", m.directory, f)); err == nil {
w.WriteHeader(http.StatusOK)
w.Write(response)
return

View file

@ -23,7 +23,6 @@ import (
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors"
"github.com/gophercloud/gophercloud/pagination"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/discovery/targetgroup"
@ -62,14 +61,14 @@ func (h *HypervisorDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group
h.provider.Context = ctx
err := openstack.Authenticate(h.provider, *h.authOpts)
if err != nil {
return nil, errors.Wrap(err, "could not authenticate to OpenStack")
return nil, fmt.Errorf("could not authenticate to OpenStack: %w", err)
}
client, err := openstack.NewComputeV2(h.provider, gophercloud.EndpointOpts{
Region: h.region, Availability: h.availability,
})
if err != nil {
return nil, errors.Wrap(err, "could not create OpenStack compute session")
return nil, fmt.Errorf("could not create OpenStack compute session: %w", err)
}
tg := &targetgroup.Group{
@ -81,7 +80,7 @@ func (h *HypervisorDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group
err = pagerHypervisors.EachPage(func(page pagination.Page) (bool, error) {
hypervisorList, err := hypervisors.ExtractHypervisors(page)
if err != nil {
return false, errors.Wrap(err, "could not extract hypervisors")
return false, fmt.Errorf("could not extract hypervisors: %w", err)
}
for _, hypervisor := range hypervisorList {
labels := model.LabelSet{}

View file

@ -25,7 +25,6 @@ import (
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/pagination"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/discovery/targetgroup"
@ -79,14 +78,14 @@ func (i *InstanceDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group,
i.provider.Context = ctx
err := openstack.Authenticate(i.provider, *i.authOpts)
if err != nil {
return nil, errors.Wrap(err, "could not authenticate to OpenStack")
return nil, fmt.Errorf("could not authenticate to OpenStack: %w", err)
}
client, err := openstack.NewComputeV2(i.provider, gophercloud.EndpointOpts{
Region: i.region, Availability: i.availability,
})
if err != nil {
return nil, errors.Wrap(err, "could not create OpenStack compute session")
return nil, fmt.Errorf("could not create OpenStack compute session: %w", err)
}
// OpenStack API reference
@ -97,7 +96,7 @@ func (i *InstanceDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group,
err = pagerFIP.EachPage(func(page pagination.Page) (bool, error) {
result, err := floatingips.ExtractFloatingIPs(page)
if err != nil {
return false, errors.Wrap(err, "could not extract floatingips")
return false, fmt.Errorf("could not extract floatingips: %w", err)
}
for _, ip := range result {
// Skip not associated ips
@ -124,11 +123,11 @@ func (i *InstanceDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group,
}
err = pager.EachPage(func(page pagination.Page) (bool, error) {
if ctx.Err() != nil {
return false, errors.Wrap(ctx.Err(), "could not extract instances")
return false, fmt.Errorf("could not extract instances: %w", ctx.Err())
}
instanceList, err := servers.ExtractServers(page)
if err != nil {
return false, errors.Wrap(err, "could not extract instances")
return false, fmt.Errorf("could not extract instances: %w", err)
}
for _, s := range instanceList {

View file

@ -15,6 +15,7 @@ package openstack
import (
"context"
"errors"
"fmt"
"net/http"
"time"
@ -23,7 +24,6 @@ import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
conntrack "github.com/mwitkow/go-conntrack"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -100,7 +100,7 @@ func (c *Role) UnmarshalYAML(unmarshal func(interface{}) error) error {
case OpenStackRoleHypervisor, OpenStackRoleInstance:
return nil
default:
return errors.Errorf("unknown OpenStack SD role %q", *c)
return fmt.Errorf("unknown OpenStack SD role %q", *c)
}
}

View file

@ -26,6 +26,17 @@
"alias": "/cgi-bin",
"path": "/var/www/cgi-bin"
}
],
"port": 22,
"pi": 3.141592653589793,
"buckets": [
0,
2,
5
],
"coordinates": [
60.13464726551357,
-2.0513768021728893
]
},
"resource": "49af83866dc5a1518968b68e58a25319107afe11",

View file

@ -19,7 +19,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
@ -30,7 +29,6 @@ import (
"github.com/go-kit/log"
"github.com/grafana/regexp"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/version"
@ -187,19 +185,19 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
return nil, err
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf("server returned HTTP status %s", resp.Status)
return nil, fmt.Errorf("server returned HTTP status %s", resp.Status)
}
if ct := resp.Header.Get("Content-Type"); !matchContentType.MatchString(ct) {
return nil, errors.Errorf("unsupported content type %s", resp.Header.Get("Content-Type"))
return nil, fmt.Errorf("unsupported content type %s", resp.Header.Get("Content-Type"))
}
b, err := ioutil.ReadAll(resp.Body)
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View file

@ -137,10 +137,14 @@ func TestPuppetDBRefreshWithParameters(t *testing.T) {
model.LabelName("__meta_puppetdb_file"): model.LabelValue("/etc/puppetlabs/code/environments/prod/modules/upstream/apache/manifests/init.pp"),
model.LabelName("__meta_puppetdb_parameter_access_log"): model.LabelValue("true"),
model.LabelName("__meta_puppetdb_parameter_access_log_file"): model.LabelValue("ssl_access_log"),
model.LabelName("__meta_puppetdb_parameter_buckets"): model.LabelValue("0,2,5"),
model.LabelName("__meta_puppetdb_parameter_coordinates"): model.LabelValue("60.13464726551357,-2.0513768021728893"),
model.LabelName("__meta_puppetdb_parameter_docroot"): model.LabelValue("/var/www/html"),
model.LabelName("__meta_puppetdb_parameter_ensure"): model.LabelValue("absent"),
model.LabelName("__meta_puppetdb_parameter_labels_alias"): model.LabelValue("edinburgh"),
model.LabelName("__meta_puppetdb_parameter_options"): model.LabelValue("Indexes,FollowSymLinks,MultiViews"),
model.LabelName("__meta_puppetdb_parameter_pi"): model.LabelValue("3.141592653589793"),
model.LabelName("__meta_puppetdb_parameter_port"): model.LabelValue("22"),
model.LabelName("__meta_puppetdb_resource"): model.LabelValue("49af83866dc5a1518968b68e58a25319107afe11"),
model.LabelName("__meta_puppetdb_tags"): model.LabelValue(",roles::hypervisor,apache,apache::vhost,class,default-ssl,profile_hypervisor,vhost,profile_apache,hypervisor,__node_regexp__edinburgh,roles,node,"),
model.LabelName("__meta_puppetdb_title"): model.LabelValue("default-ssl"),

View file

@ -46,6 +46,10 @@ func (p *Parameters) toLabels() model.LabelSet {
labelValue = value
case bool:
labelValue = strconv.FormatBool(value)
case int64:
labelValue = strconv.FormatInt(value, 10)
case float64:
labelValue = strconv.FormatFloat(value, 'g', -1, 64)
case []string:
labelValue = separator + strings.Join(value, separator) + separator
case []interface{}:
@ -59,6 +63,10 @@ func (p *Parameters) toLabels() model.LabelSet {
values[i] = value
case bool:
values[i] = strconv.FormatBool(value)
case int64:
values[i] = strconv.FormatInt(value, 10)
case float64:
values[i] = strconv.FormatFloat(value, 'g', -1, 64)
case []string:
values[i] = separator + strings.Join(value, separator) + separator
}

View file

@ -15,6 +15,7 @@ package refresh
import (
"context"
"errors"
"time"
"github.com/go-kit/log"
@ -75,7 +76,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
// Get an initial set right away.
tgs, err := d.refresh(ctx)
if err != nil {
if ctx.Err() != context.Canceled {
if !errors.Is(ctx.Err(), context.Canceled) {
level.Error(d.logger).Log("msg", "Unable to refresh target groups", "err", err.Error())
}
} else {
@ -94,7 +95,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
case <-ticker.C:
tgs, err := d.refresh(ctx)
if err != nil {
if ctx.Err() != context.Canceled {
if !errors.Is(ctx.Err(), context.Canceled) {
level.Error(d.logger).Log("msg", "Unable to refresh target groups", "err", err.Error())
}
continue

View file

@ -14,6 +14,7 @@
package discovery
import (
"errors"
"fmt"
"reflect"
"sort"
@ -247,7 +248,8 @@ func writeConfigs(structVal reflect.Value, configs Configs) error {
}
func replaceYAMLTypeError(err error, oldTyp, newTyp reflect.Type) error {
if e, ok := err.(*yaml.TypeError); ok {
var e *yaml.TypeError
if errors.As(err, &e) {
oldStr := oldTyp.String()
newStr := newTyp.String()
for i, s := range e.Errors {

View file

@ -16,9 +16,9 @@ package scaleway
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/prometheus/common/model"
@ -127,7 +127,7 @@ func mockScalewayInstance(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Content-Type", "application/json")
instance, err := ioutil.ReadFile("testdata/instance.json")
instance, err := os.ReadFile("testdata/instance.json")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View file

@ -15,13 +15,14 @@ package scaleway
import (
"context"
"io/ioutil"
"errors"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/go-kit/log"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/scaleway/scaleway-sdk-go/scw"
@ -61,7 +62,7 @@ func (c *role) UnmarshalYAML(unmarshal func(interface{}) error) error {
case roleInstance, roleBaremetal:
return nil
default:
return errors.Errorf("unknown role %q", *c)
return fmt.Errorf("unknown role %q", *c)
}
}
@ -226,17 +227,17 @@ type authTokenFileRoundTripper struct {
// newAuthTokenFileRoundTripper adds the auth token read from the file to a request.
func newAuthTokenFileRoundTripper(tokenFile string, rt http.RoundTripper) (http.RoundTripper, error) {
// fail-fast if we can't read the file.
_, err := ioutil.ReadFile(tokenFile)
_, err := os.ReadFile(tokenFile)
if err != nil {
return nil, errors.Wrapf(err, "unable to read auth token file %s", tokenFile)
return nil, fmt.Errorf("unable to read auth token file %s: %w", tokenFile, err)
}
return &authTokenFileRoundTripper{tokenFile, rt}, nil
}
func (rt *authTokenFileRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
b, err := ioutil.ReadFile(rt.authTokenFile)
b, err := os.ReadFile(rt.authTokenFile)
if err != nil {
return nil, errors.Wrapf(err, "unable to read auth token file %s", rt.authTokenFile)
return nil, fmt.Errorf("unable to read auth token file %s: %w", rt.authTokenFile, err)
}
authToken := strings.TrimSpace(string(b))

View file

@ -16,9 +16,9 @@ package triton
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
@ -26,7 +26,6 @@ import (
"github.com/go-kit/log"
conntrack "github.com/mwitkow/go-conntrack"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -203,17 +202,17 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
req = req.WithContext(ctx)
resp, err := d.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "an error occurred when requesting targets from the discovery endpoint")
return nil, fmt.Errorf("an error occurred when requesting targets from the discovery endpoint: %w", err)
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "an error occurred when reading the response body")
return nil, fmt.Errorf("an error occurred when reading the response body: %w", err)
}
// The JSON response body is different so it needs to be processed/mapped separately.
@ -235,7 +234,7 @@ func (d *Discovery) processContainerResponse(data []byte, endpoint string) ([]*t
dr := DiscoveryResponse{}
err := json.Unmarshal(data, &dr)
if err != nil {
return nil, errors.Wrap(err, "an error occurred unmarshaling the discovery response json")
return nil, fmt.Errorf("an error occurred unmarshaling the discovery response json: %w", err)
}
for _, container := range dr.Containers {
@ -268,7 +267,7 @@ func (d *Discovery) processComputeNodeResponse(data []byte, endpoint string) ([]
dr := ComputeNodeDiscoveryResponse{}
err := json.Unmarshal(data, &dr)
if err != nil {
return nil, errors.Wrap(err, "an error occurred unmarshaling the compute node discovery response json")
return nil, fmt.Errorf("an error occurred unmarshaling the compute node discovery response json: %w", err)
}
for _, cn := range dr.ComputeNodes {

View file

@ -15,6 +15,7 @@ package uyuni
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
@ -24,7 +25,6 @@ import (
"github.com/go-kit/log"
"github.com/kolo/xmlrpc"
"github.com/pkg/errors"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -55,6 +55,7 @@ var DefaultSDConfig = SDConfig{
Entitlement: "monitoring_entitled",
Separator: ",",
RefreshInterval: model.Duration(1 * time.Minute),
HTTPClientConfig: config.DefaultHTTPClientConfig,
}
func init() {
@ -117,6 +118,11 @@ func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Di
return NewDiscovery(c, opts.Logger)
}
// SetDirectory joins any relative file paths with dir.
func (c *SDConfig) SetDirectory(dir string) {
c.HTTPClientConfig.SetDirectory(dir)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultSDConfig
@ -131,7 +137,7 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
_, err = url.Parse(c.Server)
if err != nil {
return errors.Wrap(err, "Uyuni Server URL is not valid")
return fmt.Errorf("Uyuni Server URL is not valid: %w", err)
}
if c.Username == "" {
@ -276,7 +282,7 @@ func (d *Discovery) getTargetsForSystems(
systemGroupIDsBySystemID, err := getSystemGroupsInfoOfMonitoredClients(rpcClient, d.token, entitlement)
if err != nil {
return nil, errors.Wrap(err, "unable to get the managed system groups information of monitored clients")
return nil, fmt.Errorf("unable to get the managed system groups information of monitored clients: %w", err)
}
systemIDs := make([]int, 0, len(systemGroupIDsBySystemID))
@ -286,12 +292,12 @@ func (d *Discovery) getTargetsForSystems(
endpointInfos, err := getEndpointInfoForSystems(rpcClient, d.token, systemIDs)
if err != nil {
return nil, errors.Wrap(err, "unable to get endpoints information")
return nil, fmt.Errorf("unable to get endpoints information: %w", err)
}
networkInfoBySystemID, err := getNetworkInformationForSystems(rpcClient, d.token, systemIDs)
if err != nil {
return nil, errors.Wrap(err, "unable to get the systems network information")
return nil, fmt.Errorf("unable to get the systems network information: %w", err)
}
for _, endpoint := range endpointInfos {
@ -317,7 +323,7 @@ func (d *Discovery) refresh(_ context.Context) ([]*targetgroup.Group, error) {
// Uyuni API takes duration in seconds.
d.token, err = login(rpcClient, d.username, d.password, int(tokenDuration.Seconds()))
if err != nil {
return nil, errors.Wrap(err, "unable to login to Uyuni API")
return nil, fmt.Errorf("unable to login to Uyuni API: %w", err)
}
// Login again at half the token lifetime.
d.tokenExpiration = time.Now().Add(tokenDuration / 2)

View file

@ -0,0 +1,170 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package vultr
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
// SDMock is the interface for the Vultr mock
type SDMock struct {
t *testing.T
Server *httptest.Server
Mux *http.ServeMux
}
// NewSDMock returns a new Vultr mock server.
func NewSDMock(t *testing.T) *SDMock {
return &SDMock{
t: t,
}
}
// Endpoint returns the URI to the mock server.
func (m *SDMock) Endpoint() string {
return m.Server.URL + "/"
}
// Setup creates the mock server.
func (m *SDMock) Setup() {
m.Mux = http.NewServeMux()
m.Server = httptest.NewServer(m.Mux)
}
// ShutdownServer shuts down the mock server.
func (m *SDMock) ShutdownServer() {
m.Server.Close()
}
const APIKey = "ABCBTDG35OTGH2UKCC3S6CNMDUPCN3ZWSGFQ"
// HandleInstanceList mocks vultr instances list.
func (m *SDMock) HandleInstanceList() {
m.Mux.HandleFunc("/v2/instances", func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != fmt.Sprintf("Bearer %s", APIKey) {
w.WriteHeader(http.StatusForbidden)
return
}
w.Header().Add("content-type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, `
{
"instances": [
{
"id": "dbdbd38c-9884-4c92-95fe-899e50dee717",
"os": "Marketplace",
"ram": 4096,
"disk": 128,
"main_ip": "149.28.234.27",
"vcpu_count": 2,
"region": "ewr",
"plan": "vhf-2c-4gb",
"date_created": "2022-03-10T02:25:30+00:00",
"status": "active",
"allowed_bandwidth": 3000,
"netmask_v4": "255.255.254.0",
"gateway_v4": "149.28.234.1",
"power_status": "running",
"server_status": "ok",
"v6_network": "",
"v6_main_ip": "",
"v6_network_size": 0,
"label": "np-2-eae38a19b0f3",
"internal_ip": "10.1.96.5",
"kvm": "https://my.vultr.com/subs/vps/novnc/api.php?data=djJ8Q2k2NlVpNUlZNE5kM3NZMWdBNzFKUkVadDBDU3ItTEJ8-yNWBdMn1AWPqX-EnpTVbA1qBeP0txBZbahSOh3UtHIL2p0fZKEdFJwFNjWtvBKC9BGvJkoJdWBoo7hF-6RzavdUKPD3KIU5xD_T6keV8X-PPukPgXMuSUaAqurAuJvwAWXcTcodBjaIH961xgbvfkFyqVl0ZgHf3ErZQE-EmpZ0Jc3uF1DO2McJ4UL8KUvHLwa-ZDUZ_AtWJjQWsjNkxjvOiA",
"hostname": "np-2-eae38a19b0f3",
"os_id": 426,
"app_id": 0,
"image_id": "vke-worker",
"firewall_group_id": "",
"features": ["backups"],
"tags": ["tag1", "tag2", "tag3"]
},
{
"id": "fccb117c-62f7-4b17-995d-a8e56dd30b33",
"os": "Marketplace",
"ram": 4096,
"disk": 128,
"main_ip": "45.63.1.222",
"vcpu_count": 2,
"region": "ewr",
"plan": "vhf-2c-4gb",
"date_created": "2022-03-10T02:25:32+00:00",
"status": "active",
"allowed_bandwidth": 3000,
"netmask_v4": "255.255.254.0",
"gateway_v4": "45.63.0.1",
"power_status": "running",
"server_status": "ok",
"v6_network": "",
"v6_main_ip": "",
"v6_network_size": 0,
"label": "np-2-fd0714b5fe42",
"internal_ip": "10.1.96.6",
"kvm": "https://my.vultr.com/subs/vps/novnc/api.php?data=djJ8R0tzbmI0VWVfSDR6VE51Q0FGUTBCamwyTFNzZDNkVTF8Zwm93t6L2BLTYSNmM0g0Ppf43r71aIbqLiJCx6_1NuCugUqIKz8POshH91vi6XXDorpb3hYTk8RrJWQbaFMak7XbbfMObzsrFxKAQUVGfbjTBIHCKftxNIXeQOp1fEIDa3p-vkfSxEpx6ZX8nChGYYSVuOjS1twV6oI_IKtHXKdFJiVFz0Unay_K3HuXW_H2wrYSjmAChloR-eA1jSmBXLccJQ",
"hostname": "np-2-fd0714b5fe42",
"os_id": 426,
"app_id": 0,
"image_id": "vke-worker",
"firewall_group_id": "",
"features": [],
"tags": []
},
{
"id": "c4e58767-f61b-4c5e-9bce-43761cc04868",
"os": "Marketplace",
"ram": 4096,
"disk": 128,
"main_ip": "149.28.237.151",
"vcpu_count": 2,
"region": "ewr",
"plan": "vhf-2c-4gb",
"date_created": "2022-03-10T02:25:33+00:00",
"status": "active",
"allowed_bandwidth": 3000,
"netmask_v4": "255.255.254.0",
"gateway_v4": "149.28.236.1",
"power_status": "running",
"server_status": "ok",
"v6_network": "",
"v6_main_ip": "",
"v6_network_size": 0,
"label": "np-2-d04e7829fa43",
"internal_ip": "10.1.96.7",
"kvm": "https://my.vultr.com/subs/vps/novnc/api.php?data=djJ8NFEzbFZmV2hOUWNrc2R4bndnckhFQTJ3ME1yWmxVeW98P-p-KeyNtP73wzVvuWZSOrV5PvQDiBmYyB3cp99btIy5nczmvX8DOGSChbEldk3idKNdFLQMIieWnpR5xU5K-dMYlL6n6oYv2u-rh8zxS6-8hQsSbL2OsoywNPoLpQ3xPkT9Rb-yPrZX4fDLbt8yPVKDHqHO0GB-TxVPemueovfkkUZMXSraAXgHtl1YgK2uLMQf1WBFVzgwnQ7MYcfMGGYFQw",
"hostname": "np-2-d04e7829fa43",
"os_id": 426,
"app_id": 0,
"image_id": "vke-worker",
"firewall_group_id": "",
"features": [],
"tags": []
}
],
"meta": {
"total": 3,
"links": {
"next": "",
"prev": ""
}
}
}
`)
})
}

212
discovery/vultr/vultr.go Normal file
View file

@ -0,0 +1,212 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package vultr
import (
"context"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/go-kit/log"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/version"
"github.com/vultr/govultr/v2"
"github.com/prometheus/prometheus/discovery"
"github.com/prometheus/prometheus/discovery/refresh"
"github.com/prometheus/prometheus/discovery/targetgroup"
)
const (
vultrInstanceLabel = model.MetaLabelPrefix + "vultr_instance_"
vultrInstanceLabelID = vultrInstanceLabel + "id"
vultrInstanceLabelLabel = vultrInstanceLabel + "label"
vultrInstanceLabelOS = vultrInstanceLabel + "os"
vultrInstanceLabelOSID = vultrInstanceLabel + "os_id"
vultrInstanceLabelRegion = vultrInstanceLabel + "region"
vultrInstanceLabelPlan = vultrInstanceLabel + "plan"
vultrInstanceLabelMainIP = vultrInstanceLabel + "main_ip"
vultrInstanceLabelMainIPV6 = vultrInstanceLabel + "main_ipv6"
vultrInstanceLabelInternalIP = vultrInstanceLabel + "internal_ip"
vultrInstanceLabelFeatures = vultrInstanceLabel + "features"
vultrInstanceLabelTags = vultrInstanceLabel + "tags"
vultrInstanceLabelHostname = vultrInstanceLabel + "hostname"
vultrInstanceLabelServerStatus = vultrInstanceLabel + "server_status"
vultrInstanceLabelVCPU = vultrInstanceLabel + "vcpu_count"
vultrInstanceLabelMemory = vultrInstanceLabel + "ram_mb"
vultrInstanceLabelBandwidth = vultrInstanceLabel + "allowed_bandwidth_gb"
vultrInstanceLabelDisk = vultrInstanceLabel + "disk_gb"
separator = ","
)
// DefaultSDConfig is the default Vultr SD configuration.
var DefaultSDConfig = SDConfig{
Port: 80,
RefreshInterval: model.Duration(60 * time.Second),
HTTPClientConfig: config.DefaultHTTPClientConfig,
}
func init() {
discovery.RegisterConfig(&SDConfig{})
}
type SDConfig struct {
HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
RefreshInterval model.Duration `yaml:"refresh_interval"`
Port int `yaml:"port"`
}
// Name returns the name of the Config.
func (*SDConfig) Name() string { return "vultr" }
// NewDiscoverer returns a Discoverer for the Config.
func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Discoverer, error) {
return NewDiscovery(c, opts.Logger)
}
// SetDirectory joins any relative file paths with dir.
func (c *SDConfig) SetDirectory(dir string) {
c.HTTPClientConfig.SetDirectory(dir)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultSDConfig
type plain SDConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
return c.HTTPClientConfig.Validate()
}
// Discovery periodically performs Vultr requests. It implements
// the Discoverer interface.
type Discovery struct {
*refresh.Discovery
client *govultr.Client
port int
}
// NewDiscovery returns a new Discovery which periodically refreshes its targets.
func NewDiscovery(conf *SDConfig, logger log.Logger) (*Discovery, error) {
d := &Discovery{
port: conf.Port,
}
rt, err := config.NewRoundTripperFromConfig(conf.HTTPClientConfig, "vultr_sd")
if err != nil {
return nil, err
}
d.client = govultr.NewClient(&http.Client{
Transport: rt,
Timeout: time.Duration(conf.RefreshInterval),
})
d.client.SetUserAgent(fmt.Sprintf("Prometheus/%s", version.Version))
if err != nil {
return nil, fmt.Errorf("error setting up vultr agent: %w", err)
}
d.Discovery = refresh.NewDiscovery(
logger,
"vultr",
time.Duration(conf.RefreshInterval),
d.refresh,
)
return d, nil
}
func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
tg := &targetgroup.Group{
Source: "Vultr",
}
instances, err := d.listInstances(ctx)
if err != nil {
return nil, err
}
for _, instance := range instances {
labels := model.LabelSet{
vultrInstanceLabelID: model.LabelValue(instance.ID),
vultrInstanceLabelLabel: model.LabelValue(instance.Label),
vultrInstanceLabelOS: model.LabelValue(instance.Os),
vultrInstanceLabelOSID: model.LabelValue(strconv.Itoa(instance.OsID)),
vultrInstanceLabelRegion: model.LabelValue(instance.Region),
vultrInstanceLabelPlan: model.LabelValue(instance.Plan),
vultrInstanceLabelVCPU: model.LabelValue(strconv.Itoa(instance.VCPUCount)),
vultrInstanceLabelMemory: model.LabelValue(strconv.Itoa(instance.RAM)),
vultrInstanceLabelBandwidth: model.LabelValue(strconv.Itoa(instance.AllowedBandwidth)),
vultrInstanceLabelDisk: model.LabelValue(strconv.Itoa(instance.Disk)),
vultrInstanceLabelMainIP: model.LabelValue(instance.MainIP),
vultrInstanceLabelMainIPV6: model.LabelValue(instance.V6MainIP),
vultrInstanceLabelInternalIP: model.LabelValue(instance.InternalIP),
vultrInstanceLabelHostname: model.LabelValue(instance.Hostname),
vultrInstanceLabelServerStatus: model.LabelValue(instance.ServerStatus),
}
addr := net.JoinHostPort(instance.MainIP, strconv.FormatUint(uint64(d.port), 10))
labels[model.AddressLabel] = model.LabelValue(addr)
// We surround the separated list with the separator as well. This way regular expressions
// in relabeling rules don't have to consider feature positions.
if len(instance.Features) > 0 {
features := separator + strings.Join(instance.Features, separator) + separator
labels[vultrInstanceLabelFeatures] = model.LabelValue(features)
}
if len(instance.Tags) > 0 {
tags := separator + strings.Join(instance.Tags, separator) + separator
labels[vultrInstanceLabelTags] = model.LabelValue(tags)
}
tg.Targets = append(tg.Targets, labels)
}
return []*targetgroup.Group{tg}, nil
}
func (d *Discovery) listInstances(ctx context.Context) ([]govultr.Instance, error) {
var instances []govultr.Instance
listOptions := &govultr.ListOptions{
PerPage: 100,
}
for {
pagedInstances, meta, err := d.client.Instance.List(ctx, listOptions)
if err != nil {
return nil, err
}
instances = append(instances, pagedInstances...)
if meta.Links.Next == "" {
break
} else {
listOptions.Cursor = meta.Links.Next
continue
}
}
return instances, nil
}

View file

@ -0,0 +1,128 @@
// Copyright 2022 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package vultr
import (
"context"
"fmt"
"net/url"
"testing"
"github.com/go-kit/log"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
type VultrSDTestSuite struct {
Mock *SDMock
}
func (s *VultrSDTestSuite) TearDownSuite() {
s.Mock.ShutdownServer()
}
func (s *VultrSDTestSuite) SetupTest(t *testing.T) {
s.Mock = NewSDMock(t)
s.Mock.Setup()
s.Mock.HandleInstanceList()
}
func TestVultrSDRefresh(t *testing.T) {
sdMock := &VultrSDTestSuite{}
sdMock.SetupTest(t)
t.Cleanup(sdMock.TearDownSuite)
cfg := DefaultSDConfig
cfg.HTTPClientConfig.BearerToken = APIKey
d, err := NewDiscovery(&cfg, log.NewNopLogger())
require.NoError(t, err)
endpoint, err := url.Parse(sdMock.Mock.Endpoint())
require.NoError(t, err)
d.client.BaseURL = endpoint
ctx := context.Background()
tgs, err := d.refresh(ctx)
require.NoError(t, err)
require.Equal(t, 1, len(tgs))
tg := tgs[0]
require.NotNil(t, tg)
require.NotNil(t, tg.Targets)
require.Equal(t, 3, len(tg.Targets))
for i, k := range []model.LabelSet{
{
"__address__": model.LabelValue("149.28.234.27:80"),
"__meta_vultr_instance_id": model.LabelValue("dbdbd38c-9884-4c92-95fe-899e50dee717"),
"__meta_vultr_instance_label": model.LabelValue("np-2-eae38a19b0f3"),
"__meta_vultr_instance_os": model.LabelValue("Marketplace"),
"__meta_vultr_instance_os_id": model.LabelValue("426"),
"__meta_vultr_instance_region": model.LabelValue("ewr"),
"__meta_vultr_instance_plan": model.LabelValue("vhf-2c-4gb"),
"__meta_vultr_instance_main_ip": model.LabelValue("149.28.234.27"),
"__meta_vultr_instance_internal_ip": model.LabelValue("10.1.96.5"),
"__meta_vultr_instance_main_ipv6": model.LabelValue(""),
"__meta_vultr_instance_features": model.LabelValue(",backups,"),
"__meta_vultr_instance_tags": model.LabelValue(",tag1,tag2,tag3,"),
"__meta_vultr_instance_hostname": model.LabelValue("np-2-eae38a19b0f3"),
"__meta_vultr_instance_server_status": model.LabelValue("ok"),
"__meta_vultr_instance_vcpu_count": model.LabelValue("2"),
"__meta_vultr_instance_ram_mb": model.LabelValue("4096"),
"__meta_vultr_instance_disk_gb": model.LabelValue("128"),
"__meta_vultr_instance_allowed_bandwidth_gb": model.LabelValue("3000"),
},
{
"__address__": model.LabelValue("45.63.1.222:80"),
"__meta_vultr_instance_id": model.LabelValue("fccb117c-62f7-4b17-995d-a8e56dd30b33"),
"__meta_vultr_instance_label": model.LabelValue("np-2-fd0714b5fe42"),
"__meta_vultr_instance_os": model.LabelValue("Marketplace"),
"__meta_vultr_instance_os_id": model.LabelValue("426"),
"__meta_vultr_instance_region": model.LabelValue("ewr"),
"__meta_vultr_instance_plan": model.LabelValue("vhf-2c-4gb"),
"__meta_vultr_instance_main_ip": model.LabelValue("45.63.1.222"),
"__meta_vultr_instance_internal_ip": model.LabelValue("10.1.96.6"),
"__meta_vultr_instance_main_ipv6": model.LabelValue(""),
"__meta_vultr_instance_hostname": model.LabelValue("np-2-fd0714b5fe42"),
"__meta_vultr_instance_server_status": model.LabelValue("ok"),
"__meta_vultr_instance_vcpu_count": model.LabelValue("2"),
"__meta_vultr_instance_ram_mb": model.LabelValue("4096"),
"__meta_vultr_instance_disk_gb": model.LabelValue("128"),
"__meta_vultr_instance_allowed_bandwidth_gb": model.LabelValue("3000"),
},
{
"__address__": model.LabelValue("149.28.237.151:80"),
"__meta_vultr_instance_id": model.LabelValue("c4e58767-f61b-4c5e-9bce-43761cc04868"),
"__meta_vultr_instance_label": model.LabelValue("np-2-d04e7829fa43"),
"__meta_vultr_instance_os": model.LabelValue("Marketplace"),
"__meta_vultr_instance_os_id": model.LabelValue("426"),
"__meta_vultr_instance_region": model.LabelValue("ewr"),
"__meta_vultr_instance_plan": model.LabelValue("vhf-2c-4gb"),
"__meta_vultr_instance_main_ip": model.LabelValue("149.28.237.151"),
"__meta_vultr_instance_internal_ip": model.LabelValue("10.1.96.7"),
"__meta_vultr_instance_main_ipv6": model.LabelValue(""),
"__meta_vultr_instance_hostname": model.LabelValue("np-2-d04e7829fa43"),
"__meta_vultr_instance_server_status": model.LabelValue("ok"),
"__meta_vultr_instance_vcpu_count": model.LabelValue("2"),
"__meta_vultr_instance_ram_mb": model.LabelValue("4096"),
"__meta_vultr_instance_disk_gb": model.LabelValue("128"),
"__meta_vultr_instance_allowed_bandwidth_gb": model.LabelValue("3000"),
},
} {
t.Run(fmt.Sprintf("item %d", i), func(t *testing.T) {
require.Equal(t, k, tg.Targets[i])
})
}
}

View file

@ -19,7 +19,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
@ -195,7 +194,7 @@ func (rc *HTTPResourceClient) Fetch(ctx context.Context) (*v3.DiscoveryResponse,
return nil, err
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
@ -208,7 +207,7 @@ func (rc *HTTPResourceClient) Fetch(ctx context.Context) (*v3.DiscoveryResponse,
return nil, fmt.Errorf("non 200 status '%d' response during xDS fetch", resp.StatusCode)
}
respBody, err := ioutil.ReadAll(resp.Body)
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View file

@ -20,7 +20,6 @@ import (
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@ -92,7 +91,7 @@ func (c *KumaSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
if len(c.Server) == 0 {
return errors.Errorf("kuma SD server must not be empty: %s", c.Server)
return fmt.Errorf("kuma SD server must not be empty: %s", c.Server)
}
parsedURL, err := url.Parse(c.Server)
if err != nil {
@ -100,7 +99,7 @@ func (c *KumaSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
if len(parsedURL.Scheme) == 0 || len(parsedURL.Host) == 0 {
return errors.Errorf("kuma SD server must not be empty and have a scheme: %s", c.Server)
return fmt.Errorf("kuma SD server must not be empty and have a scheme: %s", c.Server)
}
return c.HTTPClientConfig.Validate()
@ -159,7 +158,7 @@ func convertKumaUserLabels(labels map[string]string) model.LabelSet {
// kumaMadsV1ResourceParser is an xds.resourceParser.
func kumaMadsV1ResourceParser(resources []*anypb.Any, typeURL string) ([]model.LabelSet, error) {
if typeURL != KumaMadsV1ResourceTypeURL {
return nil, errors.Errorf("received invalid typeURL for Kuma MADS v1 Resource: %s", typeURL)
return nil, fmt.Errorf("received invalid typeURL for Kuma MADS v1 Resource: %s", typeURL)
}
var targets []model.LabelSet

View file

@ -15,12 +15,12 @@ package xds
import (
"context"
"errors"
"fmt"
"testing"
"time"
v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"

View file

@ -16,7 +16,6 @@ package xds
import (
"context"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
@ -60,9 +59,9 @@ func createTestHTTPServer(t *testing.T, responder discoveryResponder) *httptest.
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
require.Equal(t, "application/json", r.Header.Get("Accept"))
body, err := ioutil.ReadAll(r.Body)
body, err := io.ReadAll(r.Body)
defer func() {
_, _ = io.Copy(ioutil.Discard, r.Body)
_, _ = io.Copy(io.Discard, r.Body)
_ = r.Body.Close()
}()
require.NotEmpty(t, body)

View file

@ -16,6 +16,7 @@ package zookeeper
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"strconv"
@ -24,7 +25,6 @@ import (
"github.com/go-kit/log"
"github.com/go-zookeeper/zk"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/discovery"
@ -80,7 +80,7 @@ func (c *ServersetSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
}
for _, path := range c.Paths {
if !strings.HasPrefix(path, "/") {
return errors.Errorf("serverset SD config paths must begin with '/': %s", path)
return fmt.Errorf("serverset SD config paths must begin with '/': %s", path)
}
}
return nil
@ -117,7 +117,7 @@ func (c *NerveSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
for _, path := range c.Paths {
if !strings.HasPrefix(path, "/") {
return errors.Errorf("nerve SD config paths must begin with '/': %s", path)
return fmt.Errorf("nerve SD config paths must begin with '/': %s", path)
}
}
return nil
@ -263,7 +263,7 @@ func parseServersetMember(data []byte, path string) (model.LabelSet, error) {
member := serversetMember{}
if err := json.Unmarshal(data, &member); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling serverset member %q", path)
return nil, fmt.Errorf("error unmarshaling serverset member %q: %w", path, err)
}
labels := model.LabelSet{}
@ -305,7 +305,7 @@ func parseNerveMember(data []byte, path string) (model.LabelSet, error) {
member := nerveMember{}
err := json.Unmarshal(data, &member)
if err != nil {
return nil, errors.Wrapf(err, "error unmarshaling nerve member %q", path)
return nil, fmt.Errorf("error unmarshaling nerve member %q: %w", path, err)
}
labels := model.LabelSet{}

View file

@ -99,7 +99,7 @@ remote_read:
# Storage related settings that are runtime reloadable.
storage:
[ - <exemplars> ... ]
[ exemplars: <exemplars> ]
# Configures exporting traces.
tracing:
@ -256,6 +256,11 @@ hetzner_sd_configs:
http_sd_configs:
[ - <http_sd_config> ... ]
# List of IONOS service discovery configurations.
ionos_sd_configs:
[ - <ionos_sd_config> ... ]
# List of Kubernetes service discovery configurations.
kubernetes_sd_configs:
[ - <kubernetes_sd_config> ... ]
@ -369,6 +374,12 @@ A `tls_config` allows configuring TLS connections.
# Disable validation of the server certificate.
[ insecure_skip_verify: <boolean> ]
# Minimum acceptable TLS version. Accepted values: TLS10 (TLS 1.0), TLS11 (TLS
# 1.1), TLS12 (TLS 1.2), TLS13 (TLS 1.3).
# If unset, Prometheus will use Go default minimum version, which is TLS 1.2.
# See MinVersion in https://pkg.go.dev/crypto/tls#Config.
[ min_version: <string> ]
```
### `<oauth2>`
@ -1533,6 +1544,86 @@ tls_config:
[ <tls_config> ]
```
### `<ionos_sd_config>`
IONOS SD configurations allows retrieving scrape targets from
[IONOS Cloud](https://cloud.ionos.com/) API. This service discovery uses the
first NICs IP address by default, but that can be changed with relabeling. The
following meta labels are available on all targets during
[relabeling](#relabel_config):
* `__meta_ionos_server_availability_zone`: the availability zone of the server
* `__meta_ionos_server_boot_cdrom_id`: the ID of the CD-ROM the server is booted
from
* `__meta_ionos_server_boot_image_id`: the ID of the boot image or snapshot the
server is booted from
* `__meta_ionos_server_boot_volume_id`: the ID of the boot volume
* `__meta_ionos_server_cpu_family`: the CPU family of the server
to
* `__meta_ionos_server_id`: the ID of the server
* `__meta_ionos_server_ip`: comma separated list of all IPs assigned to the
server
* `__meta_ionos_server_lifecycle`: the lifecycle state of the server resource
* `__meta_ionos_server_name`: the name of the server
* `__meta_ionos_server_nic_ip_<nic_name>`: comma separated list of IPs, grouped
by the name of each NIC attached to the server
* `__meta_ionos_server_servers_id`: the ID of the servers the server belongs to
* `__meta_ionos_server_state`: the execution state of the server
* `__meta_ionos_server_type`: the type of the server
```yaml
# The unique ID of the data center.
datacenter_id: <string>
# Authentication information used to authenticate to the API server.
# Note that `basic_auth` and `authorization` options are
# mutually exclusive.
# password and password_file are mutually exclusive.
# Optional HTTP basic authentication information, required when using IONOS
# Cloud username and password as authentication method.
basic_auth:
[ username: <string> ]
[ password: <secret> ]
[ password_file: <string> ]
# Optional `Authorization` header configuration, required when using IONOS
# Cloud token as authentication method.
authorization:
# Sets the authentication type.
[ type: <string> | default: Bearer ]
# Sets the credentials. It is mutually exclusive with
# `credentials_file`.
[ credentials: <secret> ]
# Sets the credentials to the credentials read from the configured file.
# It is mutually exclusive with `credentials`.
[ credentials_file: <filename> ]
# Optional OAuth 2.0 configuration.
# Cannot be used at the same time as basic_auth or authorization.
oauth2:
[ <oauth2> ]
# Optional proxy URL.
[ proxy_url: <string> ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: <boolean> | default = true ]
# Whether to enable HTTP2.
[ enable_http2: <bool> | default: true ]
# TLS configuration.
tls_config:
[ <tls_config> ]
# The port to scrape metrics from.
[ port: <int> | default = 80 ]
# The time after which the servers are refreshed.
[ refresh_interval: <duration> | default = 60s ]
```
### `<kubernetes_sd_config>`
Kubernetes SD configurations allow retrieving scrape targets from
@ -2447,6 +2538,83 @@ tls_config:
See [the Prometheus uyuni-sd configuration file](/documentation/examples/prometheus-uyuni.yml)
for a practical example on how to set up Uyuni Prometheus configuration.
### `<vultr_sd_config>`
Vultr SD configurations allow retrieving scrape targets from [Vultr](https://www.vultr.com/).
This service discovery uses the main IPv4 address by default, which that be
changed with relabelling, as demonstrated in [the Prometheus vultr-sd
configuration file](/documentation/examples/prometheus-vultr.yml).
The following meta labels are available on targets during [relabeling](#relabel_config):
* `__meta_vultr_instance_id` : A unique ID for the vultr Instance.
* `__meta_vultr_instance_label` : The user-supplied label for this instance.
* `__meta_vultr_instance_os` : The Operating System name.
* `__meta_vultr_instance_os_id` : The Operating System id used by this instance.
* `__meta_vultr_instance_region` : The Region id where the Instance is located.
* `__meta_vultr_instance_plan` : A unique ID for the Plan.
* `__meta_vultr_instance_main_ip` : The main IPv4 address.
* `__meta_vultr_instance_internal_ip` : The private IP address.
* `__meta_vultr_instance_main_ipv6` : The main IPv6 address.
* `__meta_vultr_instance_features` : List of features that are available to the instance.
* `__meta_vultr_instance_tags` : List of tags associated with the instance.
* `__meta_vultr_instance_hostname` : The hostname for this instance.
* `__meta_vultr_instance_server_status` : The server health status.
* `__meta_vultr_instance_vcpu_count` : Number of vCPUs.
* `__meta_vultr_instance_ram_mb` : The amount of RAM in MB.
* `__meta_vultr_instance_disk_gb` : The size of the disk in GB.
* `__meta_vultr_instance_allowed_bandwidth_gb` : Monthly bandwidth quota in GB.
```yaml
# Authentication information used to authenticate to the API server.
# Note that `basic_auth` and `authorization` options are
# mutually exclusive.
# password and password_file are mutually exclusive.
# Optional HTTP basic authentication information, not currently supported by Vultr.
basic_auth:
[ username: <string> ]
[ password: <secret> ]
[ password_file: <string> ]
# Optional `Authorization` header configuration.
authorization:
# Sets the authentication type.
[ type: <string> | default: Bearer ]
# Sets the credentials. It is mutually exclusive with
# `credentials_file`.
[ credentials: <secret> ]
# Sets the credentials to the credentials read from the configured file.
# It is mutually exclusive with `credentials`.
[ credentials_file: <filename> ]
# Optional OAuth 2.0 configuration.
# Cannot be used at the same time as basic_auth or authorization.
oauth2:
[ <oauth2> ]
# Optional proxy URL.
[ proxy_url: <string> ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: <boolean> | default = true ]
# Whether to enable HTTP2.
[ enable_http2: <bool> | default: true ]
# TLS configuration.
tls_config:
[ <tls_config> ]
# The port to scrape metrics from.
[ port: <int> | default = 80 ]
# The time after which the instances are refreshed.
[ refresh_interval: <duration> | default = 60s ]
```
### `<static_config>`
A `static_config` allows specifying a list of targets and a common label set
@ -2530,6 +2698,8 @@ anchored on both ends. To un-anchor the regex, use `.*<regex>.*`.
`target_label` to `replacement`, with match group references
(`${1}`, `${2}`, ...) in `replacement` substituted by their value. If `regex`
does not match, no replacement takes place.
* `lowercase`: Maps the concatenated `source_labels` to their lower case.
* `uppercase`: Maps the concatenated `source_labels` to their upper case.
* `keep`: Drop targets for which `regex` does not match the concatenated `source_labels`.
* `drop`: Drop targets for which `regex` matches the concatenated `source_labels`.
* `hashmod`: Set `target_label` to the `modulus` of a hash of the concatenated `source_labels`.
@ -2672,6 +2842,10 @@ hetzner_sd_configs:
http_sd_configs:
[ - <http_sd_config> ... ]
# List of IONOS service discovery configurations.
ionos_sd_configs:
[ - <ionos_sd_config> ... ]
# List of Kubernetes service discovery configurations.
kubernetes_sd_configs:
[ - <kubernetes_sd_config> ... ]
@ -2716,6 +2890,10 @@ triton_sd_configs:
uyuni_sd_configs:
[ - <uyuni_sd_config> ... ]
# List of Vultr service discovery configurations.
vultr_sd_configs:
[ - <vultr_sd_config> ... ]
# List of labeled statically configured Alertmanagers.
static_configs:
[ - <static_config> ... ]

View file

@ -76,6 +76,7 @@ versions.
| graphLink | expr | string | Returns path to graph view in the [expression browser](https://prometheus.io/docs/visualization/browser/) for the expression. |
| tableLink | expr | string | Returns path to tabular ("Table") view in the [expression browser](https://prometheus.io/docs/visualization/browser/) for the expression. |
| parseDuration | string | float | Parses a duration string such as "1h" into the number of seconds it represents. |
| stripDomain | string | string | Removes the domain part of a FQDN. Leaves port untouched. |
### Others

View file

@ -17,8 +17,8 @@ Return all time series with the metric `http_requests_total` and the given
http_requests_total{job="apiserver", handler="/api/comments"}
Return a whole range of time (in this case 5 minutes) for the same vector,
making it a range vector:
Return a whole range of time (in this case 5 minutes up to the query time)
for the same vector, making it a [range vector](../basics/#range-vector-selectors):
http_requests_total{job="apiserver", handler="/api/comments"}[5m]

View file

@ -103,6 +103,12 @@ for each of the given times in UTC. Returned values are from 1 to 31.
each of the given times in UTC. Returned values are from 0 to 6, where 0 means
Sunday etc.
## `day_of_year()`
`day_of_year(v=vector(time()) instant-vector)` returns the day of the year for
each of the given times in UTC. Returned values are from 1 to 365 for non-leap years,
and 1 to 366 in leap years.
## `days_in_month()`
`days_in_month(v=vector(time()) instant-vector)` returns number of days in the
@ -405,8 +411,6 @@ expression is to be evaluated.
`timestamp(v instant-vector)` returns the timestamp of each of the samples of
the given vector as the number of seconds since January 1, 1970 UTC.
*This function was added in Prometheus 2.0*
## `vector()`
`vector(s scalar)` returns the scalar `s` as a vector with no labels.

View file

@ -0,0 +1,20 @@
# Copyright 2022 The Prometheus Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
.PHONY: all
all: check-examples-syntax
.PHONY: check-examples-syntax
check-examples-syntax: prometheus-*.yml
@echo ">> check example configurations syntax"
@set -e; for file in $^; do ../../promtool check config --syntax-only $$file; done

View file

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
@ -101,11 +100,11 @@ func (d *discovery) parseServiceNodes(resp *http.Response, name string) (*target
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
b, err := ioutil.ReadAll(resp.Body)
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -168,8 +167,8 @@ func (d *discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
continue
}
b, err := ioutil.ReadAll(resp.Body)
io.Copy(ioutil.Discard, resp.Body)
b, err := io.ReadAll(resp.Body)
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
if err != nil {
level.Error(d.logger).Log("msg", "Error reading services list", "err", err)

View file

@ -18,7 +18,6 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
@ -117,7 +116,7 @@ func (a *Adapter) writeOutput() error {
b, _ := json.MarshalIndent(arr, "", " ")
dir, _ := filepath.Split(a.output)
tmpfile, err := ioutil.TempFile(dir, "sd-adapter")
tmpfile, err := os.CreateTemp(dir, "sd-adapter")
if err != nil {
return err
}
@ -156,6 +155,7 @@ func (a *Adapter) runCustomSD(ctx context.Context) {
// Run starts a Discovery Manager and the custom service discovery implementation.
func (a *Adapter) Run() {
//nolint:errcheck
go a.manager.Run()
a.manager.StartCustomProvider(a.ctx, a.name, a.disc)
go a.runCustomSD(a.ctx)

View file

@ -15,7 +15,6 @@ package adapter
import (
"context"
"io/ioutil"
"os"
"testing"
@ -223,7 +222,7 @@ func TestGenerateTargetGroups(t *testing.T) {
// TestWriteOutput checks the adapter can write a file to disk.
func TestWriteOutput(t *testing.T) {
ctx := context.Background()
tmpfile, err := ioutil.TempFile("", "sd_adapter_test")
tmpfile, err := os.CreateTemp("", "sd_adapter_test")
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
tmpfile.Close()

View file

@ -13,7 +13,7 @@ scrape_configs:
hetzner_sd_configs:
- authorization:
credentials: "<replace with a Hetzner Cloud API Token>"
platform: "hcloud"
role: "hcloud"
relabel_configs:
# Use the public IPv4 and port 9100 to scrape the target.
- source_labels: [__meta_hetzner_public_ipv4]
@ -26,7 +26,7 @@ scrape_configs:
hetzner_sd_configs:
- authorization:
credentials: "<replace with a Hetzner Cloud API Token>"
platform: "hcloud"
role: "hcloud"
relabel_configs:
# Use the private IPv4 within the Hetzner Cloud Network and port 9100 to scrape the target.
- source_labels: [__meta_hetzner_hcloud_private_ipv4_mynet]
@ -40,7 +40,7 @@ scrape_configs:
- basic_auth:
username: "<replace with a Hetzner Robot API username>"
password: "<replace with a Hetzner Robot API password>"
platform: "robot"
role: "robot"
relabel_configs:
# Use the public IPv4 and port 9100 to scrape the target.
- source_labels: [__meta_hetzner_public_ipv4]

View file

@ -0,0 +1,30 @@
# A example scrape configuration for running Prometheus with IONOS Cloud.
scrape_configs:
- job_name: "node"
ionos_sd_configs:
- basic_auth:
# Replace with your username.
username: username
# Replace with your password.
password: password
# You can find your data center unique ID here: https://dcd.ionos.com/latest/
datacenter_id: 11111111-1111-1111-1111-111111111111
port: 9100
relabel_configs:
# Only scrape servers that are available via API.
- source_labels: [__meta_ionos_server_lifecycle]
action: keep
regex: "AVAILABLE"
# Only scrape servers that are in a running state.
- source_labels: [__meta_ionos_server_state]
action: keep
regex: "RUNNING"
# Replace instance label with server name.
- source_labels: [__meta_ionos_server_name]
target_label: instance
# Add server availability zone as label.
- source_labels: [__meta_ionos_availability_zone]
target_label: ionos_zone

Some files were not shown because too many files have changed in this diff Show more