diff --git a/.circleci/config.yml b/.circleci/config.yml index edc5c0d..352264d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ executors: # also be updated. golang: docker: - - image: cimg/go:1.21 + - image: cimg/go:1.23 jobs: test: executor: golang diff --git a/.github/workflows/container_description.yml b/.github/workflows/container_description.yml new file mode 100644 index 0000000..1448594 --- /dev/null +++ b/.github/workflows/container_description.yml @@ -0,0 +1,57 @@ +--- +name: Push README to Docker Hub +on: + push: + paths: + - "README.md" + - "README-containers.md" + - ".github/workflows/container_description.yml" + branches: [ main, master ] + +permissions: + contents: read + +jobs: + PushDockerHubReadme: + runs-on: ubuntu-latest + name: Push README to Docker Hub + if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. + steps: + - name: git checkout + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - name: Set docker hub repo name + run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV + - name: Push README to Dockerhub + uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1 + env: + DOCKER_USER: ${{ secrets.DOCKER_HUB_LOGIN }} + DOCKER_PASS: ${{ secrets.DOCKER_HUB_PASSWORD }} + with: + destination_container_repo: ${{ env.DOCKER_REPO_NAME }} + provider: dockerhub + short_description: ${{ env.DOCKER_REPO_NAME }} + # Empty string results in README-containers.md being pushed if it + # exists. Otherwise, README.md is pushed. + readme_file: '' + + PushQuayIoReadme: + runs-on: ubuntu-latest + name: Push README to quay.io + if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. + steps: + - name: git checkout + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - name: Set quay.io org name + run: echo "DOCKER_REPO=$(echo quay.io/${GITHUB_REPOSITORY_OWNER} | tr -d '-')" >> $GITHUB_ENV + - name: Set quay.io repo name + run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV + - name: Push README to quay.io + uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1 + env: + DOCKER_APIKEY: ${{ secrets.QUAY_IO_API_TOKEN }} + with: + destination_container_repo: ${{ env.DOCKER_REPO_NAME }} + provider: quay + # Empty string results in README-containers.md being pushed if it + # exists. Otherwise, README.md is pushed. + readme_file: '' diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index babd8a0..7183091 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -12,21 +12,28 @@ on: - ".golangci.yml" pull_request: +permissions: # added using https://github.com/step-security/secure-repo + contents: read + jobs: golangci: + permissions: + contents: read # for actions/checkout to fetch code + pull-requests: read # for golangci/golangci-lint-action to fetch pull requests name: lint runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: install Go - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - name: Install Go + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: - go-version: 1.20.x + go-version: 1.23.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@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 + uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 with: - version: v1.54.2 + args: --verbose + version: v1.60.2 diff --git a/.golangci.yml b/.golangci.yml index cf03cbd..495f80e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,12 +1,10 @@ --- +linters: + enable: + - sloglint + issues: exclude-rules: - path: _test.go linters: - errcheck - -linters-settings: - errcheck: - exclude-functions: - # Never check for logger errors. - - (github.com/go-kit/log.Logger).Log diff --git a/.promu.yml b/.promu.yml index 75c55e4..6d82cf3 100644 --- a/.promu.yml +++ b/.promu.yml @@ -1,7 +1,7 @@ go: # Whenever the Go version is updated here, # .circleci/config.yml should also be updated. - version: 1.21 + version: 1.23 repository: path: github.com/prometheus-community/ipmi_exporter diff --git a/.yamllint b/.yamllint index 955a5a6..1859cb6 100644 --- a/.yamllint +++ b/.yamllint @@ -1,5 +1,7 @@ --- extends: default +ignore: | + ui/react-app/node_modules rules: braces: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c12929..d2c79b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ ## next -## 1.8.0 / 2024-10-23 +## 1.9.0 / 2024-10-17 + +* Bring back aarch64 builds (#186) +* Ignore time parse error in SEL events (#198) +* Don't prepend to already absolute path from config (#199) +* Various dependency updates + +## 1.8.0 / 2024-01-23 * Added BMC watchdog collector (#176) * Added SEL event metrics collector (#179) diff --git a/Dockerfile b/Dockerfile index cddf680..79e4bc6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,9 @@ RUN make RUN mv ipmi_exporter / #Copy the ipmi_expoter binary -FROM alpine:3 +ARG ARCH="amd64" +ARG OS="linux" +FROM --platform=${OS}/${ARCH} alpine:3 RUN apk --no-cache add freeipmi LABEL maintainer="The Prometheus Authors " WORKDIR / diff --git a/Makefile b/Makefile index 41853fa..a6ff618 100644 --- a/Makefile +++ b/Makefile @@ -15,8 +15,7 @@ .PHONY: all all: precheck style unused build test -#DOCKER_ARCHS ?= amd64 arm64 -DOCKER_ARCHS ?= amd64 +DOCKER_ARCHS ?= amd64 arm64 DOCKER_IMAGE_NAME ?= ipmi-exporter DOCKER_REPO ?= prometheuscommunity diff --git a/Makefile.common b/Makefile.common index 062a281..cbb5d86 100644 --- a/Makefile.common +++ b/Makefile.common @@ -49,23 +49,23 @@ endif GOTEST := $(GO) test GOTEST_DIR := ifneq ($(CIRCLE_JOB),) -ifneq ($(shell command -v gotestsum > /dev/null),) +ifneq ($(shell command -v gotestsum 2> /dev/null),) GOTEST_DIR := test-results GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml -- endif endif -PROMU_VERSION ?= 0.15.0 +PROMU_VERSION ?= 0.17.0 PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v1.54.2 -# golangci-lint only supports linux, darwin and windows platforms on i386/amd64. +GOLANGCI_LINT_VERSION ?= v1.60.2 +# golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) - ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) + ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64)) # If we're in CI and there is an Actions file, that means the linter # is being run in Actions, so we don't need to run it here. ifneq (,$(SKIP_GOLANGCI_LINT)) @@ -169,16 +169,20 @@ common-vet: common-lint: $(GOLANGCI_LINT) ifdef GOLANGCI_LINT @echo ">> running golangci-lint" -# 'go list' needs to be executed before staticcheck to prepopulate the modules cache. -# Otherwise staticcheck might fail randomly for some reason not yet explained. - $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) endif +.PHONY: common-lint-fix +common-lint-fix: $(GOLANGCI_LINT) +ifdef GOLANGCI_LINT + @echo ">> running golangci-lint fix" + $(GOLANGCI_LINT) run --fix $(GOLANGCI_LINT_OPTS) $(pkgs) +endif + .PHONY: common-yamllint common-yamllint: @echo ">> running yamllint on all YAML files in the repository" -ifeq (, $(shell command -v yamllint > /dev/null)) +ifeq (, $(shell command -v yamllint 2> /dev/null)) @echo "yamllint not installed so skipping" else yamllint . @@ -204,6 +208,10 @@ common-tarball: promu @echo ">> building release tarball" $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) +.PHONY: common-docker-repo-name +common-docker-repo-name: + @echo "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)" + .PHONY: common-docker $(BUILD_DOCKER_ARCHS) common-docker: $(BUILD_DOCKER_ARCHS) $(BUILD_DOCKER_ARCHS): common-docker-%: @@ -267,3 +275,9 @@ $(1)_precheck: exit 1; \ fi endef + +govulncheck: install-govulncheck + govulncheck ./... + +install-govulncheck: + command -v govulncheck > /dev/null || go install golang.org/x/vuln/cmd/govulncheck@latest diff --git a/README.md b/README.md index 905a1a6..24a6d67 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,12 @@ This is an IPMI exporter for [Prometheus][prometheus]. It supports both the regular `/metrics` endpoint, exposing metrics from the host that the exporter is running on, as well as an `/ipmi` endpoint that -supports IPMI over RMCP - one exporter running on one host can be used to -monitor a large number of IPMI interfaces by passing the `target` parameter to -a scrape. +supports IPMI over RMCP, implementing the multi-target exporter pattern. If you +plan to use the latter, please read the guide [Understanding and using the +multi-target exporter pattern][multi-target] to get the general idea about the +configuration. + +[multi-target]: https://prometheus.io/docs/guides/multi-target-exporter/ The exporter relies on tools from the [FreeIPMI][freeipmi] suite for the actual IPMI implementation. @@ -28,6 +31,12 @@ For Kubernets, you can use the community-maintained [Helm chart][helm]. [helm]: https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus-ipmi-exporter "IPMI exporter Helm chart in the helm-charts Github repo" +Pre-built container images are available on [dockerhub][dockerhub] and +[quay.io][quay.io]. + +[dockerhub]: https://hub.docker.com/r/prometheuscommunity/ipmi-exporter +[quay.io]: https://quay.io/repository/prometheuscommunity/ipmi-exporter + ### Building from source You need a Go development environment. Then, simply run `make` to build the @@ -40,23 +49,16 @@ This uses the common prometheus tooling to build and run some tests. Alternatively, you can use the standard Go tooling, which will install the executable in `$GOPATH/bin`: - go get github.com/prometheus-community/ipmi_exporter + go install github.com/prometheus-community/ipmi_exporter@latest -### Building a Docker container +### Building a container image -You can build a Docker container with the included `docker` make target: +You can build a container image with the included `docker` make target: make promu - promu crossbuild -p linux/amd64 + promu crossbuild -p linux/amd64 -p linux/arm64 make docker -This will not even require Go tooling on the host. See the included [docker -compose example](docker-compose.yml) for how to use the resulting container. - -### Building a RPM Package - -See [how to build a RPM package](contrib/rpm/README.md). - ## Running A minimal invocation looks like this: @@ -83,44 +85,12 @@ installed: - `ipmi-sel` - `ipmi-chassis` -### Running as unprivileged user - -If you are running the exporter as unprivileged user, but need to execute the -FreeIPMI tools as root, you can do the following: - - 1. Add sudoers files to permit the following commands - ``` - ipmi-exporter ALL = NOPASSWD: /usr/sbin/ipmimonitoring,\ - /usr/sbin/ipmi-sensors,\ - /usr/sbin/ipmi-dcmi,\ - /usr/sbin/ipmi-raw,\ - /usr/sbin/bmc-info,\ - /usr/sbin/ipmi-chassis,\ - /usr/sbin/ipmi-sel - ``` - 2. In your module config, override the collector command with `sudo` for - every collector you are using and add the actual command as custom - argument. Example for the "ipmi" collector: - ```yaml - collector_cmd: - ipmi: sudo - custom_args: - ipmi: - - "ipmimonitoring" - ``` - See the last module in the [example config](ipmi_remote.yml). - -### Running in Docker - -**NOTE:** you should only use Docker for remote metrics. - -See [Building a Docker container](#building-a-docker-container) and the example -`docker-compose.yml`. Edit the `ipmi_remote.yml` file to configure IPMI -credentials, then run with: - - sudo docker-compose up -d - -By default, the server will bind on `0.0.0.0:9290`. +When running a container image, make sure to: + + - set `config.file` to where the config file is mounted + - expose the default port (9290) or set `web.listen-address` accordingly + +**NOTE:** you should only use containers for collecting remote metrics. ## Configuration @@ -142,3 +112,9 @@ using the `--web.config.file` parameter. The format of the file is described For a description of the metrics that this exporter provides, see the [metrics](docs/metrics.md) document. + +## Privileges + +Collecting host-local IPMI metrics requires root privileges. See +[privileges](docs/privileges.md) document for how to avoid running the exporter +as root. diff --git a/VERSION b/VERSION index 27f9cd3..f8e233b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.0 +1.9.0 diff --git a/collector.go b/collector.go index 8611ae8..6832d6b 100644 --- a/collector.go +++ b/collector.go @@ -17,7 +17,6 @@ import ( "path" "time" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" @@ -81,7 +80,7 @@ func (c metaCollector) Collect(ch chan<- prometheus.Metric) { start := time.Now() defer func() { duration := time.Since(start).Seconds() - level.Debug(logger).Log("msg", "Scrape duration", "target", targetName(c.target), "duration", duration) + logger.Debug("Scrape duration", "target", targetName(c.target), "duration", duration) ch <- prometheus.MustNewConstMetric( durationDesc, prometheus.GaugeValue, @@ -97,9 +96,12 @@ func (c metaCollector) Collect(ch chan<- prometheus.Metric) { for _, collector := range config.GetCollectors() { var up int - level.Debug(logger).Log("msg", "Running collector", "target", target.host, "collector", collector.Name()) + logger.Debug("Running collector", "target", target.host, "collector", collector.Name()) - fqcmd := path.Join(*executablesPath, collector.Cmd()) + fqcmd := collector.Cmd() + if !path.IsAbs(fqcmd) { + fqcmd = path.Join(*executablesPath, collector.Cmd()) + } args := collector.Args() cfg := config.GetFreeipmiConfig() diff --git a/collector_bmc.go b/collector_bmc.go index e2254db..493cea1 100644 --- a/collector_bmc.go +++ b/collector_bmc.go @@ -14,7 +14,6 @@ package main import ( - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" @@ -28,7 +27,7 @@ var ( bmcInfoDesc = prometheus.NewDesc( prometheus.BuildFQName(namespace, "bmc", "info"), "Constant metric with value '1' providing details about the BMC.", - []string{"firmware_revision", "manufacturer_id", "system_firmware_version"}, + []string{"firmware_revision", "manufacturer_id", "system_firmware_version", "bmc_url"}, nil, ) ) @@ -50,25 +49,31 @@ func (c BMCCollector) Args() []string { func (c BMCCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { firmwareRevision, err := freeipmi.GetBMCInfoFirmwareRevision(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect BMC data", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect BMC data", "target", targetName(target.host), "error", err) return 0, err } manufacturerID, err := freeipmi.GetBMCInfoManufacturerID(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect BMC data", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect BMC data", "target", targetName(target.host), "error", err) return 0, err } systemFirmwareVersion, err := freeipmi.GetBMCInfoSystemFirmwareVersion(result) if err != nil { // This one is not always available. - level.Debug(logger).Log("msg", "Failed to parse bmc-info data", "target", targetName(target.host), "error", err) + logger.Debug("Failed to parse bmc-info data", "target", targetName(target.host), "error", err) systemFirmwareVersion = "N/A" } + bmcUrl, err := freeipmi.GetBMCInfoBmcUrl(result) + if err != nil { + // This one is not always available. + logger.Debug("Failed to parse bmc-info data", "target", targetName(target.host), "error", err) + bmcUrl = "N/A" + } ch <- prometheus.MustNewConstMetric( bmcInfoDesc, prometheus.GaugeValue, 1, - firmwareRevision, manufacturerID, systemFirmwareVersion, + firmwareRevision, manufacturerID, systemFirmwareVersion, bmcUrl, ) return 1, nil } diff --git a/collector_bmc_watchdog.go b/collector_bmc_watchdog.go index 86d7f9d..8cb8578 100644 --- a/collector_bmc_watchdog.go +++ b/collector_bmc_watchdog.go @@ -14,7 +14,6 @@ package main import ( - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" @@ -95,42 +94,42 @@ func (c BMCWatchdogCollector) Args() []string { func (c BMCWatchdogCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { timerState, err := freeipmi.GetBMCWatchdogTimerState(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect BMC watchdog timer", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect BMC watchdog timer", "target", targetName(target.host), "error", err) return 0, err } currentTimerUse, err := freeipmi.GetBMCWatchdogTimerUse(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect BMC watchdog timer use", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect BMC watchdog timer use", "target", targetName(target.host), "error", err) return 0, err } loggingState, err := freeipmi.GetBMCWatchdogLoggingState(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect BMC watchdog logging", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect BMC watchdog logging", "target", targetName(target.host), "error", err) return 0, err } currentTimeoutAction, err := freeipmi.GetBMCWatchdogTimeoutAction(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect BMC watchdog timeout action", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect BMC watchdog timeout action", "target", targetName(target.host), "error", err) return 0, err } currentPretimeoutInterrupt, err := freeipmi.GetBMCWatchdogPretimeoutInterrupt(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect BMC watchdog pretimeout interrupt", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect BMC watchdog pretimeout interrupt", "target", targetName(target.host), "error", err) return 0, err } pretimeoutInterval, err := freeipmi.GetBMCWatchdogPretimeoutInterval(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect BMC watchdog pretimeout interval", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect BMC watchdog pretimeout interval", "target", targetName(target.host), "error", err) return 0, err } initialCountdown, err := freeipmi.GetBMCWatchdogInitialCountdown(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect BMC watchdog initial countdown", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect BMC watchdog initial countdown", "target", targetName(target.host), "error", err) return 0, err } currentCountdown, err := freeipmi.GetBMCWatchdogCurrentCountdown(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect BMC watchdog current countdown", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect BMC watchdog current countdown", "target", targetName(target.host), "error", err) return 0, err } diff --git a/collector_chassis.go b/collector_chassis.go index 98f0a2e..094c419 100644 --- a/collector_chassis.go +++ b/collector_chassis.go @@ -14,7 +14,6 @@ package main import ( - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" @@ -62,17 +61,17 @@ func (c ChassisCollector) Args() []string { func (c ChassisCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { currentChassisPowerState, err := freeipmi.GetChassisPowerState(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect chassis data", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect chassis data", "target", targetName(target.host), "error", err) return 0, err } currentChassisDriveFault, err := freeipmi.GetChassisDriveFault(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect chassis data", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect chassis data", "target", targetName(target.host), "error", err) return 0, err } currentChassisCoolingFault, err := freeipmi.GetChassisCoolingFault(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect chassis data", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect chassis data", "target", targetName(target.host), "error", err) return 0, err } ch <- prometheus.MustNewConstMetric( diff --git a/collector_dcmi.go b/collector_dcmi.go index 7684eef..d7af65b 100644 --- a/collector_dcmi.go +++ b/collector_dcmi.go @@ -14,7 +14,6 @@ package main import ( - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" @@ -50,7 +49,7 @@ func (c DCMICollector) Args() []string { func (c DCMICollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { currentPowerConsumption, err := freeipmi.GetCurrentPowerConsumption(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect DCMI data", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect DCMI data", "target", targetName(target.host), "error", err) return 0, err } // Returned value negative == Power Measurement is not avail diff --git a/collector_ipmi.go b/collector_ipmi.go index 5ecdf02..6f93bf7 100644 --- a/collector_ipmi.go +++ b/collector_ipmi.go @@ -18,7 +18,6 @@ import ( "math" "strconv" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" @@ -133,7 +132,7 @@ func (c IPMICollector) Cmd() string { func (c IPMICollector) Args() []string { return []string{ - "-Q", + "--quiet-cache", "--ignore-unrecognized-events", "--comma-separated-output", "--no-header-output", @@ -148,7 +147,7 @@ func (c IPMICollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metr targetHost := targetName(target.host) results, err := freeipmi.GetSensorData(result, excludeIds) if err != nil { - level.Error(logger).Log("msg", "Failed to collect sensor data", "target", targetHost, "error", err) + logger.Error("Failed to collect sensor data", "target", targetHost, "error", err) return 0, err } for _, data := range results { @@ -164,11 +163,11 @@ func (c IPMICollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metr case "N/A": state = math.NaN() default: - level.Error(logger).Log("msg", "Unknown sensor state", "target", targetHost, "state", data.State) + logger.Error("Unknown sensor state", "target", targetHost, "state", data.State) state = math.NaN() } - level.Debug(logger).Log("msg", "Got values", "target", targetHost, "data", fmt.Sprintf("%+v", data)) + logger.Debug("Got values", "target", targetHost, "data", fmt.Sprintf("%+v", data)) switch data.Unit { case "RPM": diff --git a/collector_sel.go b/collector_sel.go index 96df7d6..f850cc8 100644 --- a/collector_sel.go +++ b/collector_sel.go @@ -14,7 +14,6 @@ package main import ( - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" @@ -57,12 +56,12 @@ func (c SELCollector) Args() []string { func (c SELCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { entriesCount, err := freeipmi.GetSELInfoEntriesCount(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect SEL data", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect SEL data", "target", targetName(target.host), "error", err) return 0, err } freeSpace, err := freeipmi.GetSELInfoFreeSpace(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect SEL data", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect SEL data", "target", targetName(target.host), "error", err) return 0, err } ch <- prometheus.MustNewConstMetric( diff --git a/collector_sel_events.go b/collector_sel_events.go index d45be27..826fcdd 100644 --- a/collector_sel_events.go +++ b/collector_sel_events.go @@ -16,7 +16,6 @@ package main import ( "time" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" @@ -24,6 +23,7 @@ import ( const ( SELEventsCollectorName CollectorName = "sel-events" + SELDateTimeFormat string = "Jan-02-2006 15:04:05" ) var ( @@ -65,7 +65,7 @@ func (c SELEventsCollector) Cmd() string { func (c SELEventsCollector) Args() []string { return []string{ - "-Q", + "--quiet-cache", "--comma-separated-output", "--no-header-output", "--sdr-cache-recreate", @@ -80,7 +80,7 @@ func (c SELEventsCollector) Collect(result freeipmi.Result, ch chan<- prometheus events, err := freeipmi.GetSELEvents(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect SEL events", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect SEL events", "target", targetName(target.host), "error", err) return 0, err } @@ -98,12 +98,20 @@ func (c SELEventsCollector) Collect(result freeipmi.Result, ch chan<- prometheus for _, metricConfig := range selEventConfigs { match := metricConfig.Regex.FindStringSubmatch(data.Event) if match != nil { - t, err := time.Parse("Jan-02-2006 15:04:05", data.Date+" "+data.Time) + var newTimestamp float64 = 0 + datetime := data.Date + " " + data.Time + t, err := time.Parse(SELDateTimeFormat, datetime) + // ignore errors with invalid date or time + // NOTE: in some cases ipmi-sel can return "PostInit" in Date and Time fields + // Example: + // $ ipmi-sel --comma-separated-output --output-event-state --interpret-oem-data --output-oem-event-strings + // ID,Date,Time,Name,Type,State,Event + // 3,PostInit,PostInit,Sensor #211,Memory,Warning,Correctable memory error ; Event Data3 = 34h if err != nil { - level.Error(logger).Log("msg", "Failed to collect SEL event metrics", "target", targetName(target.host), "error", err) - return 0, err + logger.Debug("Failed to parse time", "target", targetName(target.host), "error", err) + } else { + newTimestamp = float64(t.Unix()) } - newTimestamp := float64(t.Unix()) // save latest timestamp by name metrics if newTimestamp > selEventByNameTimestamp[metricConfig.Name] { selEventByNameTimestamp[metricConfig.Name] = newTimestamp diff --git a/collector_sm_lan_mode.go b/collector_sm_lan_mode.go index 0fa71b7..cc6e7b7 100644 --- a/collector_sm_lan_mode.go +++ b/collector_sm_lan_mode.go @@ -17,7 +17,6 @@ import ( "fmt" "strconv" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" @@ -53,11 +52,11 @@ func (c SMLANModeCollector) Args() []string { func (c SMLANModeCollector) Collect(result freeipmi.Result, ch chan<- prometheus.Metric, target ipmiTarget) (int, error) { octets, err := freeipmi.GetRawOctets(result) if err != nil { - level.Error(logger).Log("msg", "Failed to collect LAN mode data", "target", targetName(target.host), "error", err) + logger.Error("Failed to collect LAN mode data", "target", targetName(target.host), "error", err) return 0, err } if len(octets) != 3 { - level.Error(logger).Log("msg", "Unexpected number of octets", "target", targetName(target.host), "octets", octets) + logger.Error("Unexpected number of octets", "target", targetName(target.host), "octets", octets) return 0, fmt.Errorf("unexpected number of octets in raw response: %d", len(octets)) } @@ -66,7 +65,7 @@ func (c SMLANModeCollector) Collect(result freeipmi.Result, ch chan<- prometheus value, _ := strconv.Atoi(octets[2]) ch <- prometheus.MustNewConstMetric(lanModeDesc, prometheus.GaugeValue, float64(value)) default: - level.Error(logger).Log("msg", "Unexpected lan mode status (ipmi-raw)", "target", targetName(target.host), "sgatus", octets[2]) + logger.Error("Unexpected lan mode status (ipmi-raw)", "target", targetName(target.host), "sgatus", octets[2]) return 0, fmt.Errorf("unexpected lan mode status: %s", octets[2]) } diff --git a/config.go b/config.go index 8994a1c..814ffe8 100644 --- a/config.go +++ b/config.go @@ -20,7 +20,6 @@ import ( "strings" "sync" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus-community/ipmi_exporter/freeipmi" @@ -239,7 +238,7 @@ func (sc *SafeConfig) ReloadConfig(configFile string) error { if configFile != "" { config, err = os.ReadFile(configFile) if err != nil { - level.Error(logger).Log("msg", "Error reading config file", "error", err) + logger.Error("Error reading config file", "error", err) return err } } else { @@ -255,7 +254,7 @@ func (sc *SafeConfig) ReloadConfig(configFile string) error { sc.Unlock() if configFile != "" { - level.Info(logger).Log("msg", "Loaded config file", "path", configFile) + logger.Info("Loaded config file", "path", configFile) } return nil } @@ -281,7 +280,7 @@ func (sc *SafeConfig) ConfigForTarget(target, module string) IPMIConfig { if module != "default" { config, ok = sc.C.Modules[module] if !ok { - level.Error(logger).Log("msg", "Requested module not found, using default", "module", module, "target", targetName(target)) + logger.Error("Requested module not found, using default", "module", module, "target", targetName(target)) } } @@ -290,7 +289,7 @@ func (sc *SafeConfig) ConfigForTarget(target, module string) IPMIConfig { config, ok = sc.C.Modules["default"] if !ok { // This is probably fine for running locally, so not making this a warning - level.Debug(logger).Log("msg", "Needed default config for, but none configured, using FreeIPMI defaults", "target", targetName(target)) + logger.Debug("Needed default config for, but none configured, using FreeIPMI defaults", "target", targetName(target)) config = defaultConfig } } diff --git a/docs/configuration.md b/docs/configuration.md index cc7cf9c..b8d4f0d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,17 +1,20 @@ # Configuration Simply scraping the standard `/metrics` endpoint will make the exporter emit -local IPMI metrics. No special configuration is required. - -For remote metrics, the general configuration pattern is similar to that of the -[blackbox exporter](https://github.com/prometheus/blackbox_exporter), i.e. -Prometheus scrapes a small number (possibly one) of IPMI exporters with a -`target` and `module` URL parameter to tell the exporter which IPMI device it -should use to retrieve the IPMI metrics. We offer this approach as IPMI devices -often provide useful information even while the supervised host is turned off. -If you are running the exporter on a separate host anyway, it makes more sense -to have only a few of them, each probing many (possibly thousands of) IPMI -devices, rather than one exporter per IPMI device. +local IPMI metrics. If the exporter is running with sufficient privileges, no +special configuration is required. See the [privileges document](privileges.md) +for more details. + +For remote metrics, the general configuration pattern is that of a +[multi-target exporter][multi-target]. Please read that guide to get the general +idea about this approach. + +[multi-target]: https://prometheus.io/docs/guides/multi-target-exporter/ "Understanding and using the multi-target exporter pattern - Prometheus docs" + +We offer this approach as IPMI devices often provide useful information even +while the supervised host is turned off. Also, you can have a single exporter +instance probing many (possibly thousands of) IPMI devices, rather than one +exporter per IPMI device. **NOTE:** If you are using remote metrics, but still want to get the local process metrics from the instance, you must use a `default` module with an @@ -83,7 +86,7 @@ Create a YAML file that contains a list of targets, e.g.: job: ipmi_exporter ``` -This file needs to be stored on the Prometheus server host. Assuming that this +This file needs to be stored on the Prometheus server host. Assuming that this file is called `/srv/ipmi_exporter/targets.yml`, and the IPMI exporter is running on a host that has the DNS name `ipmi-exporter.internal.example.com`, add the following to your Prometheus config: diff --git a/docs/privileges.md b/docs/privileges.md new file mode 100644 index 0000000..c8c1035 --- /dev/null +++ b/docs/privileges.md @@ -0,0 +1,31 @@ +# Privileges + +If you are running the exporter as unprivileged user, but need to execute the +FreeIPMI tools as root (as is likely necessary to access the local IPMI +interface), you can do the following: + +**NOTE:** Make sure to adapt all absolute paths to match your distro! + + 1. Add sudoers files to permit the following commands + ``` + ipmi-exporter ALL = NOPASSWD: /usr/sbin/ipmimonitoring,\ + /usr/sbin/ipmi-sensors,\ + /usr/sbin/ipmi-dcmi,\ + /usr/sbin/ipmi-raw,\ + /usr/sbin/bmc-info,\ + /usr/sbin/ipmi-chassis,\ + /usr/sbin/ipmi-sel + ``` + 2. In your module config, override the collector command with `sudo` for + every collector you are using and add the actual command as custom + argument. Example for the "ipmi" collector: + ```yaml + collector_cmd: + ipmi: /usr/bin/sudo + custom_args: + ipmi: + - "/usr/sbin/ipmimonitoring" + ``` + See also the [sudo example config](../ipmi_local_sudo.yml). + +Note that no elevated privileges are necessary for getting remote metrics. diff --git a/freeipmi/freeipmi.go b/freeipmi/freeipmi.go index 0395c3a..9792583 100644 --- a/freeipmi/freeipmi.go +++ b/freeipmi/freeipmi.go @@ -20,6 +20,7 @@ import ( "encoding/csv" "encoding/hex" "fmt" + "log/slog" "math" "os" "os/exec" @@ -28,9 +29,6 @@ import ( "strconv" "strings" "syscall" - - "github.com/go-kit/log" - "github.com/go-kit/log/level" ) var ( @@ -45,6 +43,7 @@ var ( bmcInfoFirmwareRevisionRegex = regexp.MustCompile(`^Firmware Revision\s*:\s*(?P[0-9.]*).*`) bmcInfoSystemFirmwareVersionRegex = regexp.MustCompile(`^System Firmware Version\s*:\s*(?P[0-9.]*).*`) bmcInfoManufacturerIDRegex = regexp.MustCompile(`^Manufacturer ID\s*:\s*(?P.*)`) + bmcInfoBmcUrlRegex = regexp.MustCompile(`^BMC URL\s*:\s*(?P.*)`) bmcWatchdogTimerStateRegex = regexp.MustCompile(`^Timer:\s*(?PRunning|Stopped)`) bmcWatchdogTimerUseRegex = regexp.MustCompile(`^Timer Use:\s*(?P.*)`) bmcWatchdogTimerLoggingRegex = regexp.MustCompile(`^Logging:\s*(?PEnabled|Disabled)`) @@ -124,7 +123,7 @@ func getValue(ipmiOutput []byte, regex *regexp.Regexp) (string, error) { return "", fmt.Errorf("could not find value in output: %s", string(ipmiOutput)) } -func freeipmiConfigPipe(config string, logger log.Logger) (string, error) { +func freeipmiConfigPipe(config string, logger *slog.Logger) (string, error) { content := []byte(config) pipe, err := pipeName() if err != nil { @@ -138,24 +137,24 @@ func freeipmiConfigPipe(config string, logger log.Logger) (string, error) { go func(file string, data []byte) { f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModeNamedPipe) if err != nil { - level.Error(logger).Log("msg", "Error opening pipe", "error", err) + logger.Error("Error opening pipe", "error", err) } if _, err := f.Write(data); err != nil { - level.Error(logger).Log("msg", "Error writing config to pipe", "error", err) + logger.Error("Error writing config to pipe", "error", err) } f.Close() }(pipe, content) return pipe, nil } -func Execute(cmd string, args []string, config string, target string, logger log.Logger) Result { +func Execute(cmd string, args []string, config string, target string, logger *slog.Logger) Result { pipe, err := freeipmiConfigPipe(config, logger) if err != nil { return Result{nil, err} } defer func() { if err := os.Remove(pipe); err != nil { - level.Error(logger).Log("msg", "Error deleting named pipe", "error", err) + logger.Error("Error deleting named pipe", "error", err) } }() @@ -164,7 +163,7 @@ func Execute(cmd string, args []string, config string, target string, logger log args = append(args, "-h", target) } - level.Debug(logger).Log("msg", "Executing", "command", cmd, "args", fmt.Sprintf("%+v", args)) + logger.Debug("Executing", "command", cmd, "args", fmt.Sprintf("%+v", args)) out, err := exec.Command(cmd, args...).CombinedOutput() if err != nil { err = fmt.Errorf("error running %s: %s", cmd, err) @@ -315,6 +314,13 @@ func GetBMCInfoSystemFirmwareVersion(ipmiOutput Result) (string, error) { return getValue(ipmiOutput.output, bmcInfoSystemFirmwareVersionRegex) } +func GetBMCInfoBmcUrl(ipmiOutput Result) (string, error) { + if ipmiOutput.err != nil { + return "", fmt.Errorf("%s: %s", ipmiOutput.err, ipmiOutput.output) + } + return getValue(ipmiOutput.output, bmcInfoBmcUrlRegex) +} + func GetSELInfoEntriesCount(ipmiOutput Result) (float64, error) { if ipmiOutput.err != nil { return -1, fmt.Errorf("%s: %s", ipmiOutput.err, ipmiOutput.output) diff --git a/go.mod b/go.mod index d78f001..f4c905a 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,34 @@ module github.com/prometheus-community/ipmi_exporter -go 1.19 +go 1.22 require ( github.com/alecthomas/kingpin/v2 v2.4.0 - github.com/go-kit/log v0.2.1 - github.com/prometheus/client_golang v1.18.0 - github.com/prometheus/common v0.45.0 - github.com/prometheus/exporter-toolkit v0.11.0 + github.com/prometheus/client_golang v1.20.5 + github.com/prometheus/common v0.60.1 + github.com/prometheus/exporter-toolkit v0.13.1 gopkg.in/yaml.v2 v2.4.0 ) require ( - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/jpillora/backoff v1.0.0 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/mdlayher/vsock v1.2.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.12.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.31.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go.sum b/go.sum index b67c7c0..e234262 100644 --- a/go.sum +++ b/go.sum @@ -1,80 +1,81 @@ github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 h1:t3eaIm0rUkzbrIewtiFmMK5RXHej2XnoXNhxVsAYUfg= +github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= -github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/exporter-toolkit v0.11.0 h1:yNTsuZ0aNCNFQ3aFTD2uhPOvr4iD7fdBvKPAEGkNf+g= -github.com/prometheus/exporter-toolkit v0.11.0/go.mod h1:BVnENhnNecpwoTLiABx7mrPB/OLRIgN74qlQbV+FK1Q= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/exporter-toolkit v0.13.1 h1:Evsh0gWQo2bdOHlnz9+0Nm7/OFfIwhE2Ws4A2jIlR04= +github.com/prometheus/exporter-toolkit v0.13.1/go.mod h1:ujdv2YIOxtdFxxqtloLpbqmxd5J0Le6IITUvIRSWjj0= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ipmi_local.yml b/ipmi_local.yml index af2dc71..e30cee8 100644 --- a/ipmi_local.yml +++ b/ipmi_local.yml @@ -1,7 +1,9 @@ # Configuration file for ipmi_exporter # This is an example config for scraping the local host. -# In most cases, this should work without using a config file at all. +# In most cases, this should work without using a config file at all, but here +# are some examples of things that can be customized. If you require privilege +# elevation to get the local metrics, see the `ipmi_local_sudo.yml` example. modules: default: # Available collectors are bmc, bmc-watchdog, ipmi, chassis, dcmi, sel, sel-events and sm-lan-mode diff --git a/ipmi_local_sudo.yml b/ipmi_local_sudo.yml new file mode 100644 index 0000000..9f28818 --- /dev/null +++ b/ipmi_local_sudo.yml @@ -0,0 +1,32 @@ +# Configuration file for ipmi_exporter + +# This is an example config for scraping the local host, using `sudo` to +# elevate privileges for access to the IPMI interface. +modules: + default: + # Available collectors are bmc, bmc-watchdog, ipmi, chassis, dcmi, sel, sel-events and sm-lan-mode + collectors: + - ipmi + - sel + # Got any sensors you don't care about? Add them here. + exclude_sensor_ids: + - 2 + - 29 + - 32 + # Define custom metrics for SEL entries + sel_events: + - name: correctable_memory_error + regex: Correctable memory error.* + # USING ANY OF THE BELOW VOIDS YOUR WARRANTY! YOU MAY GET BITTEN BY SHARKS! + # You can override the command to be executed for a collector. Paired with + # custom_args, this can be used to e.g. execute the IPMI tools with sudo. + # Must be added for every enabled collector. Adapt the path to match your + # distro, and read `docs/privileges.md`! + collector_cmd: + ipmi: /usr/bin/sudo + sel: /usr/bin/sudo + custom_args: + ipmi: + - "/usr/sbin/ipmimonitoring" + sel: + - "/usr/sbin/ipmi-sel" diff --git a/ipmi_remote.yml b/ipmi_remote.yml index 94a7ffc..23dbde2 100644 --- a/ipmi_remote.yml +++ b/ipmi_remote.yml @@ -4,7 +4,6 @@ # Information required to access remote IPMI interfaces can be supplied in the # 'modules' section. A scrape can request the usage of a given config by # setting the `module` URL parameter. - modules: default: # These settings are used if no module is specified, the @@ -66,23 +65,3 @@ modules: custom_args: ipmi: - "--bridge-sensors" - advanced: - # Use these settings when scraped with module=advanced. - user: "some_user" - pass: "secret_pw" - privilege: "admin" - driver: "LAN" - collectors: - - ipmi - - sel - # USING ANY OF THE BELOW VOIDS YOUR WARRANTY! YOU MAY GET BITTEN BY SHARKS! - # You can override the command to be executed for a collector. Paired with - # custom_args, this can be used to e.g. execute the IPMI tools with sudo: - collector_cmd: - ipmi: sudo - sel: sudo - custom_args: - ipmi: - - "ipmimonitoring" - sel: - - "ipmi-sel" diff --git a/main.go b/main.go index 59d761a..7d0cbe5 100644 --- a/main.go +++ b/main.go @@ -15,18 +15,17 @@ package main import ( "fmt" + "log/slog" "net/http" "os" "os/signal" "syscall" kingpin "github.com/alecthomas/kingpin/v2" - "github.com/go-kit/log" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/prometheus/common/promlog" - "github.com/prometheus/common/promlog/flag" + "github.com/prometheus/common/promslog" + "github.com/prometheus/common/promslog/flag" "github.com/prometheus/common/version" "github.com/prometheus/exporter-toolkit/web" webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag" @@ -48,7 +47,7 @@ var ( } reloadCh chan chan error - logger log.Logger + logger *slog.Logger ) func remoteIPMIHandler(w http.ResponseWriter, r *http.Request) { @@ -68,7 +67,7 @@ func remoteIPMIHandler(w http.ResponseWriter, r *http.Request) { return } - level.Debug(logger).Log("msg", "Scraping target", "target", target, "module", module) + logger.Debug("Scraping target", "target", target, "module", module) registry := prometheus.NewRegistry() remoteCollector := metaCollector{target: target, module: module, config: sc} @@ -86,25 +85,25 @@ func updateConfiguration(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError) } default: - level.Error(logger).Log("msg", "Only POST requests allowed", "url", r.URL) + logger.Error("Only POST requests allowed", "url", r.URL) w.Header().Set("Allow", "POST") http.Error(w, "Only POST requests allowed", http.StatusMethodNotAllowed) } } func main() { - promlogConfig := &promlog.Config{} - flag.AddFlags(kingpin.CommandLine, promlogConfig) + promslogConfig := &promslog.Config{} + flag.AddFlags(kingpin.CommandLine, promslogConfig) kingpin.CommandLine.UsageWriter(os.Stdout) kingpin.HelpFlag.Short('h') kingpin.Version(version.Print("ipmi_exporter")) kingpin.Parse() - logger = promlog.New(promlogConfig) - level.Info(logger).Log("msg", "Starting ipmi_exporter", "version", version.Info()) + logger = promslog.New(promslogConfig) + logger.Info("Starting ipmi_exporter", "version", version.Info()) // Bail early if the config is bad. if err := sc.ReloadConfig(*configFile); err != nil { - level.Error(logger).Log("msg", "Error parsing config file", "error", err) + logger.Error("Error parsing config file", "error", err) os.Exit(1) } @@ -116,11 +115,11 @@ func main() { select { case <-hup: if err := sc.ReloadConfig(*configFile); err != nil { - level.Error(logger).Log("msg", "Error reloading config", "error", err) + logger.Error("Error reloading config", "error", err) } case rc := <-reloadCh: if err := sc.ReloadConfig(*configFile); err != nil { - level.Error(logger).Log("msg", "Error reloading config", "error", err) + logger.Error("Error reloading config", "error", err) rc <- err } else { rc <- nil @@ -167,7 +166,7 @@ func main() { srv := &http.Server{} if err := web.ListenAndServe(srv, webConfig, logger); err != nil { - level.Error(logger).Log("msg", "HTTP listener stopped", "error", err) + logger.Error("HTTP listener stopped", "error", err) os.Exit(1) } }