From 222017e554deab1268d4a1f493ba9e7dc4d71597 Mon Sep 17 00:00:00 2001
From: Piotr Mossakowski
Date: Fri, 23 Dec 2022 11:19:05 +0100
Subject: [PATCH] ci: import getindata/docker-image-template
---
.github/workflows/pr_opened.yml | 119 +++++++++++++++++++++++++++++
.github/workflows/pr_title.yml | 56 ++++++++++++++
.github/workflows/release.yml | 91 +++++++++++++++++++++++
.gitignore | 1 +
CODE_OF_CONDUCT.md | 128 ++++++++++++++++++++++++++++++++
CONTRIBUTING.md | 37 +++++++++
README.md | 100 +++++++++++++++++++++++++
app/.dockerignore | 16 ++++
app/Dockerfile | 92 +++++++++++++++++++++++
app/check-gitlab-approvals.sh | 80 ++++++++++++++++++++
app/docker-entrypoint.sh | 46 ++++++++++++
approval-config-example.yaml | 8 ++
12 files changed, 774 insertions(+)
create mode 100644 .github/workflows/pr_opened.yml
create mode 100644 .github/workflows/pr_title.yml
create mode 100644 .github/workflows/release.yml
create mode 100644 .gitignore
create mode 100644 CODE_OF_CONDUCT.md
create mode 100644 CONTRIBUTING.md
create mode 100644 README.md
create mode 100644 app/.dockerignore
create mode 100644 app/Dockerfile
create mode 100755 app/check-gitlab-approvals.sh
create mode 100755 app/docker-entrypoint.sh
create mode 100644 approval-config-example.yaml
diff --git a/.github/workflows/pr_opened.yml b/.github/workflows/pr_opened.yml
new file mode 100644
index 0000000..fd76b45
--- /dev/null
+++ b/.github/workflows/pr_opened.yml
@@ -0,0 +1,119 @@
+name: build test scan docker images
+
+on:
+ pull_request:
+ branches:
+ - 'main'
+ - 'master'
+# paths:
+# - ${{ env.DOCKERFILE_PATH }}/Dockerfile
+
+env:
+ DOCKERFILE_PATH: app
+ DOCKERFILE_TAG: ${{ github.event.pull_request.head.sha }}
+ REGISTRY_PATH: gcr.io/getindata-images-public/docker-atlantis
+ REGISTRY_TYPE: "gcr.io" # If not set then will default to Docker Hub
+ REGISTRY_USERNAME: _json_key
+
+jobs:
+ buildtestscan:
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 100
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v2.2.1
+
+ - name: Cache Docker layers
+ uses: actions/cache@v3.2.0
+ with:
+ path: /tmp/.buildx-cache
+ key: ${{ runner.os }}-buildx-${{ env.DOCKERFILE_TAG }}
+ restore-keys: |
+ ${{ runner.os }}-buildx-
+
+ - name: Login to registry "${{ env.REGISTRY_TYPE }}"
+ uses: docker/login-action@v2.1.0
+ with:
+ registry: ${{ env.REGISTRY_TYPE }}
+ username: ${{ env.REGISTRY_USERNAME }}
+ password: ${{ secrets.REGISTRY_PASSWORD }}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v3.2.0
+ with:
+ context: "${{ env.DOCKERFILE_PATH }}"
+ push: true
+ tags: "${{ env.REGISTRY_PATH }}:${{ env.DOCKERFILE_TAG }}"
+ cache-from: type=local,src=/tmp/.buildx-cache
+ cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
+
+ - name: Run Checkov action
+ id: checkov
+ uses: bridgecrewio/checkov-action@v12.1347.0
+ with:
+ quiet: true # optional: display only failed checks
+ soft_fail: true # optional: do not return an error code if there are failed checks
+ framework: dockerfile
+ output_format: github_failed_only
+ log_level: WARNING # optional: set log level. Default WARNING
+ dockerfile_path: "${{ env.DOCKERFILE_PATH }}/Dockerfile" # path to the Dockerfile
+
+ - name: Show Checkov results
+ uses: actions-ecosystem/action-create-comment@v1
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ body: |
+ ## Checkov
+ ${{ env.CHECKOV_RESULTS }}
+
+ - name: Run Trivy vulnerability scanner
+ uses: aquasecurity/trivy-action@0.8.0
+ env:
+ TRIVY_USERNAME: ${{ env.REGISTRY_USERNAME }}
+ TRIVY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
+ with:
+ image-ref: "${{ env.REGISTRY_PATH }}:${{ env.DOCKERFILE_TAG }}"
+ format: 'json'
+ exit-code: '0'
+ output: results_trivy.json
+ ignore-unfixed: false
+ vuln-type: 'os,library'
+ severity: 'CRITICAL'
+
+ - name: Parse Trivy results
+ run: |
+ echo "| PkgName | InstalledVersion | Severity | Title | CVE URL |
+ | ------ | ------ | ------ | ------ | ------ |" > results_trivy.md
+ cat results_trivy.json | jq -r '.Results[].Vulnerabilities[] | [.PkgName, .InstalledVersion, .Severity, .Title, .PrimaryURL]| @tsv' |
+ awk '
+ BEGIN{ FS = "\t" } # Set field separator to tab
+ {
+ # Step 2: Replace all tab characters with pipe characters
+ gsub("\t", " | ", $0)
+
+ # Step 3: Print fields with Markdown table formatting
+ printf "| %s |\n", $0
+ }' >> results_trivy.md
+
+ - name: Export Trivy results
+ run: |
+ echo 'TRIVY_RESULTS<> $GITHUB_ENV
+ cat results_trivy.md >> $GITHUB_ENV
+ echo 'EOF' >> $GITHUB_ENV
+
+ - name: Show Trivy results
+ uses: actions-ecosystem/action-create-comment@v1
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ body: |
+ ## Trivy
+ ${{ env.TRIVY_RESULTS }}
+
+ - name: Move cache
+ if: always() # always run even if the previous step fails
+ run: |
+ rm -rf /tmp/.buildx-cache
+ mv /tmp/.buildx-cache-new /tmp/.buildx-cache
\ No newline at end of file
diff --git a/.github/workflows/pr_title.yml b/.github/workflows/pr_title.yml
new file mode 100644
index 0000000..ab62f75
--- /dev/null
+++ b/.github/workflows/pr_title.yml
@@ -0,0 +1,56 @@
+name: validate PR title
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - edited
+ - synchronize
+
+jobs:
+ lint_pr:
+ name: Validate PR title
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: amannn/action-semantic-pull-request@v5
+ id: lint_pr_title
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ types: |
+ feat
+ fix
+ perf
+ docs
+ style
+ refactor
+ test
+ ci
+ chore
+ build
+ requireScope: false
+ subjectPattern: ^(?![A-Z]).+$
+ subjectPatternError: |
+ The description "{subject}" found in the pull request title "{title}"
+ didn't match the configured pattern. Please ensure that the description
+ doesn't start with an uppercase character.
+ wip: true
+
+ - uses: marocchino/sticky-pull-request-comment@v2.3.1
+ if: always() && (steps.lint_pr_title.outputs.error_message != null)
+ with:
+ header: pr-title-lint-error
+ message: |
+ Our pull requests titles follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/#summary)
+
+ Details:
+
+ ```
+ ${{ steps.lint_pr_title.outputs.error_message }}
+ ```
+
+ - uses: marocchino/sticky-pull-request-comment@v2.3.1
+ if: ${{ steps.lint_pr_title.outputs.error_message == null }}
+ with:
+ header: pr-title-lint-error
+ delete: true
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..cb2cd1f
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,91 @@
+name: create new release with changelog
+
+on:
+ pull_request:
+ branches:
+ - 'main'
+ - 'master'
+ types: [closed]
+
+env:
+ DOCKERFILE_PATH: app
+ DOCKERFILE_TAG: ${{ github.event.pull_request.head.sha }}
+ REGISTRY_PATH: gcr.io/getindata-images-public/docker-atlantis
+ REGISTRY_TYPE: "gcr.io" # If not set then will default to Docker Hub
+ REGISTRY_USERNAME: _json_key
+
+jobs:
+ release:
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 100
+
+ - name: Check release label
+ id: release-label
+ uses: actions-ecosystem/action-release-label@v1.2.0
+ if: ${{ github.event.pull_request.merged == true }}
+
+ - name: Get latest tag
+ id: get-latest-tag
+ uses: actions-ecosystem/action-get-latest-tag@v1.6.0
+ if: ${{ steps.release-label.outputs.level != null }}
+
+ - name: Bump semantic version
+ id: bump-semver
+ uses: actions-ecosystem/action-bump-semver@v1
+ if: ${{ steps.release-label.outputs.level != null }}
+ with:
+ current_version: ${{ steps.get-latest-tag.outputs.tag }}
+ level: ${{ steps.release-label.outputs.level }}
+
+ - name: Tag release
+ id: tag-relese
+ uses: actions-ecosystem/action-push-tag@v1
+ if: ${{ steps.release-label.outputs.level != null }}
+ with:
+ tag: ${{ steps.bump-semver.outputs.new_version }}
+ message: "${{ steps.bump-semver.outputs.new_version }}: PR #${{ github.event.pull_request.number }} ${{ github.event.pull_request.title }}"
+
+ - name: Login to registry ${{ env.REGISTRY_TYPE }}
+ uses: docker/login-action@v2.1.0
+ if: ${{ steps.release-label.outputs.level != null }}
+ with:
+ registry: ${{ env.REGISTRY_TYPE }}
+ username: ${{ env.REGISTRY_USERNAME }}
+ password: ${{ secrets.REGISTRY_PASSWORD }}
+
+ - name: Tag final docker image
+ if: ${{ steps.release-label.outputs.level != null }}
+ run: |
+ docker pull ${{ env.REGISTRY_PATH }}:${{ github.event.pull_request.head.sha }}
+ docker tag ${{ env.REGISTRY_PATH }}:${{ github.event.pull_request.head.sha }} ${{ env.REGISTRY_PATH }}:${{ steps.bump-semver.outputs.new_version }}
+ docker push ${{ env.REGISTRY_PATH }}:${{ steps.bump-semver.outputs.new_version }}
+
+ - name: Generate new release with changelog
+ id: release-with-changelog
+ uses: fregante/release-with-changelog@v3.6.0
+ if: ${{ steps.bump-semver.outputs.new_version != null }}
+ with:
+ token: "${{ secrets.GITHUB_TOKEN }}"
+ exclude: '^chore|^docs|^ci|^build|^refactor|^style|^v?\d+\.\d+\.\d+'
+ tag: "${{ steps.bump-semver.outputs.new_version }}"
+ title: "Version ${{ steps.bump-semver.outputs.new_version }}"
+ commit-template: "- {title} ← {hash}"
+ skip-on-empty: true
+ template: |
+ ### Changelog
+
+ {commits}
+
+ {range}
+
+ - name: Comment PR
+ id: add-comment
+ uses: actions-ecosystem/action-create-comment@v1
+ if: ${{ steps.bump-semver.outputs.new_version != null }}
+ with:
+ github_token: "${{ secrets.GITHUB_TOKEN }}"
+ body: |
+ The new version [${{ steps.bump-semver.outputs.new_version }}](https://github.com/${{ github.repository }}/releases/tag/${{ steps.bump-semver.outputs.new_version }}) has been released :tada:
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..58c4bae
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+### Examples: https://github.com/github/gitignore
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..e96966e
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+hello@getindata.com.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..47c787b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,37 @@
+# Contributing
+
+When contributing to this repository, please first discuss the change you wish to make via issue,
+email, or any other method with the owners of this repository before making a change.
+
+Please note we have a code of conduct, please follow it in all your interactions with the project.
+
+## Pull Request Process
+
+1. Update the README.md with details of changes including new [example Dockerfile](./app) if appropriate.
+2. Once all outstanding comments and checklist items have been addressed, your contribution will be merged!
+3. Merged PRs will be included in the next release. The module maintainers take care of updating the CHANGELOG as they merge.
+
+## Checklists for contributions
+
+- [ ] Add [semantics prefix](#semantic-pull-requests) to your PR or Commits (at least one of your commit groups)
+- [ ] CI tests are passing
+- [ ] README.md has been updated after any changes.
+
+## Semantic Pull Requests
+
+To generate changelog, Pull Requests or Commits must have semantic and must follow [conventional specs](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional#rules) below:
+
+- `feat:` for new features
+- `fix:` for bug fixes
+- `perf:` for performance improvements
+- `docs:` for documentation and examples
+- `style`: for formatting changes
+- `refactor:` for refactoring production code
+- `test:` for adding missing tests
+- `ci:` for CI purpose
+- `chore:` for chores stuff
+- `build:` for updating build configuration
+
+`chore docs ci build refactor style` prefixes are skipped during changelog generation.
+
+They can be used for `chore: update changelog` commit message by example.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ddd800f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,100 @@
+# Docker Atlantis Image
+
+![Docker Image Version (latest by date)](https://img.shields.io/docker/v/getindata/docker-atlantis?arch=amd64&logo=docker&sort=date&style=for-the-badge)
+![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/getindata/docker-atlantis?logo=docker&sort=date&style=for-the-badge)
+![Docker Pulls](https://img.shields.io/docker/pulls/getindata/docker-atlantis?logo=docker&style=for-the-badge)
+
+
+![Docker](https://badgen.net/badge/icon/docker?icon=docker&label)
+![License](https://badgen.net/github/license/getindata/docker-atlantis/)
+![Release](https://badgen.net/github/release/getindata/docker-atlantis/)
+
+
+
+
+
We help companies turn their data into assets
+
+
+That custom `atlantis` docker image was created in order to install few helpful tools into "stock" solution:
+
+- `terragrunt-atlantis-config` - script that dynamically generates `atlantis.yaml` for terragrunt configurations
+- `checkov` - security and "best-practice" scanner (static code analysis)
+- `asdf` - version manager used to install needed packeges and versions
+- `terragrunt` (via asdf) - thin terraform wrapper
+- `terraform` (via asdf) - IaC automation
+- `helm` (via asdf) - k8s package manager used by `helm` terraform provider
+- `kubectl` (via asdf) - k8s CLI tool used by `kubernetes` terraform provider
+- `jq` (via asdf) - command line JSON parser
+- `yq` (via asdf) - command like YAML parser
+- `glab` (via asdf) - GitLab CLI client
+
+Files found in the repo:
+
+- `Dockerfile` is based on an official atlantis docker file () with some additional tweaks (asdf installation and configuration)
+- `docker-entrypoint.sh` is based on original file from atlantis repo with additional tweaks like invoking `bash` to run `atlantis` (due to `asdf` needs)
+- `check-gitlab-approvals.sh` is a script, intended to work around GitLab CE repository security limitations (CODEOWNERS, allowed approvers, etc.)
+- `approval-config-example.yaml` is a sample approver config used by `check-gitlab-approvers.sh` script
+
+---
+
+## Work around Free GitLab limitations
+
+Free versions of all major VCS systems (GitHub, GitLab, Bitbucket) introduce a set of limitations that should encourage it's users to pay for the service. One of those limitations is no `CODEOWNERS` support
+and no ability to configure "allowed approvers" in free repositories.
+
+Since Atlantis security depends on VCS level reviews (every approved MR/PR can be `atlantis apply`ed) it is crucial to somehow workaround this limitations.
+
+We use hosted GitLab as our primary VCS in GetInData, also self-hosted version of GitLab is very popular among our clients. We're also big fans of Atlantis and engineers in the same time - which took us to obvious conclusions -
+we should create a solution that allows our clients to use self-hosted GitLab CE and Atlantis securely.
+
+As a result we created a simple bash script [check-gitlab-approval.sh](check-gitlab-approvals.sh) that uses GitLab CLI called `glab` and few other popular bash tools to verify MR approvals. Script's configuration is stored in
+yaml format and can be mounted/saved into the image or passed via environment variable, example configuration can be found [here](approval-config-example.yaml).
+
+This script is intended to be used as one of `apply` steps in custom Atlantis workflow, example:
+
+```yaml
+workflows:
+ myworkflow:
+ plan:
+ steps:
+ - init
+ - plan
+ apply:
+ steps:
+ - run: check-gitlab-approvals.sh
+ - apply
+```
+
+During the execution, script checks if any of approving users are present in `approval-config.yaml` file. It fails (returns error) when none of approving users were allowed by configuration, blocking atlantis workflow (and apply step).
+
+---
+
+## BUILDING
+
+```bash
+docker build -t IMAGE_REPO/IMAGE_NAME:TAG .
+docker push docker push IMAGE_REPO/IMAGE_NAME:TAG
+```
+
+## IMAGES
+
+## USAGE
+
+## CONTRIBUTING
+
+Contributions are very welcomed!
+
+Start by reviewing [contribution guide](CONTRIBUTING.md) and our [code of conduct](CODE_OF_CONDUCT.md). After that, start coding and ship your changes by creating a new PR.
+
+## LICENSE
+
+Apache 2 Licensed. See [LICENSE](LICENSE) for full details.
+
+## AUTHORS
+
+
+
+
+
+
+Made with [contrib.rocks](https://contrib.rocks)
diff --git a/app/.dockerignore b/app/.dockerignore
new file mode 100644
index 0000000..7ff1b4e
--- /dev/null
+++ b/app/.dockerignore
@@ -0,0 +1,16 @@
+# git files
+.git
+.gitattributes
+.gitignore
+.cache
+
+# markdown files
+*.md
+LICENSE
+
+# docker files
+docker-compose.yml
+Dockerfile
+
+# Don't include example config files
+approval-config-example.yaml
\ No newline at end of file
diff --git a/app/Dockerfile b/app/Dockerfile
new file mode 100644
index 0000000..9800d14
--- /dev/null
+++ b/app/Dockerfile
@@ -0,0 +1,92 @@
+# syntax=docker/dockerfile:1
+ARG ATLANTIS_BASE_VERSION=2022.09.08
+# The runatlantis/atlantis-base is created by docker-base/Dockerfile.
+FROM ghcr.io/runatlantis/atlantis-base:${ATLANTIS_BASE_VERSION} AS base
+
+# Default tool versions installed in that image
+ARG ATLANTIS_VERSION=v0.19.8
+ARG ASDF_VERSION=v0.10.2
+ARG K8S_VERSION=1.25.0
+ARG HELM_VERSION=3.8.0
+ARG TF_VERSION=1.2.3
+ARG TG_VERSION=0.38.4
+ARG TG_ATLANTIS_VERSION=1.15.0
+ARG CONFTEST_VERSION=v0.34.0
+ARG GLAB_VERSION=1.22.0
+ARG JQ_VERSION=1.6
+ARG YQ_VERSION=4.9.8
+
+
+# Install awscli and checkov dependencies
+# RUN apk --no-cache add grep zlib-dev libffi-dev gcompat groff openssl3-dev python3 python3-dev py3-pip build-base gcc && \
+# pip3 --no-cache-dir install wheel && \
+# pip3 --no-cache-dir install checkov && \
+# pip3 cache purge && \
+# apk --no-cache del python3-dev build-base gcc
+
+# Download and install Atlantis
+RUN curl -LOs https://github.com/runatlantis/atlantis/releases/download/${ATLANTIS_VERSION}/atlantis_linux_amd64.zip && \
+ unzip atlantis_linux_amd64.zip -d /usr/bin && \
+ chmod a+x /usr/bin/atlantis && \
+ rm atlantis_linux_amd64.zip
+
+# Download and install terragrunt-atlantis-config
+RUN curl -LOs https://github.com/transcend-io/terragrunt-atlantis-config/releases/download/v${TG_ATLANTIS_VERSION}/terragrunt-atlantis-config_${TG_ATLANTIS_VERSION}_linux_amd64.tar.gz && \
+ tar xzf terragrunt-atlantis-config_${TG_ATLANTIS_VERSION}_linux_amd64.tar.gz && \
+ mv terragrunt-atlantis-config_${TG_ATLANTIS_VERSION}_linux_amd64/terragrunt-atlantis-config_${TG_ATLANTIS_VERSION}_linux_amd64 /usr/bin/terragrunt-atlantis-config && \
+ chmod a+x /usr/bin/terragrunt-atlantis-config && \
+ rm -rf terragrunt-atlantis-config_${TG_ATLANTIS_VERSION}_linux_amd64*
+
+# Download and install asdf, create .profile and source asdf inside
+RUN gosu atlantis bash -l -c " \
+ git clone --quiet https://github.com/asdf-vm/asdf.git /home/atlantis/.asdf --branch ${ASDF_VERSION} && \
+ echo '. /home/atlantis/.asdf/asdf.sh' >> /home/atlantis/.profile && \
+ chown atlantis.atlantis /home/atlantis/.profile && \
+ chmod u+rw /home/atlantis/.profile"
+
+# Install all needed plugins
+RUN gosu atlantis bash -l -c " \
+ asdf plugin-add kubectl && \
+ asdf plugin-add helm && \
+ asdf plugin-add terragrunt && \
+ asdf plugin-add terraform && \
+ asdf plugin-add conftest && \
+ asdf plugin-add glab && \
+ asdf plugin-add jq && \
+ asdf plugin-add yq"
+# Install default versions and define them globally
+RUN gosu atlantis bash -l -c " \
+ cd /home/atlantis/ && \
+ asdf install kubectl ${K8S_VERSION} && \
+ asdf install helm ${HELM_VERSION} && \
+ asdf install terraform ${TF_VERSION} && \
+ asdf install terragrunt ${TG_VERSION} && \
+ asdf install conftest ${CONFTEST_VERSION} && \
+ asdf install glab ${GLAB_VERSION} && \
+ asdf install jq ${JQ_VERSION} && \
+ asdf install yq ${YQ_VERSION} && \
+ asdf global kubectl ${K8S_VERSION} && \
+ asdf global helm ${HELM_VERSION} && \
+ asdf global terraform ${TF_VERSION} && \
+ asdf global terragrunt ${TG_VERSION} && \
+ asdf global conftest ${CONFTEST_VERSION} && \
+ asdf global glab ${GLAB_VERSION} && \
+ asdf global jq ${JQ_VERSION} && \
+ asdf global yq ${YQ_VERSION}"
+
+# Additional cleanup
+RUN rm -rf /tmp/*
+
+# Set atlantis login shell to bash
+RUN sed -i s#atlantis:/sbin/nologin#atlantis:/bin/bash#g /etc/passwd
+
+COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
+COPY check-gitlab-approvals.sh /usr/local/bin/check-gitlab-approvals.sh
+
+RUN chmod a+x /usr/local/bin/docker-entrypoint.sh && \
+ chmod a+x /usr/local/bin/check-gitlab-approvals.sh
+
+USER atlantis
+
+ENTRYPOINT ["docker-entrypoint.sh"]
+CMD ["server"]
diff --git a/app/check-gitlab-approvals.sh b/app/check-gitlab-approvals.sh
new file mode 100755
index 0000000..1cce37a
--- /dev/null
+++ b/app/check-gitlab-approvals.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+# Script checks if GitLab MR was approved by allowed user. It's intention is to work around
+# free GitLab limitations (no CODEOWNERS, required approvals).
+#
+# This script makes great use of yq, jq, glab commands and they should be installed
+# and availiable in $PATH priror script execution
+#
+# Approvers configuration can be set in number of ways:
+# - By pointing `APPROVAL_CONFIG_PATH` environment variable to proper APPROVAL_CONFIG yaml file
+# - By passing path to APPROVAL_CONFIG yaml file as the first input argument to this script
+# - By directly populating `APPROVAL_CONFIG` environment variable with configuration
+#
+# APPROVAL_CONFIG is a `yaml` file with list of approved GitLab usernames per project, example:
+# ---
+# repository:
+# REPOSITORY:
+# allowed_approvers:
+# - GITLAB_USERNAME
+# getindata/devops/dummy-test-project:
+# allowed_approvers:
+# - john.doe
+# getindata/devops/aws/gid-aws-terragrunt-platform:
+# allowed_approvers:
+# - jane.doe
+# - example_username
+#
+# We assume that env variables are populated correctly (according to Atlantis dockumentation)
+# and script is executed in proper custom workflow context:
+# - ATLANTIS_GITLAB_TOKEN
+# - HEAD_REPO_OWNER
+# - HEAD_REPO_NAME
+# - PULL_NUM
+
+# Get approval-config.yaml file path from: environment variable or 1st argument,
+# use default when nothing is set
+if [ $# -gt 0 ]; then
+ # if ARG is passed to script, use it
+ APPROVAL_CONFIG_PATH=${1}
+elif [ ! -v APPROVAL_CONFIG_PATH ] || [ -z APPROVAL_CONFIG_PATH ]; then
+ # if env is not set or is empty set default
+ APPROVAL_CONFIG_PATH="/atlantis-data/approval-config.yaml"
+fi
+
+# Variable needed for `glab`
+export GITLAB_TOKEN=${ATLANTIS_GITLAB_TOKEN}
+
+REPO_NAME="${HEAD_REPO_OWNER}/${HEAD_REPO_NAME}"
+
+# Declare arrays
+declare -a APPROVERS_ALLOWED
+declare -a APPROVERS_GITLAB
+declare -a RESULT
+
+# Get repository approvers configuration
+if [ -v APPROVAL_CONFIG ] && [ ! -z APPROVAL_CONFIG ]; then
+ # If env is set and not empty - read approvers configuration yaml directly from ENV
+ APPROVERS_ALLOWED=($(yq --null-input eval "env(APPROVAL_CONFIG)" | yq eval ".repository.$REPO_NAME.allowed_approvers.[]" - | sort))
+elif [ -f $APPROVAL_CONFIG_PATH ]; then
+ # If file passed through APPROVAL_CONFIG_PATH env exists, try to parse it
+ APPROVERS_ALLOWED=($(yq eval ".repository.$REPO_NAME.allowed_approvers.[]" ${APPROVAL_CONFIG_PATH} | sort))
+else
+ printf "GitLab approval configuration file not found in '%s' nor in \$APPROVAL_CONFIG - will not continue...\n" ${APPROVAL_CONFIG_PATH}
+ exit 1;
+fi
+
+# Get list of MR approvals from GitLab API
+APPROVERS_GITLAB=($(jq -rn --arg x "${REPO_NAME}" '$x|@uri' | xargs -i glab api projects/{}/merge_requests/$PULL_NUM/approvals | jq '.approved_by[].user.username' | tr -d \" | sort))
+
+# Find intersection between Allowed and Actual approvers of MR
+RESULT=($(comm -12 <(printf '%s\n' ${APPROVERS_ALLOWED[@]}) <(printf '%s\n' ${APPROVERS_GITLAB[@]})))
+
+if [ ${#RESULT[@]} -gt 0 ]; then
+ printf "MR approved correctly by [%s]\n" $(IFS=,; printf %s "${RESULT[*]}")
+elif [ ${#APPROVERS_ALLOWED[@]} -eq 0 ]; then
+ printf "Missing or bad configuration for '$REPO_NAME' repo in approval configuration - will not continue...\n"
+ exit 1;
+else
+ printf "Your MR has to be approved by at least one of those users [%s] to continue !!!\n" $(IFS=,; printf %s "${APPROVERS_ALLOWED[*]}")
+ exit 1;
+fi
diff --git a/app/docker-entrypoint.sh b/app/docker-entrypoint.sh
new file mode 100755
index 0000000..049f302
--- /dev/null
+++ b/app/docker-entrypoint.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/dumb-init /bin/sh
+set -e
+
+# Modified: https://github.com/hashicorp/docker-consul/blob/2c2873f9d619220d1eef0bc46ec78443f55a10b5/0.X/docker-entrypoint.sh
+
+# If the user is trying to run atlantis directly with some arguments, then
+# pass them to atlantis.
+if [ "${1:0:1}" = '-' ]; then
+ set -- atlantis "@"
+fi
+
+# If the user is running an atlantis subcommand (ex. server) then we want to prepend
+# atlantis as the first arg to exec. To detect if they're running a subcommand
+# we take the potential subcommand and run it through atlantis help {subcommand}.
+# If the output contains "atlantis subcommand" then we know it's a subcommand
+# since the help output contains that string. For anything else (ex. sh)
+# it won't contain that string.
+# NOTE: We use grep instead of the exit code since help always returns 0.
+if atlantis help "$1" 2>&1 | grep -q "atlantis $1"; then
+ # We can't use the return code to check for the existence of a subcommand, so
+ # we have to use grep to look for a pattern in the help output.
+ set -- bash --login -c "atlantis $@"
+fi
+
+# If the current uid running does not have a user create one in /etc/passwd
+if ! whoami &> /dev/null; then
+ if [ -w /etc/passwd ]; then
+ echo "${USER_NAME:-default}:x:$(id -u):0:${USER_NAME:-default} user:/home/atlantis:/sbin/bash" >> /etc/passwd
+ fi
+fi
+
+# If we're running as root and we're trying to execute atlantis then we use
+# gosu to step down from root and run as the atlantis user.
+# In OpenShift, containers are run as a random users so we don't need to use gosu.
+if [[ $(id -u) == 0 ]] && [[ "$1" = 'atlantis' ]]; then
+ # If requested, set the capability to bind to privileged ports before
+ # we drop to the non-root user. Note that this doesn't work with all
+ # storage drivers (it won't work with AUFS).
+ if [ ! -z ${ATLANTIS_ALLOW_PRIVILEGED_PORTS+x} ]; then
+ setcap "cap_net_bind_service=+ep" /bin/atlantis
+ fi
+
+ set -- gosu atlantis "$@"
+fi
+
+exec "$@"
\ No newline at end of file
diff --git a/approval-config-example.yaml b/approval-config-example.yaml
new file mode 100644
index 0000000..b6289bd
--- /dev/null
+++ b/approval-config-example.yaml
@@ -0,0 +1,8 @@
+repository:
+ owner/dummy-test-project:
+ allowed_approvers:
+ - john.doe
+ organization/test/test-repo:
+ allowed_approvers:
+ - jane.doe
+ - example.user