From 2b774c5ad1a4ada92c80aaa496b776433e58dd87 Mon Sep 17 00:00:00 2001 From: Martin Chodur <m.chodur@seznam.cz> Date: Tue, 18 Jan 2022 16:12:33 +0100 Subject: [PATCH] feat: version 1.0.0 Signed-off-by: Martin Chodur <m.chodur@seznam.cz> --- .circleci/config.yml | 87 ------ .github/workflows/go.yml | 25 ++ .github/workflows/release.yaml | 40 +++ .gitignore | 3 +- .golangci.yml | 6 - .goreleaser.yaml | 39 +++ .promu.yml | 25 -- CHANGELOG.md | 15 + Dockerfile | 12 +- Makefile | 23 -- Makefile.common | 277 ------------------ README.md | 12 +- VERSION | 1 - .../default_issue.tmpl | 31 ++ .../prometheus-gitlab-notifier.go | 77 +++-- conf/default_issue.tmpl | 32 +- errcheck_excludes.txt | 1 - go.mod | 56 +++- go.sum | 20 +- ...prometheus-gitlab-notifier-deployment.yaml | 2 +- pkg/api/api.go | 9 +- pkg/gitlab/gitlab.go | 59 ++-- pkg/handler/handler.go | 7 +- pkg/prober/prober.go | 13 +- pkg/processor/processor.go | 13 +- 25 files changed, 344 insertions(+), 541 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/go.yml create mode 100644 .github/workflows/release.yaml delete mode 100644 .golangci.yml create mode 100644 .goreleaser.yaml delete mode 100644 .promu.yml delete mode 100644 Makefile delete mode 100644 Makefile.common delete mode 100644 VERSION create mode 100644 cmd/prometheus-gitlab-notifier/default_issue.tmpl mode change 100644 => 120000 conf/default_issue.tmpl delete mode 100644 errcheck_excludes.txt diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 60b5fd4..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,87 +0,0 @@ ---- -version: 2.1 - - -orbs: - prometheus: prometheus/prometheus@0.1.0 - -executors: - # Whenever the Go version is updated here, .travis.yml and .promu.yml - # should also be updated. - golang: - docker: - - image: circleci/golang:1.12 - -jobs: - test: - executor: golang - steps: - - prometheus/setup_environment - - run: make - - prometheus/store_artifact: - file: prometheus-gitlab-notifier - - publish_master: - description: | - Build and publish container images from the master branch. - docker: - - image: circleci/golang - steps: - - prometheus/setup_build_environment - - prometheus/publish_images: - login_variable: DOCKER_LOGIN - organization: fusakla - password_variable: DOCKER_PASSWORD - registry: docker.io - - publish_release: - description: Build and publish binaries and container images for a given release tag. - docker: - - image: circleci/golang - steps: - - prometheus/setup_build_environment - - run: promu crossbuild tarballs - - run: promu checksum .tarballs - - run: promu release .tarballs - - store_artifacts: - destination: releases - path: .tarballs - - prometheus/publish_release_images: - login_variable: DOCKER_LOGIN - organization: fusakla - password_variable: DOCKER_PASSWORD - registry: docker.io - - - -workflows: - version: 2 - prometheus-gitlab-notifier: - jobs: - - test: - filters: - tags: - only: /.*/ - - prometheus/build: - name: build - filters: - branches: - only: master - tags: - only: /.*/ - - publish_master: - requires: - - test - - build - filters: - branches: - only: master - - publish_release: - requires: - - test - - build - filters: - tags: - only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ - branches: - ignore: /.*/ diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..0746c5e --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,25 @@ +name: Go + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..fdedd05 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,40 @@ + +name: goreleaser + +on: + push: + tags: + - '*' + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index fc2e3ad..1df889f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,10 +13,11 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out .build +dist/ # Dependency directories (remove the comment below to include it) # vendor/ /conf/*.token -/prometheus-gitlab-notifier +/prometheus-gitlab-notifier/cmd/prometheus-gitlab-notifier diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index f9abe8d..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,6 +0,0 @@ -service: - golangci-lint-version: 1.12.4 - -linters-settings: - errcheck: - exclude: errcheck_excludes.txt diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..be68c66 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,39 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com +project_name: prometheus-gitlab-notifier +before: + hooks: + - go mod tidy + +builds: + - main: "./cmd/{{.ProjectName}}" + env: + - CGO_ENABLED=0 + goos: + - linux + +dockers: + - image_templates: + - "fusakla/{{.ProjectName}}:latest" + - "fusakla/{{.ProjectName}}:{{ .Tag }}" + - "fusakla/{{.ProjectName}}:v{{ .Major }}" + - "fusakla/{{.ProjectName}}:v{{ .Major }}.{{ .Minor }}" + +archives: + - replacements: + linux: Linux + amd64: x86_64 + +checksum: + name_template: 'checksums.txt' + +snapshot: + name_template: "{{ incpatch .Version }}-next" + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + - '^chore:' diff --git a/.promu.yml b/.promu.yml deleted file mode 100644 index 73df6c3..0000000 --- a/.promu.yml +++ /dev/null @@ -1,25 +0,0 @@ -go: - # Whenever the Go version is updated here, .travis.yml and - # .circle/config.yml should also be updated. - version: 1.12 -repository: - path: github.com/fusakla/prometheus-gitlab-notifier -build: - binaries: - - name: prometheus-gitlab-notifier - path: ./cmd/prometheus-gitlab-notifier - flags: -a -tags netgo - ldflags: | - -X github.com/fusakla/prometheus-gitlab-notifier/metrics.appVersion={{.Version}} - -X github.com/fusakla/prometheus-gitlab-notifier/metrics.gitRevision={{.Revision}} - -X github.com/fusakla/prometheus-gitlab-notifier/metrics.gitBranch={{.Branch}} - -X github.com/fusakla/prometheus-gitlab-notifier/metrics.gitTag={{.Version}} -tarball: - files: - - LICENSE - - NOTICE -crossbuild: - platforms: - - linux/amd64 - - darwin/amd64 - - windows/amd64 diff --git a/CHANGELOG.md b/CHANGELOG.md index cfda750..85cbc54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,22 @@ ## Unreleased + +## 1.0.0 / 2022-01-18 + +### Fixed +- Fixed alert template loading + +### Added +- support for templating functions from [the sprig library](https://github.com/Masterminds/sprig) +- default value for the gitlab.url flag pointing to `https://gtilab.com` +- new flag `--log.json` to enable JSON logging + ### Changed - prometheus and alertmanager dependency versions +- Upgraded to Go 1.17 +- Migrated to goreleaser +- Switched to logrus library, default log format has changed +- Default issue template is now embedded in the binary ## 0.7.0 / 2019-08-13 diff --git a/Dockerfile b/Dockerfile index 63389a7..6379c09 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,11 @@ -FROM quay.io/prometheus/busybox:latest +FROM alpine:3.15.0 LABEL maintainer="FUSAKLA Martin Chodúr <m.chodur@seznam.cz>" -ARG ARCH="amd64" -ARG OS="linux" -COPY --chown=nobody:nogroup .build/${OS}-${ARCH}/prometheus-gitlab-notifier /bin/prometheus-gitlab-notifier -COPY --chown=nobody:nogroup conf/default_issue.tmpl /prometheus-gitlab-notifier/conf/ +COPY --chown=nobody:nogroup prometheus-gitlab-notifier /usr/bin/ COPY --chown=nobody:nogroup Dockerfile / EXPOSE 9629 -RUN mkdir -p /prometheus-gitlab-notifier && chown nobody:nogroup /prometheus-gitlab-notifier -WORKDIR /prometheus-gitlab-notifier - USER 65534 -ENTRYPOINT ["/bin/prometheus-gitlab-notifier"] +ENTRYPOINT ["/usr/bin/prometheus-gitlab-notifier"] diff --git a/Makefile b/Makefile deleted file mode 100644 index e9a6c89..0000000 --- a/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2016 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. - -# Needs to be defined before including Makefile.common to auto-generate targets -DOCKER_ARCHS ?= amd64 - -include Makefile.common - -DOCKER_IMAGE_NAME ?= prometheus-gitlab-notifier - -assets: - @echo ">> writing assets" - @cd $(PREFIX)/asset && GO111MODULE=$(GO111MODULE) $(GO) generate && $(GOFMT) -w assets_vfsdata.go diff --git a/Makefile.common b/Makefile.common deleted file mode 100644 index db98993..0000000 --- a/Makefile.common +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright 2018 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. - - -# A common Makefile that includes rules to be reused in different prometheus projects. -# !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! - -# Example usage : -# Create the main Makefile in the root project directory. -# include Makefile.common -# customTarget: -# @echo ">> Running customTarget" -# - -# Ensure GOBIN is not set during build so that promu is installed to the correct path -unexport GOBIN - -GO ?= go -GOFMT ?= $(GO)fmt -FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) -GOOPTS ?= -GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) -GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) - -GO_VERSION ?= $(shell $(GO) version) -GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) -PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') - -GOVENDOR := -GO111MODULE := -ifeq (, $(PRE_GO_111)) - ifneq (,$(wildcard go.mod)) - # Enforce Go modules support just in case the directory is inside GOPATH (and for Travis CI). - GO111MODULE := on - - ifneq (,$(wildcard vendor)) - # Always use the local vendor/ directory to satisfy the dependencies. - GOOPTS := $(GOOPTS) -mod=vendor - endif - endif -else - ifneq (,$(wildcard go.mod)) - ifneq (,$(wildcard vendor)) -$(warning This repository requires Go >= 1.11 because of Go modules) -$(warning Some recipes may not work as expected as the current Go runtime is '$(GO_VERSION_NUMBER)') - endif - else - # This repository isn't using Go modules (yet). - GOVENDOR := $(FIRST_GOPATH)/bin/govendor - endif -endif -PROMU := $(FIRST_GOPATH)/bin/promu -pkgs = ./... - -ifeq (arm, $(GOHOSTARCH)) - GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) - GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) -else - GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) -endif - -PROMU_VERSION ?= 0.5.0 -PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz - -GOLANGCI_LINT := -GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v1.17.1 -# golangci-lint only supports linux, darwin and windows platforms on i386/amd64. -# windows isn't included here because of the path separator being different. -ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) - ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) - GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint - endif -endif - -PREFIX ?= $(shell pwd) -BIN_DIR ?= $(shell pwd) -DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) -DOCKERFILE_PATH ?= ./Dockerfile -DOCKERBUILD_CONTEXT ?= ./ -DOCKER_REPO ?= prom - -DOCKER_ARCHS ?= amd64 - -BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) -PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) -TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) - -ifeq ($(GOHOSTARCH),amd64) - ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) - # Only supported on amd64 - test-flags := -race - endif -endif - -# This rule is used to forward a target like "build" to "common-build". This -# allows a new "build" target to be defined in a Makefile which includes this -# one and override "common-build" without override warnings. -%: common-% ; - -.PHONY: common-all -common-all: precheck style check_license lint unused build test - -.PHONY: common-style -common-style: - @echo ">> checking code style" - @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ - if [ -n "$${fmtRes}" ]; then \ - echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ - echo "Please ensure you are using $$($(GO) version) for formatting code."; \ - exit 1; \ - fi - -.PHONY: common-check_license -common-check_license: - @echo ">> checking license header" - @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ - awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ - done); \ - if [ -n "$${licRes}" ]; then \ - echo "license header checking failed:"; echo "$${licRes}"; \ - exit 1; \ - fi - -.PHONY: common-deps -common-deps: - @echo ">> getting dependencies" -ifdef GO111MODULE - GO111MODULE=$(GO111MODULE) $(GO) mod download -else - $(GO) get $(GOOPTS) -t ./... -endif - -.PHONY: common-test-short -common-test-short: - @echo ">> running short tests" - GO111MODULE=$(GO111MODULE) $(GO) test -short $(GOOPTS) $(pkgs) - -.PHONY: common-test -common-test: - @echo ">> running all tests" - GO111MODULE=$(GO111MODULE) $(GO) test $(test-flags) $(GOOPTS) $(pkgs) - -.PHONY: common-format -common-format: - @echo ">> formatting code" - GO111MODULE=$(GO111MODULE) $(GO) fmt $(pkgs) - -.PHONY: common-vet -common-vet: - @echo ">> vetting code" - GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs) - -.PHONY: common-lint -common-lint: $(GOLANGCI_LINT) -ifdef GOLANGCI_LINT - @echo ">> running golangci-lint" -ifdef GO111MODULE -# 'go list' needs to be executed before staticcheck to prepopulate the modules cache. -# Otherwise staticcheck might fail randomly for some reason not yet explained. - GO111MODULE=$(GO111MODULE) $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null - GO111MODULE=$(GO111MODULE) $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) -else - $(GOLANGCI_LINT) run $(pkgs) -endif -endif - -# For backward-compatibility. -.PHONY: common-staticcheck -common-staticcheck: lint - -.PHONY: common-unused -common-unused: $(GOVENDOR) -ifdef GOVENDOR - @echo ">> running check for unused packages" - @$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages' -else -ifdef GO111MODULE - @echo ">> running check for unused/missing packages in go.mod" - GO111MODULE=$(GO111MODULE) $(GO) mod tidy -ifeq (,$(wildcard vendor)) - @git diff --exit-code -- go.sum go.mod -else - @echo ">> running check for unused packages in vendor/" - GO111MODULE=$(GO111MODULE) $(GO) mod vendor - @git diff --exit-code -- go.sum go.mod vendor/ -endif -endif -endif - -.PHONY: common-build -common-build: promu - @echo ">> building binaries" - GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) - -.PHONY: common-tarball -common-tarball: promu - @echo ">> building release tarball" - $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) - -.PHONY: common-docker $(BUILD_DOCKER_ARCHS) -common-docker: $(BUILD_DOCKER_ARCHS) -$(BUILD_DOCKER_ARCHS): common-docker-%: - docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \ - -f $(DOCKERFILE_PATH) \ - --build-arg ARCH="$*" \ - --build-arg OS="linux" \ - $(DOCKERBUILD_CONTEXT) - -.PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) -common-docker-publish: $(PUBLISH_DOCKER_ARCHS) -$(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: - docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" - -.PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) -common-docker-tag-latest: $(TAG_DOCKER_ARCHS) -$(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: - docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" - -.PHONY: common-docker-manifest -common-docker-manifest: - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG)) - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" - -.PHONY: promu -promu: $(PROMU) - -$(PROMU): - $(eval PROMU_TMP := $(shell mktemp -d)) - curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) - mkdir -p $(FIRST_GOPATH)/bin - cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu - rm -r $(PROMU_TMP) - -.PHONY: proto -proto: - @echo ">> generating code from proto files" - @./scripts/genproto.sh - -ifdef GOLANGCI_LINT -$(GOLANGCI_LINT): - mkdir -p $(FIRST_GOPATH)/bin - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ - | sed -e '/install -d/d' \ - | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) -endif - -ifdef GOVENDOR -.PHONY: $(GOVENDOR) -$(GOVENDOR): - GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor -endif - -.PHONY: precheck -precheck:: - -define PRECHECK_COMMAND_template = -precheck:: $(1)_precheck - -PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) -.PHONY: $(1)_precheck -$(1)_precheck: - @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ - echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ - exit 1; \ - fi -endef diff --git a/README.md b/README.md index 216ddf4..405446f 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,12 @@ Flags: To test it is running check logs or http://0.0.0.0:9629/readiness +### Try it out +You can send a test alert to it using the prepared alert JSON by running thin in root of this repo +```bash +curl -X POST -H "Content-Type: application/json" -d @./conf/alert.json http://localhost:9629/api/alertmanager +``` + ### Issue template Look of the resulting issue in Gitlab can be customized using [Go template](https://golang.org/pkg/text/template/). Default template can be found in [conf/default_issue.tmpl](conf/default_issue.tmpl). @@ -55,7 +61,7 @@ fails in the runtime, raw JSON of the alert will be pasted to the text of the is Example of the default template: - + ### Configure Alertmanager You just need to add the [`<webhook_config>`](https://prometheus.io/docs/alerting/configuration/#webhook_config) @@ -70,14 +76,14 @@ The Gitlab notifier allows to label the resulting issue based on the alert label It uses mostly Gitlab scoped labels in format `label::value`. The grouping labels of the alert are added to the issue automatically to allow identifying same alerts (more on that in [Grouping](#Grouping) section). -Additionally you can specify names of labels to be also added to the issue using flag `--dynamic.issue.label.name`. +Additionally, you can specify names of labels to be also added to the issue using flag `--dynamic.issue.label.name`. Last thing you can add are static labels which will be added to every issue using flag `--issue.label`, ### Grouping To avoid flooding gitlab with identical alerts if they happen to fire and resolve again and again, Gitlab notifier checks for issues witch the same grouping labels as the new incoming alert. -If if finds any still open issue younger than `1h` by default (can be controlled by flag `--group.interval`), +If if it finds any still open issue younger than `1h` by default (can be controlled by flag `--group.interval`), it only appends the rendered template to the end of the issue description and adds to the issue label `appended-alerts::<number>` witch count of how many times it was updated. diff --git a/VERSION b/VERSION deleted file mode 100644 index a918a2a..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.6.0 diff --git a/cmd/prometheus-gitlab-notifier/default_issue.tmpl b/cmd/prometheus-gitlab-notifier/default_issue.tmpl new file mode 100644 index 0000000..d05b890 --- /dev/null +++ b/cmd/prometheus-gitlab-notifier/default_issue.tmpl @@ -0,0 +1,31 @@ +{{define "alert"}} + - **`{{ index .Annotations "description" }}`** + - **Starts at**: {{ .StartsAt }} + - **Ends at**: {{ .EndsAt }} + - **Generator URL**: [{{ .GeneratorURL }}]({{ .GeneratorURL }}) + - **Labels**: `{{`{`}}{{ range $k,$v := .Labels }}{{$k}}="{{$v}}", {{end}}{{`}`}}` +{{end}} + + +# `{{ index .CommonLabels "severity" }}` alert `{{ index .CommonLabels "alertname" }}` occurred +**Title:** {{ index .CommonAnnotations "title" }} +**Alertmanager link:** [{{ .ExternalURL }}]({{ .ExternalURL }}) + +### Common labels: +{{- range $k,$v := .CommonLabels }} + - **`{{ $k }}`**: `{{ $v }}` +{{- end }} + +### Common annotations: +{{- range $k,$v := .CommonAnnotations }} + {{- if and (not (eq $k "title")) (not (eq $k "description")) }} + - **`{{ $k }}`**: `{{ $v }}` + {{- end }} +{{- end }} + +--- + +## Alerts +{{- range .Alerts }} + {{ template "alert" . }} +{{- end }} diff --git a/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go b/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go index 2b9fb0f..7660397 100644 --- a/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go +++ b/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go @@ -15,6 +15,7 @@ package main import ( "context" + _ "embed" "io/ioutil" "net/http" "os" @@ -24,6 +25,7 @@ import ( "text/template" "time" + "github.com/Masterminds/sprig" "github.com/alecthomas/kingpin" "github.com/fusakla/prometheus-gitlab-notifier/pkg/alertmanager" "github.com/fusakla/prometheus-gitlab-notifier/pkg/api" @@ -32,37 +34,37 @@ import ( "github.com/fusakla/prometheus-gitlab-notifier/pkg/metrics" "github.com/fusakla/prometheus-gitlab-notifier/pkg/prober" "github.com/fusakla/prometheus-gitlab-notifier/pkg/processor" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "github.com/gorilla/mux" "github.com/pkg/errors" + log "github.com/sirupsen/logrus" ) -func setupLogger(debug bool) log.Logger { - l := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) - l = log.With(l, "ts", log.DefaultTimestamp, "caller", log.DefaultCaller) +func setupLogger(debug bool, logJson bool) log.FieldLogger { + l := log.New() + l.SetOutput(os.Stdout) + if logJson { + l.SetFormatter(&log.JSONFormatter{}) + } if debug { - l = level.NewFilter(l, level.AllowDebug()) - } else { - l = level.NewFilter(l, level.AllowInfo()) + l.SetLevel(log.DebugLevel) } return l } -func waitForEmptyChannel(logger log.Logger, ch <-chan *alertmanager.Webhook) { - level.Info(logger).Log("msg", "waiting for all the alerts to be processed") +func waitForEmptyChannel(logger log.FieldLogger, ch <-chan *alertmanager.Webhook) { + logger.Info("waiting for all the alerts to be processed") for { if len(ch) > 0 { - level.Info(logger).Log("msg", "there are still alerts in the queue, waiting forthem to be processed", "queue_size", len(ch)) + logger.WithField("queue_size", len(ch)).Info("there are still alerts in the queue, waiting for them to be processed") time.Sleep(10 * time.Millisecond) continue } break } - level.Info(logger).Log("msg", "processing of the rest of alerts is done") + logger.Info("processing of the rest of alerts is done") } -func startServer(logger log.Logger, r http.Handler) (*http.Server, <-chan error) { +func startServer(logger log.FieldLogger, r http.Handler) (*http.Server, <-chan error) { errCh := make(chan error, 1) srv := &http.Server{ Handler: handler.Instrumented(logger, r), @@ -72,10 +74,10 @@ func startServer(logger log.Logger, r http.Handler) (*http.Server, <-chan error) } go func() { defer close(errCh) - level.Info(logger).Log("msg", "Starting prometheus-gitlab-notifier", "addr", "0.0.0.0:9629") + logger.WithField("addr", "0.0.0.0:9629").Info("Starting prometheus-gitlab-notifier") if err := srv.ListenAndServe(); err != nil { if err != http.ErrServerClosed { - level.Error(logger).Log("msg", "server failed", "error", err) + logger.WithField("err", err).Error("server failed") errCh <- err } } @@ -86,40 +88,55 @@ func startServer(logger log.Logger, r http.Handler) (*http.Server, <-chan error) var ( app = kingpin.New("prometheus-gitlab-notifier", "Web server listening for webhooks of alertmanager and creating an issue in Gitlab based on it.") debug = app.Flag("debug", "Enables debug logging.").Bool() + logJson = app.Flag("log.json", "Log in JSON format").Bool() serverAddr = app.Flag("server.addr", "Allows to change the address and port at which the server will listen for incoming connections.").Default("0.0.0.0:9629").String() - gitlabURL = app.Flag("gitlab.url", "URL of the Gitlab API.").Required().String() + gitlabURL = app.Flag("gitlab.url", "URL of the Gitlab API.").Default("https://gitlab.com").String() gitlabTokenFile = app.Flag("gitlab.token.file", "Path to file containing gitlab token.").Required().ExistingFile() projectId = app.Flag("project.id", "Id of project where to create the issues.").Required().Int() groupInterval = app.Flag("group.interval", "Duration how long back to check for opened issues with the same group labels to append the new alerts to (go duration syntax allowing 'ns', 'us' , 'ms', 's', 'm', 'h').").Default("1h").Duration() issueLabels = app.Flag("issue.label", "Labels to add to the created issue. (Can be passed multiple times)").Strings() dynamicIssueLabels = app.Flag("dynamic.issue.label.name", "Alert label, which is to be propagated to the resulting Gitlab issue as scoped label if present in the received alert. (Can be passed multiple times)").Strings() - issueTemplatePath = app.Flag("issue.template", "Path to the issue golang template file.").Default("conf/default_issue.tmpl").ExistingFile() + issueTemplatePath = app.Flag("issue.template", "Path to the issue golang template file.").ExistingFile() queueSizeLimit = app.Flag("queue.size.limit", "Limit of the alert queue size.").Default("100").Int() retryBackoff = app.Flag("retry.backoff", "Duration how long to wait till next retry (go duration syntax allowing 'ns', 'us' , 'ms', 's', 'm', 'h').").Default("5m").Duration() retryLimit = app.Flag("retry.limit", "Maximum number of retries for single alert. If exceeded it's thrown away.").Default("5").Int() gracefulShutdownWait = app.Flag("graceful.shutdown.wait.duration", "Duration how long to wait on graceful shutdown marked as not ready (go duration syntax allowing 'ns', 'us' , 'ms', 's', 'm', 'h').").Default("30s").Duration() ) -func main() { +//go:embed default_issue.tmpl +var defaultIssueTemplate []byte +func main() { + var err error kingpin.MustParse(app.Parse(os.Args[1:])) // Initiate logging. - logger := setupLogger(*debug) + logger := setupLogger(*debug, *logJson) + + tpl := template.New("base").Funcs(template.FuncMap(sprig.FuncMap())) + + templateContents := defaultIssueTemplate + if *issueTemplatePath != "" { + templateContents, err = os.ReadFile(*issueTemplatePath) + if err != nil { + logger.WithFields(log.Fields{"err": err, "file": issueTemplatePath}).Error("failed to read template file") + os.Exit(1) + } + } // Initiate Gitlab client. - gitlabIssueTextTemplate, err := template.ParseFiles(*issueTemplatePath) + gitlabIssueTextTemplate, err := tpl.Parse(string(templateContents)) if err != nil { - level.Error(logger).Log("msg", "invalid gitlab issue template", "file", *issueTemplatePath, "err", err) + logger.WithFields(log.Fields{"err": err, "file": issueTemplatePath}).Error("invalid gitlab issue template") os.Exit(1) } token, err := ioutil.ReadFile(*gitlabTokenFile) if err != nil { - level.Error(logger).Log("msg", "failed to read token file", "file", gitlabTokenFile, "err", err) + logger.WithFields(log.Fields{"err": err, "file": gitlabTokenFile}).Error("failed to read token file") os.Exit(1) } g, err := gitlab.New( - log.With(logger, "component", "gitlab"), + logger.WithField("component", "gitlab"), *gitlabURL, strings.TrimSpace(string(token)), *projectId, @@ -129,13 +146,13 @@ func main() { groupInterval, ) if err != nil { - level.Error(logger).Log("msg", "invalid gitlab configuration") + logger.WithField("err", err).Error("invalid gitlab configuration") os.Exit(1) } // Start processing all incoming alerts. alertChan := make(chan *alertmanager.Webhook, *queueSizeLimit) - proc := processor.New(log.With(logger, "component", "processor")) + proc := processor.New(logger.WithField("component", "processor")) processCtx, processCancelFunc := context.WithCancel(context.Background()) defer processCancelFunc() proc.Process(processCtx, g, alertChan, *retryLimit, *retryBackoff) @@ -144,20 +161,20 @@ func main() { r := mux.NewRouter() // Initialize the main API. webhookApi := api.NewInRouter( - log.With(logger, "component", "api"), + logger.WithField("component", "api"), r.PathPrefix("/api").Subrouter(), alertChan, ) // Initialize prober providing readiness and liveness checks. readinessProber := prober.NewInRouter( - log.With(logger, "component", "prober"), + logger.WithField("component", "prober"), r.PathPrefix("/").Subrouter(), ) // Initialize metrics handler to serve Prometheus metrics. metrics.HandleInRouter(r) // Start HTTP server - _, serverErrorChan := startServer(log.With(logger, "component", "server"), r) + _, serverErrorChan := startServer(logger.WithField("component", "server"), r) // Subscribe to system signals so we can react on them with graceful termination. gracefulStop := make(chan os.Signal, 2) @@ -172,11 +189,11 @@ func main() { waitForEmptyChannel(logger, alertChan) os.Exit(1) case sig := <-gracefulStop: - level.Info(logger).Log("msg", "received system signal for graceful shutdown", "signal", sig) + logger.WithField("signal", sig).Info("received system signal for graceful shutdown") // Mark server as not ready so no new connections will come. readinessProber.SetServerNotReady(errors.New("server is shutting down")) // Wait for specified time after marking server not ready so the environment can react on it. - level.Info(logger).Log("msg", "waiting for graceful shutdown", "duration", gracefulShutdownWait) + logger.WithField("duration", gracefulShutdownWait).Info("waiting for graceful shutdown") time.Sleep(*gracefulShutdownWait) // Stop receiving new alerts. webhookApi.Close() diff --git a/conf/default_issue.tmpl b/conf/default_issue.tmpl deleted file mode 100644 index d05b890..0000000 --- a/conf/default_issue.tmpl +++ /dev/null @@ -1,31 +0,0 @@ -{{define "alert"}} - - **`{{ index .Annotations "description" }}`** - - **Starts at**: {{ .StartsAt }} - - **Ends at**: {{ .EndsAt }} - - **Generator URL**: [{{ .GeneratorURL }}]({{ .GeneratorURL }}) - - **Labels**: `{{`{`}}{{ range $k,$v := .Labels }}{{$k}}="{{$v}}", {{end}}{{`}`}}` -{{end}} - - -# `{{ index .CommonLabels "severity" }}` alert `{{ index .CommonLabels "alertname" }}` occurred -**Title:** {{ index .CommonAnnotations "title" }} -**Alertmanager link:** [{{ .ExternalURL }}]({{ .ExternalURL }}) - -### Common labels: -{{- range $k,$v := .CommonLabels }} - - **`{{ $k }}`**: `{{ $v }}` -{{- end }} - -### Common annotations: -{{- range $k,$v := .CommonAnnotations }} - {{- if and (not (eq $k "title")) (not (eq $k "description")) }} - - **`{{ $k }}`**: `{{ $v }}` - {{- end }} -{{- end }} - ---- - -## Alerts -{{- range .Alerts }} - {{ template "alert" . }} -{{- end }} diff --git a/conf/default_issue.tmpl b/conf/default_issue.tmpl new file mode 120000 index 0000000..37e48a7 --- /dev/null +++ b/conf/default_issue.tmpl @@ -0,0 +1 @@ +../cmd/prometheus-gitlab-notifier/default_issue.tmpl \ No newline at end of file diff --git a/errcheck_excludes.txt b/errcheck_excludes.txt deleted file mode 100644 index 97b9771..0000000 --- a/errcheck_excludes.txt +++ /dev/null @@ -1 +0,0 @@ -(github.com/go-kit/kit/log.Logger).Log diff --git a/go.mod b/go.mod index 3bcc339..c3862d4 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,65 @@ module github.com/fusakla/prometheus-gitlab-notifier -go 1.12 +go 1.17 require ( + github.com/Masterminds/sprig v2.22.0+incompatible github.com/alecthomas/kingpin v2.2.6+incompatible - github.com/go-kit/kit v0.9.0 github.com/gorilla/mux v1.7.2 github.com/pkg/errors v0.8.1 github.com/prometheus/alertmanager v0.20.0 github.com/prometheus/client_golang v1.6.0 + github.com/sirupsen/logrus v1.4.2 github.com/xanzy/go-gitlab v0.18.0 ) + +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect + github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/go-kit/kit v0.9.0 // indirect + github.com/go-logfmt/logfmt v0.4.0 // indirect + github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48 // indirect + github.com/golang/protobuf v1.4.0 // indirect + github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect + github.com/google/go-querystring v1.0.0 // indirect + github.com/google/uuid v1.1.1 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-immutable-radix v1.0.0 // indirect + github.com/hashicorp/go-msgpack v0.5.3 // indirect + github.com/hashicorp/go-multierror v1.0.0 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/golang-lru v0.5.0 // indirect + github.com/hashicorp/memberlist v0.1.4 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect + github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/miekg/dns v1.0.14 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.9.1 // indirect + github.com/prometheus/procfs v0.0.11 // indirect + github.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect + github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect + github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect + golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56 // indirect + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect + golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 // indirect + golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect + google.golang.org/appengine v1.3.0 // indirect + google.golang.org/protobuf v1.21.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect +) diff --git a/go.sum b/go.sum index 5ec0f33..b843396 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -99,6 +105,7 @@ github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASu github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -119,6 +126,10 @@ github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCO github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs= github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -126,6 +137,7 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -146,8 +158,12 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -199,6 +215,7 @@ github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJ github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -280,5 +297,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/kubernetes/prometheus-gitlab-notifier-deployment.yaml b/kubernetes/prometheus-gitlab-notifier-deployment.yaml index 1bc58d4..27154f8 100644 --- a/kubernetes/prometheus-gitlab-notifier-deployment.yaml +++ b/kubernetes/prometheus-gitlab-notifier-deployment.yaml @@ -14,7 +14,7 @@ spec: spec: containers: - name: prometheus-gitlab-notifier - image: fusakla/prometheus-gitlab-notifier:0.6.0 + image: fusakla/prometheus-gitlab-notifier:latest args: - "--gitlab.url=https://gitlab.com/api/v4" - "--project.id=13766104" diff --git a/pkg/api/api.go b/pkg/api/api.go index 5acd4b8..3e4d53c 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -22,14 +22,13 @@ import ( "github.com/fusakla/prometheus-gitlab-notifier/pkg/alertmanager" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "github.com/gorilla/mux" "github.com/prometheus/alertmanager/notify/webhook" + log "github.com/sirupsen/logrus" ) // NewInRouter creates new Api instance which will register it's handlers in the given router. -func NewInRouter(logger log.Logger, r *mux.Router, ch chan<- *alertmanager.Webhook) *Api { +func NewInRouter(logger log.FieldLogger, r *mux.Router, ch chan<- *alertmanager.Webhook) *Api { api := &Api{ logger: logger, alertChan: ch, @@ -41,7 +40,7 @@ func NewInRouter(logger log.Logger, r *mux.Router, ch chan<- *alertmanager.Webho // Api defines handler functions for receiving Alertmanager endpoints. type Api struct { - logger log.Logger + logger log.FieldLogger alertChan chan<- *alertmanager.Webhook receiveAlerts bool receiveAlertsMtx sync.RWMutex @@ -65,7 +64,7 @@ func (a *Api) webhookHandler(w http.ResponseWriter, r *http.Request) { // Push the message to channel a.alertChan <- alertmanager.NewWebhookFromAlertmanagerMessage(message) - level.Debug(a.logger).Log("msg", "enqueued alert for processing", "group_key", message.GroupKey) + a.logger.WithField("group_key", message.GroupKey).Debug("enqueued alert for processing") w.WriteHeader(http.StatusOK) _, _ = io.WriteString(w, `Ok, Alert enqueued.`) diff --git a/pkg/gitlab/gitlab.go b/pkg/gitlab/gitlab.go index 69942c7..b97104c 100644 --- a/pkg/gitlab/gitlab.go +++ b/pkg/gitlab/gitlab.go @@ -26,16 +26,15 @@ import ( "github.com/fusakla/prometheus-gitlab-notifier/pkg/alertmanager" "github.com/fusakla/prometheus-gitlab-notifier/pkg/metrics" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" + log "github.com/sirupsen/logrus" "github.com/xanzy/go-gitlab" ) // New creates new Gitlab instance configured to work with specified gitlab instance, project and with given authentication. -func New(logger log.Logger, url string, token string, projectId int, issueTemplate *template.Template, issueLabels *[]string, dynamicIssueLabels *[]string, groupInterval *time.Duration) (*Gitlab, error) { +func New(logger log.FieldLogger, url string, token string, projectId int, issueTemplate *template.Template, issueLabels *[]string, dynamicIssueLabels *[]string, groupInterval *time.Duration) (*Gitlab, error) { cli := gitlab.NewClient(nil, token) if err := cli.SetBaseURL(url); err != nil { - level.Error(logger).Log("msg", "invalid Gitlab URL", "url", url, "err", "err") + logger.WithFields(log.Fields{"url": url, "err": "err"}).Error("invalid Gitlab URL") return nil, err } g := &Gitlab{ @@ -48,7 +47,7 @@ func New(logger log.Logger, url string, token string, projectId int, issueTempla logger: logger, } if err := g.ping(); err != nil { - level.Error(logger).Log("msg", "cannot reach the Gitlab", "url", url, "err", "err") + logger.WithFields(log.Fields{"url": url, "err": err}).Error("msg", "cannot reach the Gitlab") return nil, err } return g, nil @@ -62,7 +61,7 @@ type Gitlab struct { issueLabels *[]string dynamicIssueLabels *[]string groupInterval *time.Duration - logger log.Logger + logger log.FieldLogger } func (g *Gitlab) formatGitlabScopedLabel(key string, value string) string { @@ -99,14 +98,34 @@ func (g *Gitlab) renderIssueTemplate(msg *alertmanager.Webhook) (*bytes.Buffer, var issueText bytes.Buffer // Try to template the issue text template with the alert data. if err := g.issueTemplate.Execute(&issueText, msg.Data); err != nil { - // As a fallback we try to add raw JSON of the alert to the issue text so we don't miss an alert just because of template error. + // As a fallback we try to add raw JSON of the alert to the issue text, so we don't miss an alert just because of template error. metrics.ReportError("IssueTemplateError", "") - level.Error(g.logger).Log("msg", "failed to template issue text, using pure JSON instead", "err", err) + g.logger.WithFields(log.Fields{"err": err}).Error("failed to template issue text, using pure JSON instead") w := bufio.NewWriter(&issueText) - if err := json.NewEncoder(w).Encode(msg); err != nil { + _, err := w.WriteString("\n```json\n") + if err != nil { + metrics.ReportError("JSONWriteError", "") + g.logger.WithFields(log.Fields{"err": err}).Error("failed to write the alert to JSON") + return nil, err + } + e := json.NewEncoder(w) + e.SetIndent("", " ") + if err := e.Encode(msg); err != nil { // If even JSON marshalling fails we return error metrics.ReportError("JSONMarshalError", "") - level.Error(g.logger).Log("msg", "failed to marshall alert to JSON", "err", err) + g.logger.WithFields(log.Fields{"err": err}).Error("failed to marshall alert to JSON") + return nil, err + } + _, err = w.WriteString("\n```\n") + if err != nil { + metrics.ReportError("JSONWriteError", "") + g.logger.WithFields(log.Fields{"err": err}).Error("failed to write the alert to JSON") + return nil, err + } + err = w.Flush() + if err != nil { + metrics.ReportError("JSONWriteError", "") + g.logger.WithFields(log.Fields{"err": err}).Error("failed to write the alert to JSON") return nil, err } } @@ -127,7 +146,7 @@ func (g *Gitlab) getOpenIssuesSince(groupingLabels []string, sinceTime time.Time issues, response, err := g.client.Issues.ListIssues(&listOpts) if err != nil { metrics.ReportError("ListGitlabIssuesError", "gitlab") - level.Error(g.logger).Log("msg", "failed to list gitlab issues with", "opts", listOpts, "response", response, "err", err) + g.logger.WithFields(log.Fields{"opts": listOpts, "response": response, "err": err}).Error("failed to list gitlab issues with") return []*gitlab.Issue{}, err } return issues, nil @@ -151,10 +170,10 @@ func (g *Gitlab) createGitlabIssue(msg *alertmanager.Webhook, groupingLabels []s createdIssue, response, err := g.client.Issues.CreateIssue(g.projectId, options) if err != nil { metrics.ReportError("FailedToCreateGitlabIssue", "gitlab") - level.Error(g.logger).Log("msg", "failed to create gitlab issue", "err", err, "response", response) + g.logger.WithFields(log.Fields{"err": err, "response": response}).Error("failed to create gitlab issue") return err } - level.Info(g.logger).Log("msg", "created issue in gitlab", "gitlab_issue_id", createdIssue.IID, "alert_grouping_key", msg.GroupKey) + g.logger.WithFields(log.Fields{"gitlab_issue_id": createdIssue.IID, "alert_grouping_key": msg.GroupKey}).Info("created issue in gitlab") return nil } @@ -171,7 +190,7 @@ func (g *Gitlab) increaseAppendLabel(labels []string) []string { // Convert it to number if possible otherwise leave the old one as is count, err := strconv.Atoi(matched[2]) if err != nil { - level.Error(g.logger).Log("msg", "failed to parse gitlab issue label `appended-alerts`, leaving it unmodified", "label_value", l, "err", err) + g.logger.WithFields(log.Fields{"err": err, "label_value": l}).Error("failed to parse gitlab issue label `appended-alerts`, leaving it unmodified") newLabels = append(newLabels, l) continue } @@ -197,10 +216,10 @@ func (g *Gitlab) updateGitlabIssue(issue *gitlab.Issue, issueText *bytes.Buffer) issue, response, err := g.client.Issues.UpdateIssue(g.projectId, issue.IID, options) if err != nil { metrics.ReportError("FailedToUpdateGitlabIssue", "gitlab") - level.Error(g.logger).Log("msg", "failed to update gitlab issue, will try to create new", "err", err, "response", response) + g.logger.WithFields(log.Fields{"err": err, "response": response}).Error("failed to update gitlab issue, will try to create new") return err } - level.Info(g.logger).Log("msg", "updated issue in gitlab", "gitlab_issue_id", issue.IID) + g.logger.WithFields(log.Fields{"gitlab_issue_id": issue.IID}).Info("updated issue in gitlab") return nil } @@ -212,7 +231,7 @@ func (g *Gitlab) CreateIssue(msg *alertmanager.Webhook) error { // Check for existing issues with same grouping labels matchingIssues, err := g.getOpenIssuesSince(groupingLabels, g.getTimeBefore(g.groupInterval)) if err != nil { - level.Warn(g.logger).Log("msg", "listing of open issues to check for duplicates failed , opening a new one even though possible duplicate") + g.logger.Warn("listing of open issues to check for duplicates failed , opening a new one even though possible duplicate") } // Try to render the issue text template @@ -225,7 +244,7 @@ func (g *Gitlab) CreateIssue(msg *alertmanager.Webhook) error { // Issues are ordered by created date, we update the first so the newest one. issueToUpdate := matchingIssues[0] if err := g.updateGitlabIssue(issueToUpdate, issueText); err != nil { - level.Warn(g.logger).Log("msg", "updating an existing issue failed, opening a new one", "updated_issue_id", issueToUpdate.IID) + g.logger.WithField("updated_issue_id", issueToUpdate.IID).Warn("updating an existing issue failed, opening a new one") } else { return nil } @@ -238,11 +257,11 @@ func (g *Gitlab) CreateIssue(msg *alertmanager.Webhook) error { } func (g *Gitlab) ping() error { - level.Debug(g.logger).Log("msg", "trying to ping gitlab", "url", g.client.BaseURL()) + g.logger.WithField("url", g.client.BaseURL()).Debug("trying to ping gitlab") _, err := http.Head(g.client.BaseURL().String()) if err != nil { metrics.ReportError("FailedToPingGitlab", "gitlab") - level.Error(g.logger).Log("msg", "failed to ping gitlab with HEAD request", "err", err) + g.logger.WithField("err", err).Error("failed to ping gitlab with HEAD request") return err } return nil diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 2159e7f..3fad989 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -19,9 +19,8 @@ import ( "time" "github.com/fusakla/prometheus-gitlab-notifier/pkg/metrics" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" ) var ( @@ -56,13 +55,13 @@ func (w *instrumentedWriter) Write(b []byte) (int, error) { } // Instrumented returns instrumented handler which provides access logging and prometheus metrics for incoming requests. -func Instrumented(logger log.Logger, handler http.Handler) http.HandlerFunc { +func Instrumented(logger log.FieldLogger, handler http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() sw := instrumentedWriter{ResponseWriter: w} handler.ServeHTTP(&sw, r) duration := time.Since(start) - level.Info(logger).Log("msg", "access log", "uri", r.RequestURI, "method", r.Method, "status", sw.status, "remote_addr", r.RemoteAddr, "duration", duration) + logger.WithFields(log.Fields{"uri": r.RequestURI, "method": r.Method, "status": sw.status, "remote_addr": r.RemoteAddr, "duration": duration}).Info("served request") metricsEndpoint := r.URL.Path if sw.status == 404 { metricsEndpoint = "non-existing-endpoint" diff --git a/pkg/prober/prober.go b/pkg/prober/prober.go index 823a7d7..e349445 100644 --- a/pkg/prober/prober.go +++ b/pkg/prober/prober.go @@ -18,13 +18,12 @@ import ( "net/http" "sync" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "github.com/gorilla/mux" + log "github.com/sirupsen/logrus" ) -// NewInRouter returns new Prober which registers it's endpoints in the Router to provide readiness and liveness endpoints. -func NewInRouter(logger log.Logger, router *mux.Router) *prober { +// NewInRouter returns new Prober which registers its endpoints in the Router to provide readiness and liveness endpoints. +func NewInRouter(logger log.FieldLogger, router *mux.Router) *prober { p := &prober{ logger: logger, serverReady: nil, @@ -35,7 +34,7 @@ func NewInRouter(logger log.Logger, router *mux.Router) *prober { // prober holds application readiness/liveness status and provides handlers for reporting it. type prober struct { - logger log.Logger + logger log.FieldLogger serverReadyMtx sync.RWMutex serverReady error } @@ -51,7 +50,7 @@ func (p *prober) livenessHandler(w http.ResponseWriter, r *http.Request) { } func (p *prober) writeFailedReadiness(w http.ResponseWriter, err error) { - level.Error(p.logger).Log("msg", "readiness probe failed", "err", err) + p.logger.WithField("err", err).Error("readiness probe failed") http.Error(w, err.Error(), http.StatusServiceUnavailable) } @@ -68,7 +67,7 @@ func (p *prober) readinessHandler(w http.ResponseWriter, r *http.Request) { func (p *prober) SetServerNotReady(err error) { p.serverReadyMtx.Lock() defer p.serverReadyMtx.Unlock() - level.Warn(p.logger).Log("msg", "Marking server as not ready", "reason", err) + p.logger.WithField("reason", err).Warn("Marking server as not ready") p.serverReady = err } diff --git a/pkg/processor/processor.go b/pkg/processor/processor.go index 815852d..fc38db5 100644 --- a/pkg/processor/processor.go +++ b/pkg/processor/processor.go @@ -19,9 +19,8 @@ import ( "github.com/fusakla/prometheus-gitlab-notifier/pkg/alertmanager" "github.com/fusakla/prometheus-gitlab-notifier/pkg/gitlab" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" ) var ( @@ -41,14 +40,14 @@ func init() { } // New returns new processor which handles the alert queue and retrying. -func New(logger log.Logger) *processor { +func New(logger log.FieldLogger) *processor { return &processor{ logger: logger, } } type processor struct { - logger log.Logger + logger log.FieldLogger } // Process processes alerts from the given channel and creates Gitlab issues from them. @@ -64,10 +63,10 @@ func (p *processor) Process(ctx context.Context, gitlab *gitlab.Gitlab, alertCha if !ok { return } - level.Debug(p.logger).Log("msg", "fetched alert from queue for processing", "group_key", alert.GroupKey) + p.logger.WithField("group_key", alert.GroupKey).Debug("fetched alert from queue for processing") if err := gitlab.CreateIssue(alert); err != nil { if alert.RetryCount() >= retryLimit-1 { - level.Warn(p.logger).Log("msg", "alert exceeded maximum number of retries, dropping it", "group_key", alert.GroupKey, "retry_count", retryLimit) + p.logger.WithFields(log.Fields{"group_key": alert.GroupKey, "retry_count": retryLimit}).Warn("alert exceeded maximum number of retries, dropping it") continue } go func() { @@ -75,7 +74,7 @@ func (p *processor) Process(ctx context.Context, gitlab *gitlab.Gitlab, alertCha alert.Retry() alertChannel <- alert retryCount.Inc() - level.Warn(p.logger).Log("msg", "added alert to queue for retrying ", "group_key", alert.GroupKey, "retry_backoff", retryBackoff) + p.logger.WithFields(log.Fields{"group_key": alert.GroupKey, "retry_backoff": retryBackoff}).Warn("added alert to queue for retrying ") }() } processedItems.Inc() -- GitLab