diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..26ac1f5 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,47 @@ +{ + "name": "devcontainer", + "postAttachCommand": ".devcontainer/hooks/post_attach.sh", + "remoteUser": "dev", + + "build": { + "dockerfile": "../src/Dockerfile", + "context": ".." + }, + + "customizations": { + "vscode": { + "settings": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.rulers": [80, 120], + "editor.wordBasedSuggestions": "off", + "explorer.excludeGitIgnore": true, + "extensions.ignoreRecommendations": true, + "git.enableCommitSigning": true, + "terminal.integrated.defaultProfile.linux": "bash", + "terminal.integrated.shellIntegration.decorationsEnabled": "never", + + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/usr/bin/bash" + } + } + }, + + "extensions": [ + "eamodio.gitlens", + "EditorConfig.EditorConfig", + "esbenp.prettier-vscode", + "jetmartin.bats", + "ms-azuretools.vscode-docker", + "ms-vscode.makefile-tools" + ] + } + }, + + "mounts": [ + "type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock", + "type=bind,consistency=cached,source=${localWorkspaceFolder}/.tmp/history,target=/home/dev/.history", + "type=bind,consistency=cached,source=${localEnv:HOME}/.aws,target=/home/dev/.aws" + ] +} diff --git a/.devcontainer/hooks/post_attach.sh b/.devcontainer/hooks/post_attach.sh new file mode 100755 index 0000000..ef2e957 --- /dev/null +++ b/.devcontainer/hooks/post_attach.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +direnv allow /workspaces/* + +sudo chown root:docker /var/run/docker.sock +sudo chmod g+w /var/run/docker.sock + +ls -d /workspaces/* | xargs git config --global --add safe.directory + +starship preset plain-text-symbols -o ~/.config/starship.toml +starship config container.disabled true diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e166442 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.gitkeep diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b56c6d5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{yml,yaml,json}] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..6b9f3f7 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +export PROJECT_AWS_ACCOUNT_ID="000000000000" +export PROJECT_BUILD_DATE="0000-00-00 00:00:00+00:00" +export PROJECT_NAME="devcontainer" diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 0000000..cf7445e --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,113 @@ +name: Pipeline + +on: + pull_request: + push: + tags: + - "v*" + workflow_dispatch: + +env: + DOCKER_BUILDKIT: 1 + PROJECT_DOCKER_PLATFORMS: ${{ vars.PROJECT_DOCKER_PLATFORMS }} + PROJECT_NAME: ${{ vars.PROJECT_NAME }} + +jobs: + lint: + name: Lint + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Prepare + uses: schubergphilis/prepare-action@v1 + with: + type: container + job: lint + + - name: Lint + run: | + make lint + + scan: + name: Scan + runs-on: ubuntu-22.04 + needs: lint + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Prepare + uses: schubergphilis/prepare-action@v1 + with: + type: container + job: scan + + - name: Scan + run: | + make scan + + pre-ci: + name: Pre-CI + runs-on: ubuntu-22.04 + needs: scan + outputs: + platforms: ${{ steps.platforms.outputs.value }} + steps: + - name: Get Platforms as JSON + id: platforms + run: | + echo "value=[\"$(echo $PROJECT_DOCKER_PLATFORMS | sed 's/,/\",\"/g')\"]" >> $GITHUB_OUTPUT + + ci: + name: CI + runs-on: ubuntu-22.04 + needs: pre-ci + strategy: + matrix: + platforms: ${{ fromJSON(needs.pre-ci.outputs.platforms) }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Prepare + uses: schubergphilis/prepare-action@v1 + with: + type: container + job: ci + + - name: Build + env: + PROJECT_DOCKER_PLATFORMS: ${{ matrix.platforms }} + run: | + make build + + - name: Test + env: + PROJECT_DOCKER_PLATFORMS: ${{ matrix.platforms }} + run: | + make test + + cd: + name: CD + runs-on: ubuntu-22.04 + needs: ci + environment: dev + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + permissions: + id-token: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Prepare + uses: schubergphilis/prepare-action@v1 + with: + type: container + job: cd + + - name: Release + run: | + make release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39d1b15 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# +# MACOS +# + +.apdisk +.AppleDB +.AppleDesktop +.AppleDouble +.com.apple.timemachine.donotpresent +.DocumentRevisions-V100 +.DS_Store +.fseventsd +.LSOverride +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +*.icloud +Icon +Network Trash Folder +Temporary Items + +# +# PROJECT +# + +.tmp/history/ +!.tmp/history/.gitkeep diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 0000000..a318a0e --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,7 @@ +ignored: + - DL3008 # Pin versions in apt get install + - DL3050 # Superfluous label(s) present + - DL3059 # Multiple consecutive `RUN` instructions +failure-threshold: style +format: tty +strict-labels: true diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e64769f --- /dev/null +++ b/Makefile @@ -0,0 +1,180 @@ +SHELL := /bin/bash + +# +# Directories +# + +ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +POLICIES_DIR := $(abspath ${ROOT_DIR}/policies) +SOURCE_DIR := $(abspath ${ROOT_DIR}/src) +TESTS_DIR := $(abspath ${ROOT_DIR}/tests) + +# +# Project: General +# + +PROJECT_BUILD_DATE ?= $(shell date --rfc-3339=seconds) +PROJECT_COMMIT ?= $(shell git rev-parse HEAD) +PROJECT_NAME ?= $(error PROJECT_NAME is not set) +PROJECT_VERSION ?= $(strip \ + $(if \ + $(shell git rev-list --tags --max-count=1), \ + $(shell git describe --tags `git rev-list --tags --max-count=1`), \ + $(shell git rev-parse --short HEAD) \ + ) \ +) + +# +# Project: AWS +# + +PROJECT_AWS_ACCOUNT_ID ?= $(error PROJECT_AWS_ACCOUNT_ID is not set) +PROJECT_AWS_REGION ?= eu-central-1 + +# +# Project: Docker +# + +PROJECT_DOCKER_BUILDER := builder-$(PROJECT_NAME) +PROJECT_DOCKER_PLATFORMS ?= linux/arm64,linux/amd64 + +# +# Project: ECR +# + +PROJECT_ECR_HOST ?= $(PROJECT_AWS_ACCOUNT_ID).dkr.ecr.$(PROJECT_AWS_REGION).amazonaws.com +PROJECT_ECR_REPO ?= $(PROJECT_NAME) + +# +# Image Arguments +# + +DEFAULT_LANG ?= C.UTF-8 +DEFAULT_USER_PRIMARY_GROUP ?= developers +DEFAULT_USER_SECONDARY_GROUPS ?= sudo,docker +DEFAULT_USER_SHELL ?= /bin/bash +DEFAULT_USER ?= dev + +# +# TARGETS +# + +.PHONY: all +all: lint scan build test + +.PHONY: build +build: + @for platform in `echo ${PROJECT_DOCKER_PLATFORMS} | tr ',' ' '`; do \ + arch="$$(echo $$platform | cut -d/ -f2)"; \ + echo "Building $(PROJECT_NAME):$(PROJECT_VERSION)-$$arch"; \ + docker build \ + --build-arg PROJECT_BUILD_DATE="$(PROJECT_BUILD_DATE)" \ + --build-arg PROJECT_COMMIT="$(PROJECT_COMMIT)" \ + --build-arg PROJECT_VERSION="$(PROJECT_VERSION)" \ + --build-arg DEFAULT_LANG="$(DEFAULT_LANG)" \ + --build-arg DEFAULT_USER_PRIMARY_GROUP="$(DEFAULT_USER_PRIMARY_GROUP)" \ + --build-arg DEFAULT_USER_SECONDARY_GROUPS="$(DEFAULT_USER_SECONDARY_GROUPS)" \ + --build-arg DEFAULT_USER_SHELL="$(DEFAULT_USER_SHELL)" \ + --build-arg DEFAULT_USER="$(DEFAULT_USER)" \ + --file "$(SOURCE_DIR)/Dockerfile" \ + --platform "$$platform" \ + --tag "$(PROJECT_NAME):$(PROJECT_VERSION)-$$arch" \ + .; \ + done + + +.PHONY: clean +clean: + @for platform in `echo ${PROJECT_DOCKER_PLATFORMS} | tr ',' ' '`; do \ + arch="$$(echo $$platform | cut -d/ -f2)"; \ + echo "Removing $(PROJECT_NAME):$(PROJECT_VERSION)-$$arch"; \ + docker images \ + --quiet \ + "$(PROJECT_NAME):$(PROJECT_VERSION)-$$arch" >/dev/null 2>&1 \ + && docker rmi \ + --force \ + "$(PROJECT_NAME):$(PROJECT_VERSION)-$$arch" >/dev/null 2>&1; \ + echo "Removing $(PROJECT_NAME)-test:$$arch"; \ + docker images \ + --quiet \ + "$(PROJECT_NAME)-test:$$arch" >/dev/null 2>&1 \ + && docker rmi \ + --force \ + "$(PROJECT_NAME)-test:$$arch" >/dev/null 2>&1; \ + done + +.PHONY: lint +lint: + @echo "Linting ${SOURCE_DIR}/Dockerfile" + @hadolint "${SOURCE_DIR}/Dockerfile" + @echo "Linting ${SOURCE_DIR}/Dockerfile.test" + @hadolint "${SOURCE_DIR}/Dockerfile.test" + +.PHONY: release +release: + @docker buildx inspect \ + --bootstrap \ + --builder "$(PROJECT_DOCKER_BUILDER)" >/dev/null 2>&1 \ + || docker buildx create \ + --bootstrap \ + --name "$(PROJECT_DOCKER_BUILDER)" \ + --platform "$(PROJECT_DOCKER_PLATFORMS)" \ + --use + + @aws ecr batch-delete-image \ + --repository-name "$(PROJECT_ECR_REPO)" \ + --image-ids imageTag="latest" + + @docker buildx build \ + --build-arg PROJECT_BUILD_DATE="$(PROJECT_BUILD_DATE)" \ + --build-arg PROJECT_COMMIT="$(PROJECT_COMMIT)" \ + --build-arg PROJECT_VERSION="$(PROJECT_VERSION)" \ + --build-arg DEFAULT_LANG="$(DEFAULT_LANG)" \ + --build-arg DEFAULT_USER_PRIMARY_GROUP="$(DEFAULT_USER_PRIMARY_GROUP)" \ + --build-arg DEFAULT_USER_SECONDARY_GROUPS="$(DEFAULT_USER_SECONDARY_GROUPS)" \ + --build-arg DEFAULT_USER_SHELL="$(DEFAULT_USER_SHELL)" \ + --build-arg DEFAULT_USER="$(DEFAULT_USER)" \ + --builder "$(PROJECT_DOCKER_BUILDER)" \ + --file "$(SOURCE_DIR)/Dockerfile" \ + --platform "$(PROJECT_DOCKER_PLATFORMS)" \ + --tag "$(PROJECT_ECR_HOST)/$(PROJECT_ECR_REPO):$(PROJECT_VERSION)" \ + --tag "$(PROJECT_ECR_HOST)/$(PROJECT_ECR_REPO):latest" \ + --push \ + . + +.PHONY: scan +scan: + @echo "Scanning $(SOURCE_DIR)/Dockerfile" + @checkov \ + --file "$(SOURCE_DIR)/Dockerfile" \ + --external-checks-dir "$(POLICIES_DIR)" \ + --framework dockerfile \ + --output cli + +.PHONY: reset +reset: clean + @echo "Removing builder $(PROJECT_DOCKER_BUILDER)" + @docker buildx inspect \ + --bootstrap \ + --builder "$(PROJECT_DOCKER_BUILDER)" >/dev/null 2>&1 \ + && docker buildx rm \ + --builder "$(PROJECT_DOCKER_BUILDER)" \ + || echo -n "" + +.PHONY: test +test: + @for platform in `echo ${PROJECT_DOCKER_PLATFORMS} | tr ',' ' '`; do \ + arch="$$(echo $$platform | cut -d/ -f2)"; \ + echo "Testing $(PROJECT_NAME)-test:$$arch"; \ + docker build \ + --build-arg PROJECT_NAME="$(PROJECT_NAME)" \ + --build-arg PROJECT_VERSION="$(PROJECT_VERSION)-$$arch" \ + --file "$(SOURCE_DIR)/Dockerfile.test" \ + --platform "$$platform" \ + --tag "$(PROJECT_NAME)-test:$$arch" \ + . \ + && docker run \ + --platform "$$platform" \ + --rm \ + "$(PROJECT_NAME)-test:$$arch"; \ + done diff --git a/README.md b/README.md index 845f65a..d09cee9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,21 @@ +[![Release Status](https://github.com/schubergphilis/devcontainer/actions/workflows/pipeline.yml/badge.svg)](https://github.com/schubergphilis/devcontainer/actions/workflows/pipeline.yml) + # devcontainer -A container image to be used as a remote development environment inside Visual Studio Code through the Remote Dev Container extension. + +This project defines a container image to be used as a development environment +inside [Visual Studio Code](https://code.visualstudio.com/) through the +[Remote Dev Container](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension. + +## License + +```text +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/policies/.gitkeep b/policies/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Dockerfile b/src/Dockerfile new file mode 100644 index 0000000..72e10b0 --- /dev/null +++ b/src/Dockerfile @@ -0,0 +1,245 @@ +#checkov:skip=CKV2_DOCKER_1:Ensure that sudo isn't used + +FROM ubuntu:22.04 + +# +# DOCKER BUILDKIT +# + +ARG BUILDARCH +ARG BUILDOS +ARG BUILDPLATFORM +ARG TARGETARCH +ARG TARGETOS +ARG TARGETPLATFORM + +# +# PROJECT +# + +ARG PROJECT_BUILD_DATE +ARG PROJECT_COMMIT +ARG PROJECT_VERSION + +# +# IMAGE +# + +ARG DEFAULT_LANG="C.UTF-8" +ARG DEFAULT_USER_PRIMARY_GROUP="developers" +ARG DEFAULT_USER_SECONDARY_GROUPS="sudo,docker" +ARG DEFAULT_USER_SHELL="/bin/bash" +ARG DEFAULT_USER="dev" + +# +# OS +# + +ENV VERSION_UBUNTU="22.04" +ENV VERSION_UBUNTU_NAME="jammy" + +SHELL [ "/bin/bash", "-o", "pipefail", "-c" ] + +RUN export DEBIAN_FRONTEND="noninteractive" \ + && apt-get update \ + && apt-get -y install --no-install-recommends \ + build-essential \ + ca-certificates \ + curl \ + direnv \ + gcc-aarch64-linux-gnu \ + git \ + git-extras \ + gnupg2 \ + gpg-agent \ + jq \ + libbz2-dev \ + libffi-dev \ + liblzma-dev \ + libncursesw5-dev \ + libreadline-dev \ + libsqlite3-dev \ + libssl-dev \ + libxml2-dev \ + libxmlsec1-dev \ + locales \ + lsb-release \ + make \ + pipx \ + pkg-config \ + python3 \ + python3-full \ + software-properties-common \ + ssh \ + sudo \ + tk-dev \ + unzip \ + vim \ + xz-utils \ + zip \ + zlib1g-dev \ + && apt-get autoremove -y \ + && apt-get clean autoclean -y \ + && rm -r /var/cache/* /var/lib/apt/lists/* + +RUN locale-gen "${DEFAULT_LANG}" \ + && update-locale LANG="${DEFAULT_LANG}" + +# +# INSTALL DOCKER +# + +ENV VERSION_DOCKER_CLI="25.0.0-1" +ENV VERSION_DOCKER_BUILDX_PLUGIN="0.12.1-1" +ENV VERSION_DOCKER_COMPOSE_PLUGIN="2.24.1-1" +ENV FILENAME_DOCKER_SUFFIX="ubuntu.${VERSION_UBUNTU}~${VERSION_UBUNTU_NAME}_${TARGETARCH}.deb" +ENV FILENAME_DOCKER_CLI="docker-ce-cli_${VERSION_DOCKER_CLI}~${FILENAME_DOCKER_SUFFIX}" +ENV FILENAME_DOCKER_BUILDX_PLUGIN="docker-buildx-plugin_${VERSION_DOCKER_BUILDX_PLUGIN}~${FILENAME_DOCKER_SUFFIX}" +ENV FILENAME_DOCKER_COMPOSE_PLUGIN="docker-compose-plugin_${VERSION_DOCKER_COMPOSE_PLUGIN}~${FILENAME_DOCKER_SUFFIX}" +ENV URL_DOCKER_BASE="https://download.docker.com/linux/ubuntu/dists/${VERSION_UBUNTU_NAME}/pool/stable/${TARGETARCH}" +ENV URL_DOCKER_CLI="${URL_DOCKER_BASE}/${FILENAME_DOCKER_CLI}" +ENV URL_DOCKER_BUILDX_PLUGIN="${URL_DOCKER_BASE}/${FILENAME_DOCKER_BUILDX_PLUGIN}" +ENV URL_DOCKER_COMPOSE_PLUGIN="${URL_DOCKER_BASE}/${FILENAME_DOCKER_COMPOSE_PLUGIN}" + +RUN curl -sSL -o "/tmp/${FILENAME_DOCKER_CLI}" "${URL_DOCKER_CLI}" \ + && curl -sSL -o "/tmp/${FILENAME_DOCKER_BUILDX_PLUGIN}" "${URL_DOCKER_BUILDX_PLUGIN}" \ + && curl -sSL -o "/tmp/${FILENAME_DOCKER_COMPOSE_PLUGIN}" "${URL_DOCKER_COMPOSE_PLUGIN}" \ + && dpkg -i "/tmp/${FILENAME_DOCKER_CLI}" \ + && dpkg -i "/tmp/${FILENAME_DOCKER_BUILDX_PLUGIN}" \ + && dpkg -i "/tmp/${FILENAME_DOCKER_COMPOSE_PLUGIN}" \ + && groupadd docker \ + && rm -f "/tmp/"*"${FILENAME_DOCKER_SUFFIX}" + +# +# INSTALL HADOLINT +# + +ENV VERSION_HADOLINT="2.12.0" +ENV URL_HADOLINT="https://github.com/hadolint/hadolint/releases/download/v${VERSION_HADOLINT}/hadolint-Linux-${TARGETARCH}" + +RUN curl -sSL -o /usr/local/bin/hadolint "${URL_HADOLINT}" \ + && chmod +x /usr/local/bin/hadolint + +# +# INSTALL GIT DELTA +# + +ENV VERSION_GIT_DELTA="0.16.5" +ENV FILENAME_GIT_DELTA="git-delta_${VERSION_GIT_DELTA}_${TARGETARCH}.deb" +ENV URL_GIT_DELTA_BASE="https://github.com/dandavison/delta/releases/download/${VERSION_GIT_DELTA}" +ENV URL_GIT_DELTA="${URL_GIT_DELTA_BASE}/${FILENAME_GIT_DELTA}" + +RUN curl -sSL --http1.1 -o "/tmp/${FILENAME_GIT_DELTA}" "${URL_GIT_DELTA}" \ + && dpkg -i "/tmp/${FILENAME_GIT_DELTA}" \ + && rm -f "/tmp/${FILENAME_GIT_DELTA}" + +# +# INSTALL STARSHIP +# + +RUN curl -fsSL https://starship.rs/install.sh | sh -s -- --yes + +# +# INSTALL AWSCLI +# + +RUN if [ "${TARGETARCH}" == "arm64" ]; then arch="aarch64"; elif [ "${TARGETARCH}" == "amd64" ]; then arch="x86_64"; else arch="${TARGETARCH}"; fi \ + && curl -vfsSL "https://awscli.amazonaws.com/awscli-exe-linux-${arch}.zip" -o "/tmp/awscliv2.zip" \ + && unzip "/tmp/awscliv2.zip" \ + && ./aws/install \ + && rm -rf "./aws" \ + && rm -f "/tmp/awscliv2.zip" + +# +# CREATE DEV USER +# + +RUN groupadd "${DEFAULT_USER_PRIMARY_GROUP}" \ + && useradd \ + -s "${DEFAULT_USER_SHELL}" \ + -g "${DEFAULT_USER_PRIMARY_GROUP}" \ + -G "${DEFAULT_USER_SECONDARY_GROUPS}" \ + -m "${DEFAULT_USER}" + +# +# CONFIGURE SUDO +# + +RUN echo \ + # CONTENT \ + "%${DEFAULT_USER_PRIMARY_GROUP} ALL=(ALL) NOPASSWD: ALL" \ + # END \ + >"/etc/sudoers.d/${DEFAULT_USER_PRIMARY_GROUP}" + +USER "${DEFAULT_USER}" + +ENV HOME="/home/${DEFAULT_USER}" +ENV LANG="${DEFAULT_LANG}" +ENV LANGUAGE="${DEFAULT_LANG}" +ENV LC_ALL="${DEFAULT_LANG}" +ENV PATH="${HOME}/.local/bin:${PATH}" +ENV PROMPT_COMMAND="history -a" +ENV HISTFILE="${HOME}/.history/.bash_history" + +# +# GENERAL +# + +RUN mkdir -p "${HOME}/.config" \ + && mkdir "${HOME}/.history" \ + && touch "${HOME}/.history/.bash_history" \ + && mkdir -p "${HOME}/.local/bin" + +# +# INSTALL TFENV +# + +ENV TFENV_VERSION="3.0.0" +ENV TFENV_ROOT="${HOME}/.tfenv" +ENV PATH="${TFENV_ROOT}/bin:${PATH}" + +RUN git clone --depth=1 --branch "v${TFENV_VERSION}" "https://github.com/tfutils/tfenv.git" "${TFENV_ROOT}" + +# +# INSTALL PYENV +# + +ENV PYENV_ROOT="${HOME}/.pyenv" +ENV PATH="${PYENV_ROOT}/bin:$PATH" + +RUN curl -fsSL https://pyenv.run | bash + +# +# OTHER TOOLS +# + +ENV VERSION_CHECKOV="3.2.16" +ENV VERSION_POETRY="1.7.1" +ENV VERSION_PRECOMMIT="3.6.0" + +# +# PYTHON +# + +RUN pipx install "checkov==${VERSION_CHECKOV}" \ + && pipx install "poetry==${VERSION_POETRY}" \ + && pipx install "pre-commit==${VERSION_PRECOMMIT}" + +# +# CONFIGURE SHELL +# + +RUN echo "eval \"\$(direnv hook bash)\"" >> "${HOME}/.bashrc" \ + && echo "eval \"\$(starship init bash)\"" >> "${HOME}/.bashrc" \ + && echo "eval \"\$(pyenv init --path)\"" >> "${HOME}/.bashrc" \ + && echo "eval \"\$(pyenv virtualenv-init -)\"" >> "${HOME}/.bashrc" + +# +# RUN +# + +HEALTHCHECK NONE + +ENTRYPOINT [ ] + +CMD [ ] diff --git a/src/Dockerfile.test b/src/Dockerfile.test new file mode 100644 index 0000000..f54c7f9 --- /dev/null +++ b/src/Dockerfile.test @@ -0,0 +1,30 @@ +# This is a container intended to be used to run tests which might require elevated permissions +# hadolint global ignore=DL3002 + +ARG PROJECT_NAME +ARG PROJECT_VERSION + +FROM ${PROJECT_NAME}:${PROJECT_VERSION} + +ARG BUILDARCH +ARG BUILDOS +ARG BUILDPLATFORM +ARG TARGETARCH +ARG TARGETOS +ARG TARGETPLATFORM + +USER root + +SHELL [ "/bin/bash", "-o", "pipefail", "-c" ] + +RUN export DEBIAN_FRONTEND="noninteractive" \ + && apt-get update \ + && apt-get -y install --no-install-recommends \ + bats \ + && apt-get autoremove -y \ + && apt-get clean autoclean -y \ + && rm -r /var/cache/* /var/lib/apt/lists/* + +COPY ./tests/* /tests/ + +ENTRYPOINT [ "/usr/bin/bats", "/tests/" ] diff --git a/src/filesystem/.gitkeep b/src/filesystem/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/unittest.bats b/tests/unittest.bats new file mode 100644 index 0000000..0cc17f8 --- /dev/null +++ b/tests/unittest.bats @@ -0,0 +1,142 @@ +#!/usr/bin/env bats + +# +# DOCKER +# + +@test "docker client is available" { + run which docker + [ "$status" -eq 0 ] +} + +@test "docker compose plugin is available" { + run docker compose version + [ "$status" -eq 0 ] +} + +@test "docker buildx plugin is available" { + run docker buildx version + [ "$status" -eq 0 ] +} + +# +# USER +# + +@test "dev user is created" { + run bash -c "grep -E '^dev:x:' /etc/passwd" + [ "$status" -eq 0 ] +} + +@test "developers is the primary group of dev user" { + run bash -c "id dev | grep -E 'gid=[0-9]+\(developers\)'" + [ "$status" -eq 0 ] +} + +@test "sudo is a secondary group of dev user" { + run bash -c "id dev | grep -E 'groups=.*[0-9]+\(sudo\)'" + [ "$status" -eq 0 ] +} + +@test "docker is a secondary group of dev user" { + run bash -c "id dev | grep -E 'groups=.*[0-9]+\(docker\)'" + [ "$status" -eq 0 ] +} + +# +# SYSTEM CONFIGURATION +# + +@test "sudo is configured" { + run ls /etc/sudoers.d/developers + [ "$status" -eq 0 ] +} + +# +# PACKAGES +# + +@test "checkov is installed" { + run which checkov + [ "$status" -eq 0 ] +} + +@test "curl is installed" { + run which curl + [ "$status" -eq 0 ] +} + +@test "delta is installed" { + run which delta + [ "$status" -eq 0 ] +} + +@test "direnv is installed" { + run which direnv + [ "$status" -eq 0 ] +} + +@test "git is installed" { + run which git + [ "$status" -eq 0 ] +} + +@test "gpg is installed" { + run which gpg + [ "$status" -eq 0 ] +} + +@test "hadolint is installed" { + run which hadolint + [ "$status" -eq 0 ] +} + +@test "make is installed" { + run which git + [ "$status" -eq 0 ] +} + +@test "pipx is installed" { + run which pipx + [ "$status" -eq 0 ] +} + +@test "poetry is installed" { + run which poetry + [ "$status" -eq 0 ] +} + +@test "pre-commit is installed" { + run which pre-commit + [ "$status" -eq 0 ] +} + +@test "pyenv is installed" { + run which pyenv + [ "$status" -eq 0 ] +} + +@test "starship is installed" { + run which starship + [ "$status" -eq 0 ] +} + +@test "sudo is installed" { + run which sudo + [ "$status" -eq 0 ] +} + +@test "tfenv is installed" { + run which tfenv + [ "$status" -eq 0 ] +} + +@test "unzip is installed" { + run which unzip + [ "$status" -eq 0 ] +} + +@test "zip is installed" { + run which unzip + [ "$status" -eq 0 ] +}