diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c2f20be1fec828bcecf0ac9ea324332d9bfe194b
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,49 @@
+---
+version: 2.1
+
+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
+
+workflows:
+  version: 2
+  pushgateway:
+    jobs:
+    - test:
+        filters:
+          tags:
+            only: /.*/
+    - prometheus/build:
+        name: build
+        filters:
+          tags:
+            only: /.*/
+    - prometheus/publish_master:
+        requires:
+        - test
+        - build
+        filters:
+          branches:
+            only: master
+    - prometheus/publish_release:
+        requires:
+        - test
+        - build
+        filters:
+          tags:
+            only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
+          branches:
+            ignore: /.*/
diff --git a/.gitignore b/.gitignore
index f1c181ec9c5c921245027c6b452ecfc1d3626364..2336c7440c14e3b07d9d9a659445c2aa1a09dfe4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Go template
 # Binaries for programs and plugins
 *.exe
 *.exe~
@@ -5,8 +7,15 @@
 *.so
 *.dylib
 
-# Test binary, build with `go test -c`
+# Test binary, built with `go test -c`
 *.test
 
 # Output of the go coverage tool, specifically when used with LiteIDE
 *.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+/conf/*.token
+/prometheus-gitlab-notifier
+
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f9abe8d6ffe60de80fce45af93b4ab8188d9ef3e
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,6 @@
+service:
+  golangci-lint-version: 1.12.4
+
+linters-settings:
+  errcheck:
+    exclude: errcheck_excludes.txt
diff --git a/.promu.yml b/.promu.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d4e4510a412987224850ba3de231abf637cbca96
--- /dev/null
+++ b/.promu.yml
@@ -0,0 +1,20 @@
+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
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..cf94cc5870416ab8f584847f7099b8646eba352b
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,62 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [0.6.0] - 2019-07-17
+
+>**! Warning:** This release significantly changes logic of creating Gitlab issues and labeling scheme. 
+Please read more about the new grouping feature.  
+
+## Changed
+- Dynamic labels are now added as scoped labels to the issues in form `label::value`
+- To every issue the group- To every issue the grouping labels are added as scoped labels same way as dynamic labels. 
+ing labels are added as scoped labels same way as dynamic labels. 
+
+## Added
+- If alert comes and opened issue with the same group labels is present in the Gitlab, 
+the rendered template is just appended to this already existing issue instead of creating a new one.
+This applies only for issues younger than by default `1h` which can be controlled by new flag `--group.interval`. 
+Every appended issue gets new scoped label `appended-alerts::<numer>` with number of times it was appended.
+- Readme notes about contributing and release.
+
+## [0.5.0] - 2019-07-10
+
+## Added
+- Added dynamic label addition from the alert labels using flag `dynamic.issue.label.name`
+
+## [0.4.1] - 2019-06-27
+
+## Fixed
+- Metric `app_build_info` is now initialized to value `1`
+
+## [0.4.0] - 2019-06-27
+
+## Added
+- Added time to log messages
+- Added metric `app_build_info` with info about version of the app, build etc.
+
+## [0.3.0] - 2019-06-26
+
+## Changed
+- Removed Gitlab call from readiness probe since the alerts
+are just enqueued and retrying should take care of that.
+
+## Added
+- Check on startup that Gitlab is reachable.
+
+## [0.2.0] - 2019-06-26
+
+## Added:
+- Added `status_code` to metrics and access log.
+
+## Changed
+- Refactored HTTP server middleware.
+
+## [0.1.0] - 2019-06-25
+
+Initial release
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..84792d8aaf8c17c676665fc2caa7e841c1e259ac
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,16 @@
+ARG ARCH="amd64"
+ARG OS="linux"
+FROM quay.io/prometheus/busybox:latest
+LABEL maintainer="FUSAKLA Martin Chodúr <m.chodur@seznam.cz>"
+
+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 Dockerfile /
+
+EXPOSE 9288
+RUN mkdir -p /prometheus-gitlab-notifier && chown nobody:nogroup /prometheus-gitlab-notifier
+WORKDIR /prometheus-gitlab-notifier
+
+USER 65534
+
+ENTRYPOINT ["/bin/prometheus-gitlab-notifier"]
diff --git a/LICENSE b/LICENSE
index 43fddfdf38cdc58c8760481abd2fe9d688e22806..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,201 @@
-MIT License
-
-Copyright (c) 2019 Martin Chodur
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
new file mode 100644
index 0000000000000000000000000000000000000000..cd9df82915cb2a1cf95284b5db70cf204f9402cd
--- /dev/null
+++ b/MAINTAINERS.md
@@ -0,0 +1 @@
+* FUSAKLA Martin Chodúr <m.chodur@seznam.cz>
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..9fcb1431fe5d308391237acd80e32d39af739663
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
+# 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 armv7 arm64
+
+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
new file mode 100644
index 0000000000000000000000000000000000000000..db98993d699ce89ab333bba92d65615073c707b9
--- /dev/null
+++ b/Makefile.common
@@ -0,0 +1,277 @@
+# 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 1143b316a7ab72f270dddeb722e1c9eb8f3a559a..f0c0e17674119ccdfa10d980bbae09d6e27ad7e0 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,92 @@
-# prometheus-gitlab-notifier
\ No newline at end of file
+# Prometheus Gitlab Notifier
+
+Tool which implements [Alertmanager](https://github.com/prometheus/alertmanager) webhook notifier
+and creates Gitlab issue with the alert metadata.
+
+For new features or changes check out the [CHANGELOG.md](./CHANGELOG.md)
+
+### How to run it
+```
+$ ./prometheus-gitlab-notifier --help
+usage: prometheus-gitlab-notifier --gitlab.url=GITLAB.URL --gitlab.token.file=GITLAB.TOKEN.FILE --project.id=PROJECT.ID [<flags>]
+
+Web server listening for webhooks of alertmanager and creating an issue in Gitlab based on it.
+
+Flags:
+  --help                         Show context-sensitive help (also try --help-long and --help-man).
+  --debug                        Enables debug logging.
+  --server.addr="0.0.0.0:9288"   Allows to change the address and port at which the server will listen for incoming connections.
+  --gitlab.url=GITLAB.URL        URL of the Gitlab API.
+  --gitlab.token.file=GITLAB.TOKEN.FILE  
+                                 Path to file containing gitlab token.
+  --project.id=PROJECT.ID        Id of project where to create the issues.
+  --group.interval=1h            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').
+  --issue.label=ISSUE.LABEL ...  Labels to add to the created issue. (Can be passed multiple times)
+  --dynamic.issue.label.name=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)
+  --issue.template=conf/default_issue.tmpl  
+                                 Path to the issue golang template file.
+  --queue.size.limit=100         Limit of the alert queue size.
+  --retry.backoff=5m             Duration how long to wait till next retry (go duration syntax allowing 'ns', 'us' , 'ms', 's', 'm', 'h').
+  --retry.limit=5                Maximum number of retries for single alert. If exceeded it's thrown away.
+  --graceful.shutdown.wait.duration=30s  
+                                 Duration how long to wait on graceful shutdown marked as not ready (go duration syntax allowing 'ns', 'us' , 'ms', 's', 'm', 'h').
+```
+
+To test it is running check logs or http://0.0.0.0:9288/readiness
+
+### 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).
+The available data during templating is the Alertmanager webhook message struct itself.
+Example can be found in [conf/alert.json](conf/alert.json).
+To use own template override the default one with the `--issue.template` flag.
+> The template is validated on startup but if even after validation the templating
+fails in the runtime, raw JSON of the alert will be pasted to the text of the issue as a fallback.
+
+Example of the default template:
+
+![Issue axample](conf/issue_example.png)
+
+### Configure Alertmanager
+You just need to add the [`<webhook_config>`](https://prometheus.io/docs/alerting/configuration/#webhook_config)
+receiver to your Alertmanager configuration and disable sending resolved notifications with `send_resolved: false`.
+Also better to set the `repeat_interval` to higher value since every retry will create new issue.
+
+See the minimal example in the [conf/alertmanager_conf.yaml](conf/alertmanager_conf.yaml).
+
+
+### Issue labeling scheme
+The Gitlab notifier allows to label the resulting issue based on the alert labels.
+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`.
+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`),
+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. 
+
+
+### Deployment
+Example kubernetes manifests can be found at [kubernetes/](./kubernetes)
+
+
+### Instrumentation
+
+- `/liveness`: liveness endpoint returns always 200
+- `/readiness`: tries HEAD request to the configured Gitlab URL and fails if it does not succeeds. 
+- `/metrics`: metrics endpoint returning app runtime metrics in Prometheus format
+
+### How to contribute and release
+
+**Contributing:**
+1. Implement your changes and test them on your own testing repository.
+1. Add note about changes made to the [CHANGELOG.md](CHANGELOG.md) `[Unreleased]` section.
+1. Create PR and apply for CR from maintainers.
+1. Get it merged.
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000000000000000000000000000000000000..a918a2aa18d5bec6a8bb93891a7a63c243111796
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.6.0
diff --git a/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go b/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go
new file mode 100644
index 0000000000000000000000000000000000000000..7741b76ee81959ee76c00d0beaa3e2b6c4834c4a
--- /dev/null
+++ b/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go
@@ -0,0 +1,189 @@
+// Copyright 2019 FUSAKLA Martin Chodúr
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"context"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"os/signal"
+	"strings"
+	"syscall"
+	"text/template"
+	"time"
+
+	"github.com/alecthomas/kingpin"
+	"github.com/fusakla/prometheus-gitlab-notifier/pkg/alertmanager"
+	"github.com/fusakla/prometheus-gitlab-notifier/pkg/api"
+	"github.com/fusakla/prometheus-gitlab-notifier/pkg/gitlab"
+	"github.com/fusakla/prometheus-gitlab-notifier/pkg/handler"
+	"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"
+)
+
+func setupLogger(debug bool) log.Logger {
+	l := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
+	l = log.With(l, "ts", log.DefaultTimestamp, "caller", log.DefaultCaller)
+	if debug {
+		l = level.NewFilter(l, level.AllowDebug())
+	} else {
+		l = level.NewFilter(l, level.AllowInfo())
+	}
+	return l
+}
+
+func waitForEmptyChannel(logger log.Logger, ch <-chan *alertmanager.Webhook) {
+	level.Info(logger).Log("msg", "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))
+			time.Sleep(10 * time.Millisecond)
+			continue
+		}
+		break
+	}
+	level.Info(logger).Log("msg", "processing of the rest of alerts is done")
+}
+
+func startServer(logger log.Logger, r http.Handler) (*http.Server, <-chan error) {
+	errCh := make(chan error, 1)
+	srv := &http.Server{
+		Handler:      handler.Instrumented(logger, r),
+		Addr:         *serverAddr,
+		WriteTimeout: 5 * time.Second,
+		ReadTimeout:  5 * time.Second,
+	}
+	go func() {
+		defer close(errCh)
+		level.Info(logger).Log("msg", "Starting prometheus-gitlab-notifier", "addr", "0.0.0.0:9288")
+		if err := srv.ListenAndServe(); err != nil {
+			if err != http.ErrServerClosed {
+				level.Error(logger).Log("msg", "server failed", "error", err)
+				errCh <- err
+			}
+		}
+	}()
+	return srv, errCh
+}
+
+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()
+	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:9288").String()
+	gitlabURL            = app.Flag("gitlab.url", "URL of the Gitlab API.").Required().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()
+	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() {
+
+	kingpin.MustParse(app.Parse(os.Args[1:]))
+
+	// Initiate logging.
+	logger := setupLogger(*debug)
+
+	// Initiate Gitlab client.
+	gitlabIssueTextTemplate, err := template.ParseFiles(*issueTemplatePath)
+	if err != nil {
+		level.Error(logger).Log("msg", "invalid gitlab issue template", "file", *issueTemplatePath, "err", err)
+		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)
+		os.Exit(1)
+	}
+	g, err := gitlab.New(
+		log.With(logger, "component", "gitlab"),
+		*gitlabURL,
+		strings.TrimSpace(string(token)),
+		*projectId,
+		gitlabIssueTextTemplate,
+		issueLabels,
+		dynamicIssueLabels,
+		groupInterval,
+	)
+	if err != nil {
+		level.Error(logger).Log("msg", "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"))
+	processCtx, processCancelFunc := context.WithCancel(context.Background())
+	defer processCancelFunc()
+	proc.Process(processCtx, g, alertChan, *retryLimit, *retryBackoff)
+
+	// Setup routing for HTTP server.
+	r := mux.NewRouter()
+	// Initialize the main API.
+	webhookApi := api.NewInRouter(
+		log.With(logger, "component", "api"),
+		r.PathPrefix("/api").Subrouter(),
+		alertChan,
+	)
+	// Initialize prober providing readiness and liveness checks.
+	readinessProber := prober.NewInRouter(
+		log.With(logger, "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)
+
+	// Subscribe to system signals so we can react on them with graceful termination.
+	gracefulStop := make(chan os.Signal, 2)
+	signal.Notify(gracefulStop, syscall.SIGTERM)
+	signal.Notify(gracefulStop, syscall.SIGINT)
+
+	// It the server fails or we receive signal to gracefully shut down we wait till the alert queue is processed(empty).
+	for {
+		select {
+		case <-serverErrorChan:
+			// If server failed just wait for all the alerts to be processed.
+			waitForEmptyChannel(logger, alertChan)
+			os.Exit(1)
+		case sig := <-gracefulStop:
+			level.Info(logger).Log("msg", "received system signal for graceful shutdown", "signal", sig)
+			// 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)
+			time.Sleep(*gracefulShutdownWait)
+			// Stop receiving new alerts.
+			webhookApi.Close()
+			// Wait for all enqueued alerts to be processed.
+			waitForEmptyChannel(logger, alertChan)
+			os.Exit(0)
+		}
+	}
+
+}
diff --git a/conf/alert.json b/conf/alert.json
new file mode 100644
index 0000000000000000000000000000000000000000..81a1e80ec92371b32bc738f24bde5edc9b1ca98f
--- /dev/null
+++ b/conf/alert.json
@@ -0,0 +1,32 @@
+{
+  "version": "1",
+  "groupKey": "meh",
+  "receiver": "test-receiver",
+  "status": "firing",
+  "alerts": [
+    {
+      "status": "test",
+      "labels": {
+        "app": "test"
+      },
+      "annotations": {
+        "description": "this is a testing alert"
+      },
+      "startsAt": "2018-09-22T12:42:31Z",
+      "endsAt": "2018-09-22T12:42:31Z",
+      "generatorURL": "http://test.com"
+    }
+  ],
+  "groupLabels": {
+    "label": "value"
+  },
+  "commonLabels": {
+    "locality": "nagano",
+    "severity": "warning",
+    "alertname": "ThisIsATestingAlert"
+  },
+  "commonAnnotations": {
+    "title": "Something is wrong!!!"
+  },
+  "externalURL": "http://test.com"
+}
diff --git a/conf/alertmanager_conf.yaml b/conf/alertmanager_conf.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1e3044d65d718aca4999876e231c6595f16e0f31
--- /dev/null
+++ b/conf/alertmanager_conf.yaml
@@ -0,0 +1,9 @@
+route:
+  repeat_interval: 10000h
+  receiver: PrometheusGitlabNotifier
+
+  receivers:
+    - name: PrometheusGitlabNotifier
+      webhook_configs:
+        - send_resolved: false
+          url: http://0.0.0.0:9288/api/alertmanager
diff --git a/conf/default_issue.tmpl b/conf/default_issue.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..d05b890fbb085838ee609407b4603664fddcda3e
--- /dev/null
+++ b/conf/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/conf/issue_example.png b/conf/issue_example.png
new file mode 100644
index 0000000000000000000000000000000000000000..1fc2427e13ff018d242713f6e8b42597a49344f9
Binary files /dev/null and b/conf/issue_example.png differ
diff --git a/errcheck_excludes.txt b/errcheck_excludes.txt
new file mode 100644
index 0000000000000000000000000000000000000000..97b977141bb32b71933ad87d7c985bedf10728d7
--- /dev/null
+++ b/errcheck_excludes.txt
@@ -0,0 +1 @@
+(github.com/go-kit/kit/log.Logger).Log
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..f870b07e4ead883facf5623cb86900d9ff08b981
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,13 @@
+module github.com/fusakla/prometheus-gitlab-notifier
+
+go 1.12
+
+require (
+	github.com/alecthomas/kingpin v2.2.6+incompatible
+	github.com/go-kit/kit v0.8.0
+	github.com/gorilla/mux v1.7.2
+	github.com/pkg/errors v0.8.0
+	github.com/prometheus/alertmanager v0.17.1-0.20190619131440-bef850ac905c
+	github.com/prometheus/client_golang v1.0.0
+	github.com/xanzy/go-gitlab v0.18.0
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..f4a26cf92edef7703bdcbb9fc0e260d719ad85b6
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,195 @@
+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=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI=
+github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409 h1:Da6uN+CAo1Wf09Rz1U4i9QN8f0REjyNJ73BEwAq/paU=
+github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059 h1:o4GWHLIzU2GCL0R5PZVFpVdPCGmzBH0tXXZlZ78QddA=
+github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
+github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.17.2/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.17.2/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
+github.com/go-openapi/runtime v0.18.0/go.mod h1:uI6pHuxWYTy94zZxgcwJkUWa9wbIlhteGfloI10GD4U=
+github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.0/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=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
+github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/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=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
+github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/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/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
+github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
+github.com/oklog/ulid v0.0.0-20170117200651-66bb6560562f h1:UpfE/Q64+1idrbE+phdstApLr3SJBSjkxg8AvRx1mSk=
+github.com/oklog/ulid v0.0.0-20170117200651-66bb6560562f/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/alertmanager v0.17.1-0.20190619131440-bef850ac905c h1:NT8YaSNAgl5RYWyL/i05KO+R8yCq2mn1NLhUPFdnaoU=
+github.com/prometheus/alertmanager v0.17.1-0.20190619131440-bef850ac905c/go.mod h1:WcxHBl40VSPuOaqWae6l6HpnEOVRIycEJ7i9iYkadEE=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/prometheus v0.0.0-20180315085919-58e2a31db8de/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s=
+github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef h1:RoeI7K0oZIcUirMHsFpQjTVDrl1ouNh8T7v3eNsUxL0=
+github.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 h1:SWV2fHctRpRrp49VXJ6UZja7gU9QLHwRpIPBN89SKEo=
+github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
+github.com/shurcooL/vfsgen v0.0.0-20180825020608-02ddb050ef6b h1:rKVW5h3pEu8gGxD+ZlOmBvFYAxXLCYeQv/eg+t6QvLQ=
+github.com/shurcooL/vfsgen v0.0.0-20180825020608-02ddb050ef6b/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+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=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/xanzy/go-gitlab v0.18.0 h1:LybNSWSIw8BK+GnxuETAhUXEzzh5rHsHjopqVkGJXRE=
+github.com/xanzy/go-gitlab v0.18.0/go.mod h1:LSfUQ9OPDnwRqulJk2HcWaAiFfCzaknyeGvjQI67MbE=
+github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 h1:JIqe8uIcRBHXDQVvZtHwp80ai3Lw3IJAeJEs55Dc1W0=
+golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5 h1:mzjBh+S5frKOsOBobWIMAbXavqjmgO17k/2puhcFR94=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180805044716-cb6730876b98/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190118193359-16909d206f00 h1:6OmoTtlNJlHuWNIjTEyUtMBHrryp8NRuf/XtnC7MmXM=
+golang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
+google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/kubernetes/gitlab-token-secret.yaml b/kubernetes/gitlab-token-secret.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a75ca85d2032e89e7b3dda77a02e2985ffcfe225
--- /dev/null
+++ b/kubernetes/gitlab-token-secret.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: prometheus-gitlab-notifier-gitlab-token
+data:
+  gitlab_token: "Y2hhY2hh"
diff --git a/kubernetes/issue-template-configmap.yaml b/kubernetes/issue-template-configmap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1fa8b02c5a714ce29d0588346b86eba0c040fd08
--- /dev/null
+++ b/kubernetes/issue-template-configmap.yaml
@@ -0,0 +1,37 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: prometheus-gitlab-notifier-issue-template
+data:
+  issue.tmpl: |
+    {{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/kubernetes/prometheus-gitlab-notifier-deployment.yaml b/kubernetes/prometheus-gitlab-notifier-deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..25f44447089b4a7c228e89596765bb1cbe73d0d9
--- /dev/null
+++ b/kubernetes/prometheus-gitlab-notifier-deployment.yaml
@@ -0,0 +1,55 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: prometheus-gitlab-notifier
+spec:
+  selector:
+    matchLabels:
+      app: prometheus-gitlab-notifier
+  replicas: 2
+  template:
+    metadata:
+      labels:
+        app: prometheus-gitlab-notifier
+    spec:
+      containers:
+        - name: prometheus-gitlab-notifier
+          image: fusakla/prometheus-gitlab-notifier:0.6.0
+          args:
+            - "--gitlab.url=https://gitlab.com/api/v4"
+            - "--project.id=13766104"
+            - "--issue.label=automated-alert-issue"
+            - "--group.interval=168h" # 7d
+            - "--issue.template=/prometheus-gitlab-notifier/issue-templates/issue.tmpl"
+            - "--gitlab.token.file=/prometheus-gitlab-notifier/secrets/gitlab_token"
+          readinessProbe:
+            httpGet:
+              port: 9288
+              path: /readiness
+          livenessProbe:
+            httpGet:
+              port: 9288
+              path: /liveness
+          ports:
+            - containerPort: 9288
+          resources:
+            requests:
+              cpu: "50m"
+              memory: "50Mi"
+            limits:
+              cpu: "500m"
+              memory: "512Mi"
+          volumeMounts:
+            - name: issue-template
+              readOnly: true
+              mountPath: "/prometheus-gitlab-notifier/issue-templates/"
+            - name: gitlab-token
+              readOnly: true
+              mountPath: "/prometheus-gitlab-notifier/secrets"
+      volumes:
+        - name: issue-template
+          configMap:
+            name: prometheus-gitlab-notifier-issue-template
+        - name: gitlab-token
+          secret:
+            secretName: prometheus-gitlab-notifier-gitlab-token
diff --git a/kubernetes/prometheus-gitlab-notifier-service.yaml b/kubernetes/prometheus-gitlab-notifier-service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..18ff7f95becdc384ca7daad7dc1a98d5cb3de203
--- /dev/null
+++ b/kubernetes/prometheus-gitlab-notifier-service.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: prometheus-gitlab-notifier
+  labels:
+    app: prometheus-gitlab-notifier
+spec:
+  ports:
+    - port: 9288
+      protocol: TCP
+  selector:
+    app: prometheus-gitlab-notifier
diff --git a/pkg/alertmanager/webhook.go b/pkg/alertmanager/webhook.go
new file mode 100644
index 0000000000000000000000000000000000000000..7093d5ecd26cb2d71b501f50c5b779e690194d3d
--- /dev/null
+++ b/pkg/alertmanager/webhook.go
@@ -0,0 +1,49 @@
+// Copyright 2019 FUSAKLA Martin Chodúr
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package alertmanager
+
+import (
+	"sync"
+
+	"github.com/prometheus/alertmanager/notify/webhook"
+)
+
+// NewWebhookFromAlertmanagerMessage returns new Webhook wrapping the original Alertmanager webhook.message.
+func NewWebhookFromAlertmanagerMessage(message webhook.Message) *Webhook {
+	return &Webhook{
+		Message:    message,
+		retryCount: 0,
+	}
+}
+
+// Webhook is wrapper for the Alertmanager webhook.message adding retry counter.
+type Webhook struct {
+	webhook.Message
+	retryCount int
+	retryMtx   sync.RWMutex
+}
+
+// Retry increments number of retries for the Webhook.
+func (w *Webhook) Retry() {
+	w.retryMtx.Lock()
+	defer w.retryMtx.Unlock()
+	w.retryCount++
+}
+
+// RetryCount returns number of retries for the given alertmanager message.
+func (w *Webhook) RetryCount() int {
+	w.retryMtx.RLock()
+	defer w.retryMtx.RUnlock()
+	return w.retryCount
+}
diff --git a/pkg/api/api.go b/pkg/api/api.go
new file mode 100644
index 0000000000000000000000000000000000000000..5acd4b8a7153396b220c03d52b52d1c6e0180ce6
--- /dev/null
+++ b/pkg/api/api.go
@@ -0,0 +1,86 @@
+// Copyright 2019 FUSAKLA Martin Chodúr
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"sync"
+
+	"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"
+)
+
+// 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 {
+	api := &Api{
+		logger:        logger,
+		alertChan:     ch,
+		receiveAlerts: true,
+	}
+	api.registerHandlers(r)
+	return api
+}
+
+// Api defines handler functions for receiving Alertmanager endpoints.
+type Api struct {
+	logger           log.Logger
+	alertChan        chan<- *alertmanager.Webhook
+	receiveAlerts    bool
+	receiveAlertsMtx sync.RWMutex
+}
+
+func (a *Api) registerHandlers(router *mux.Router) {
+	router.HandleFunc("/alertmanager", a.webhookHandler)
+}
+
+func (a *Api) webhookHandler(w http.ResponseWriter, r *http.Request) {
+	if !a.canReceiveAlerts() {
+		http.Error(w, "Server is not receiving new alerts.", http.StatusServiceUnavailable)
+		return
+	}
+	var message webhook.Message
+	err := json.NewDecoder(r.Body).Decode(&message)
+	if err != nil {
+		http.Error(w, fmt.Sprintf("Invalid incomming webhook format. Failed with error: %s", err), http.StatusBadRequest)
+		return
+	}
+
+	// Push the message to channel
+	a.alertChan <- alertmanager.NewWebhookFromAlertmanagerMessage(message)
+	level.Debug(a.logger).Log("msg", "enqueued alert for processing", "group_key", message.GroupKey)
+
+	w.WriteHeader(http.StatusOK)
+	_, _ = io.WriteString(w, `Ok, Alert enqueued.`)
+}
+
+// Close disabled receiving of new alerts in the API used mainly for graceful shutdown.
+func (a *Api) Close() {
+	a.receiveAlertsMtx.Lock()
+	defer a.receiveAlertsMtx.Unlock()
+	a.receiveAlerts = false
+	close(a.alertChan)
+}
+
+func (a *Api) canReceiveAlerts() bool {
+	a.receiveAlertsMtx.RLock()
+	defer a.receiveAlertsMtx.RUnlock()
+	return a.receiveAlerts
+}
diff --git a/pkg/gitlab/gitlab.go b/pkg/gitlab/gitlab.go
new file mode 100644
index 0000000000000000000000000000000000000000..69942c7de5c6635bfe3e6c93b71fccd239336e74
--- /dev/null
+++ b/pkg/gitlab/gitlab.go
@@ -0,0 +1,249 @@
+// Copyright 2019 FUSAKLA Martin Chodúr
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gitlab
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"regexp"
+	"strconv"
+	"text/template"
+	"time"
+
+	"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"
+	"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) {
+	cli := gitlab.NewClient(nil, token)
+	if err := cli.SetBaseURL(url); err != nil {
+		level.Error(logger).Log("msg", "invalid Gitlab URL", "url", url, "err", "err")
+		return nil, err
+	}
+	g := &Gitlab{
+		client:             cli,
+		projectId:          projectId,
+		issueTemplate:      issueTemplate,
+		issueLabels:        issueLabels,
+		dynamicIssueLabels: dynamicIssueLabels,
+		groupInterval:      groupInterval,
+		logger:             logger,
+	}
+	if err := g.ping(); err != nil {
+		level.Error(logger).Log("msg", "cannot reach the Gitlab", "url", url, "err", "err")
+		return nil, err
+	}
+	return g, nil
+}
+
+// Gitlab holds configured Gitlab client and provides API for creating templated issue from the Webhook.
+type Gitlab struct {
+	client             *gitlab.Client
+	projectId          int
+	issueTemplate      *template.Template
+	issueLabels        *[]string
+	dynamicIssueLabels *[]string
+	groupInterval      *time.Duration
+	logger             log.Logger
+}
+
+func (g *Gitlab) formatGitlabScopedLabel(key string, value string) string {
+	return fmt.Sprintf("%s::%s", key, value)
+}
+
+func (g *Gitlab) extractDynamicLabels(msg *alertmanager.Webhook) []string {
+	var labelsMap = map[string]string{}
+	for _, a := range msg.Alerts {
+		for k, v := range a.Labels {
+			for _, l := range *g.dynamicIssueLabels {
+				if k == l {
+					labelsMap[k] = v
+				}
+			}
+		}
+	}
+	var resLabels []string
+	for k, v := range labelsMap {
+		resLabels = append(resLabels, g.formatGitlabScopedLabel(k, v))
+	}
+	return resLabels
+}
+
+func (g *Gitlab) extractGroupingLabels(msg *alertmanager.Webhook) []string {
+	var resLabels []string
+	for k, v := range msg.GroupLabels {
+		resLabels = append(resLabels, g.formatGitlabScopedLabel(k, v))
+	}
+	return resLabels
+}
+
+func (g *Gitlab) renderIssueTemplate(msg *alertmanager.Webhook) (*bytes.Buffer, error) {
+	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.
+		metrics.ReportError("IssueTemplateError", "")
+		level.Error(g.logger).Log("msg", "failed to template issue text, using pure JSON instead", "err", err)
+		w := bufio.NewWriter(&issueText)
+		if err := json.NewEncoder(w).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)
+			return nil, err
+		}
+	}
+	return &issueText, nil
+}
+
+func (g *Gitlab) getOpenIssuesSince(groupingLabels []string, sinceTime time.Time) ([]*gitlab.Issue, error) {
+	openState := "opened"
+	scope := "created_by_me"
+	orderBy := "created_at"
+	listOpts := gitlab.ListIssuesOptions{
+		Labels:       groupingLabels,
+		CreatedAfter: &sinceTime,
+		State:        &openState,
+		Scope:        &scope,
+		OrderBy:      &orderBy,
+	}
+	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)
+		return []*gitlab.Issue{}, err
+	}
+	return issues, nil
+}
+
+func (g *Gitlab) getTimeBefore(before *time.Duration) time.Time {
+	return time.Now().Local().Add(-*before)
+}
+
+func (g *Gitlab) createGitlabIssue(msg *alertmanager.Webhook, groupingLabels []string, issueText *bytes.Buffer) error {
+	// Collect all new issue labels
+	labels := *g.issueLabels
+	labels = append(labels, groupingLabels...)
+	labels = append(labels, g.extractDynamicLabels(msg)...)
+	options := &gitlab.CreateIssueOptions{
+		Title:       gitlab.String(fmt.Sprintf("Firing alert `%s`", msg.CommonLabels["alertname"])),
+		Description: gitlab.String(issueText.String()),
+		Labels:      labels,
+	}
+
+	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)
+		return err
+	}
+	level.Info(g.logger).Log("msg", "created issue in gitlab", "gitlab_issue_id", createdIssue.IID, "alert_grouping_key", msg.GroupKey)
+	return nil
+}
+
+func (g *Gitlab) increaseAppendLabel(labels []string) []string {
+	// Every updated issue has special label containing number of updates
+	appendLabelRegex := regexp.MustCompile(`(appended-alerts)::(\d+)`)
+	alreadyAppended := false
+	var newLabels []string
+	for _, l := range labels {
+		// Check if the label is the special one
+		matched := appendLabelRegex.FindStringSubmatch(l)
+		if len(matched) == 3 {
+			alreadyAppended = true
+			// 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)
+				newLabels = append(newLabels, l)
+				continue
+			}
+			// Increase the number of appends and add override the old label with it
+			newLabels = append(newLabels, g.formatGitlabScopedLabel(matched[1], strconv.Itoa(count+1)))
+			continue
+		}
+		newLabels = append(newLabels, l)
+	}
+	if !alreadyAppended {
+		newLabels = append(newLabels, g.formatGitlabScopedLabel("appended-alerts", "1"))
+	}
+	return newLabels
+}
+
+func (g *Gitlab) updateGitlabIssue(issue *gitlab.Issue, issueText *bytes.Buffer) error {
+	newLabels := g.increaseAppendLabel(issue.Labels)
+	options := &gitlab.UpdateIssueOptions{
+		// Concat original description with the new rendered template separated by `Appended on <date>` statement
+		Description: gitlab.String(fmt.Sprintf("%s\n\n&nbsp;\n\n&nbsp;\n\n&nbsp;\n\n_Appended on `%s`_\n%s", issue.Description, time.Now().Local(), issueText.String())),
+		Labels:      newLabels,
+	}
+	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)
+		return err
+	}
+	level.Info(g.logger).Log("msg", "updated issue in gitlab", "gitlab_issue_id", issue.IID)
+	return nil
+}
+
+// CreateIssue from the Webhook in Gitlab
+func (g *Gitlab) CreateIssue(msg *alertmanager.Webhook) error {
+	// Extract grouping labels from the message
+	groupingLabels := g.extractGroupingLabels(msg)
+
+	// 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")
+	}
+
+	// Try to render the issue text template
+	issueText, err := g.renderIssueTemplate(msg)
+	if err != nil {
+		return err
+	}
+
+	if len(matchingIssues) > 0 {
+		// 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)
+		} else {
+			return nil
+		}
+	}
+	// Try to create a new issue rather than discarding it after failed update.
+	if err := g.createGitlabIssue(msg, groupingLabels, issueText); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (g *Gitlab) ping() error {
+	level.Debug(g.logger).Log("msg", "trying to ping gitlab", "url", g.client.BaseURL())
+	_, 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)
+		return err
+	}
+	return nil
+}
diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..2159e7fa20cf61c1b9e9c8aa0f4a22bb69546b9b
--- /dev/null
+++ b/pkg/handler/handler.go
@@ -0,0 +1,72 @@
+// Copyright 2019 FUSAKLA Martin Chodúr
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package handler
+
+import (
+	"net/http"
+	"strconv"
+	"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"
+)
+
+var (
+	requestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
+		Name:    "request_duration_seconds",
+		Help:    "Time (in seconds) spent serving HTTP requests.",
+		Buckets: prometheus.DefBuckets,
+	}, []string{"app", "method", "endpoint", "status_code"})
+)
+
+func init() {
+	metrics.Register(requestDuration)
+}
+
+type instrumentedWriter struct {
+	http.ResponseWriter
+	status int
+}
+
+// WriteHeader writes header to the response.
+func (w *instrumentedWriter) WriteHeader(status int) {
+	w.status = status
+	w.ResponseWriter.WriteHeader(status)
+}
+
+func (w *instrumentedWriter) Write(b []byte) (int, error) {
+	if w.status == 0 {
+		w.status = 200
+	}
+	n, err := w.ResponseWriter.Write(b)
+	return n, err
+}
+
+// Instrumented returns instrumented handler which provides access logging and prometheus metrics for incoming requests.
+func Instrumented(logger log.Logger, 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)
+		metricsEndpoint := r.URL.Path
+		if sw.status == 404 {
+			metricsEndpoint = "non-existing-endpoint"
+		}
+		requestDuration.WithLabelValues(metrics.AppLabel, r.Method, metricsEndpoint, strconv.Itoa(sw.status)).Observe(float64(duration.Seconds()))
+	}
+}
diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go
new file mode 100644
index 0000000000000000000000000000000000000000..668c05cbbe701b75e4a79cdfba280f52b6141a73
--- /dev/null
+++ b/pkg/metrics/metrics.go
@@ -0,0 +1,73 @@
+// Copyright 2019 FUSAKLA Martin Chodúr
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package metrics
+
+import (
+	"github.com/gorilla/mux"
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+)
+
+// AppLabel is constant name of the application used
+const AppLabel = "prometheus-gitlab-notifier"
+
+var (
+	appVersion  = "unknown"
+	gitRevision = "unknown"
+	gitBranch   = "unknown"
+	gitTag      = "unknown"
+
+	registry     *prometheus.Registry
+	errorsTotal  *prometheus.CounterVec
+	appBuildInfo *prometheus.CounterVec
+)
+
+func init() {
+	registry = prometheus.NewRegistry()
+
+	// Metric with information about build AppVersion, golang AppVersion etc/
+	appBuildInfo = prometheus.NewCounterVec(prometheus.CounterOpts{
+		Name: "app_build_info",
+		Help: "Metadata metric with info about build and AppVersion.",
+	}, []string{"app", "version", "revision", "branch", "tag"})
+	registry.MustRegister(appBuildInfo)
+	appBuildInfo.WithLabelValues(AppLabel, appVersion, gitRevision, gitBranch, gitTag).Inc()
+
+	// Generic metric for reporting errors
+	errorsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
+		Name: "errors_total",
+		Help: "Count of occurred errors.",
+	}, []string{"app", "type", "remote_app"})
+	registry.MustRegister(errorsTotal)
+
+	// When using custom registry we need to explicitly register the Go and process collectors.
+	registry.MustRegister(prometheus.NewGoCollector())
+	registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
+
+}
+
+// HandleInRouter registers prometheus metrics rendering in given router.
+func HandleInRouter(r *mux.Router) {
+	r.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
+}
+
+// Register new Prometheus metric Collector to the registry.
+func Register(m prometheus.Collector) {
+	registry.MustRegister(m)
+}
+
+// ReportError to errors_total metric global for the whole application.
+func ReportError(errorType string, remoteApp string) {
+	errorsTotal.WithLabelValues(AppLabel, errorType, remoteApp).Inc()
+}
diff --git a/pkg/prober/prober.go b/pkg/prober/prober.go
new file mode 100644
index 0000000000000000000000000000000000000000..823a7d777dbdf75152f6b99f66e174ab18b9e829
--- /dev/null
+++ b/pkg/prober/prober.go
@@ -0,0 +1,79 @@
+// Copyright 2019 FUSAKLA Martin Chodúr
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package prober
+
+import (
+	"io"
+	"net/http"
+	"sync"
+
+	"github.com/go-kit/kit/log"
+	"github.com/go-kit/kit/log/level"
+	"github.com/gorilla/mux"
+)
+
+// 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 {
+	p := &prober{
+		logger:      logger,
+		serverReady: nil,
+	}
+	p.registerInRouter(router)
+	return p
+}
+
+// prober holds application readiness/liveness status and provides handlers for reporting it.
+type prober struct {
+	logger         log.Logger
+	serverReadyMtx sync.RWMutex
+	serverReady    error
+}
+
+func (p *prober) registerInRouter(router *mux.Router) {
+	router.HandleFunc("/liveness", p.livenessHandler)
+	router.HandleFunc("/readiness", p.readinessHandler)
+}
+
+func (p *prober) livenessHandler(w http.ResponseWriter, r *http.Request) {
+	w.WriteHeader(http.StatusOK)
+	_, _ = io.WriteString(w, `OK`)
+}
+
+func (p *prober) writeFailedReadiness(w http.ResponseWriter, err error) {
+	level.Error(p.logger).Log("msg", "readiness probe failed", "err", err)
+	http.Error(w, err.Error(), http.StatusServiceUnavailable)
+}
+
+func (p *prober) readinessHandler(w http.ResponseWriter, r *http.Request) {
+	if err := p.isReady(); err != nil {
+		p.writeFailedReadiness(w, err)
+		return
+	}
+	w.WriteHeader(http.StatusOK)
+	_, _ = io.WriteString(w, `OK`)
+}
+
+// SetServerNotReady sets the readiness probe to invalid state.
+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.serverReady = err
+}
+
+func (p *prober) isReady() error {
+	p.serverReadyMtx.RLock()
+	defer p.serverReadyMtx.RUnlock()
+	return p.serverReady
+}
diff --git a/pkg/processor/processor.go b/pkg/processor/processor.go
new file mode 100644
index 0000000000000000000000000000000000000000..815852df0a42c5fa21450b6dd5598f412f3f9d16
--- /dev/null
+++ b/pkg/processor/processor.go
@@ -0,0 +1,85 @@
+// Copyright 2019 FUSAKLA Martin Chodúr
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package processor
+
+import (
+	"context"
+	"time"
+
+	"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"
+)
+
+var (
+	processedItems = prometheus.NewCounter(prometheus.CounterOpts{
+		Name: "prometheus_gitlab_notifier_processed_alerts_processed_total",
+		Help: "Count of processed alerts.",
+	})
+	retryCount = prometheus.NewCounter(prometheus.CounterOpts{
+		Name: "prometheus_gitlab_notifier_processed_alerts_retried_total",
+		Help: "Count of retries.",
+	})
+)
+
+func init() {
+	prometheus.MustRegister(processedItems)
+	prometheus.MustRegister(retryCount)
+}
+
+// New returns new processor which handles the alert queue and retrying.
+func New(logger log.Logger) *processor {
+	return &processor{
+		logger: logger,
+	}
+}
+
+type processor struct {
+	logger log.Logger
+}
+
+// Process processes alerts from the given channel and creates Gitlab issues from them.
+func (p *processor) Process(ctx context.Context, gitlab *gitlab.Gitlab, alertChannel chan *alertmanager.Webhook, retryLimit int, retryBackoff time.Duration) {
+	doneChannel := make(chan bool, 1)
+	go func() {
+		defer close(doneChannel)
+		for {
+			select {
+			case <-ctx.Done():
+				return
+			case alert, ok := <-alertChannel:
+				if !ok {
+					return
+				}
+				level.Debug(p.logger).Log("msg", "fetched alert from queue for processing", "group_key", alert.GroupKey)
+				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)
+						continue
+					}
+					go func() {
+						time.Sleep(retryBackoff)
+						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)
+					}()
+				}
+				processedItems.Inc()
+			}
+		}
+	}()
+}