diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 575dd0b..db8db1c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,6 +20,7 @@ jobs: - infisical-cli - mailpit - terraform-backend-git + - yamlfix baseImage: - debian:latest - ubuntu:latest diff --git a/README.md b/README.md index 0fe4357..4953042 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Please take a closer look at the detailed instructions for the individual featur - [Infisical CLI](src/infisical-cli) - [Mailpit](src/mailpit) - [Terraform State management using Git](src/terraform-backend-git) +- [yamlfix](src/yamlfix) ## Repo and Feature Structure @@ -35,6 +36,9 @@ Each Feature has its own sub-folder, containing at least a `devcontainer-feature │ ├── terraform-backend-git │ │ ├── devcontainer-feature.json │ │ └── install.sh +│ ├── yamlfix +│ │ ├── devcontainer-feature.json +│ │ └── install.sh | ├── ... │ │ ├── devcontainer-feature.json │ │ └── install.sh @@ -57,6 +61,9 @@ Each Feature has its own sub-folder, containing at least a `devcontainer-feature │ ├── terraform-backend-git │ │ ├── scenarios.json │ │ └── test.sh +│ ├── yamlfix +│ │ ├── scenarios.json +│ │ └── test.sh | ├── ... │ │ └── test.sh ... diff --git a/src/yamlfix/devcontainer-feature.json b/src/yamlfix/devcontainer-feature.json new file mode 100644 index 0000000..2350ec6 --- /dev/null +++ b/src/yamlfix/devcontainer-feature.json @@ -0,0 +1,21 @@ +{ + "id": "yamlfix", + "version": "1.0.0", + "name": "yamlfix (via pipx)", + "documentationURL": "https://github.com/skriptfabrik/devcontainer-features/tree/main/src/yamlfix", + "description": "yamlfix is a simple opinionated yaml formatter that keeps your comments.", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest" + ], + "default": "latest", + "description": "Select or enter a yamlfix version." + } + }, + "installsAfter": [ + "ghcr.io/devcontainers-extra/features/pipx-package", + "ghcr.io/devcontainers/features/python" + ] +} diff --git a/src/yamlfix/install.sh b/src/yamlfix/install.sh new file mode 100644 index 0000000..023c260 --- /dev/null +++ b/src/yamlfix/install.sh @@ -0,0 +1,177 @@ +#!/bin/sh + +set -e + +# The purpose of this function is to download a file with minimal impact on container layer size +# this means if no valid downloader is found (curl or wget) then we install a downloader (currently wget) in a +# temporary manner, and making sure to +# 1. uninstall the downloader at the return of the function +# 2. revert back any changes to the package installer database/cache (for example apt-get lists) +# The above steps will minimize the leftovers being created while installing the downloader +# Supported distros: +# debian/ubuntu/alpine +clean_download() { + url=$1 + output_location=$2 + tempdir=$(mktemp -d) + downloader_installed="" + + _apt_get_install() { + tempdir=$1 + + # copy current state of apt list - in order to revert back later (minimize container layer size) + cp -p -R /var/lib/apt/lists $tempdir + apt-get update -y + apt-get -y install --no-install-recommends wget ca-certificates + } + + _apt_get_cleanup() { + tempdir=$1 + + echo "removing wget" + apt-get -y purge wget --auto-remove + + echo "revert back apt lists" + rm -rf /var/lib/apt/lists/* + rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists + } + + _apk_install() { + tempdir=$1 + + # copy current state of apk cache - in order to revert back later (minimize container layer size) + cp -p -R /var/cache/apk $tempdir + apk add --no-cache wget + } + + _apk_cleanup() { + tempdir=$1 + + echo "removing wget" + apk del wget + } + + # try to use either wget or curl if one of them already installed + if type curl >/dev/null 2>&1; then + downloader=curl + elif type wget >/dev/null 2>&1; then + downloader=wget + else + downloader="" + fi + + # in case none of them is installed, install wget temporarily + if [ -z $downloader ] ; then + if [ -x "/usr/bin/apt-get" ] ; then + _apt_get_install $tempdir + elif [ -x "/sbin/apk" ] ; then + _apk_install $tempdir + else + echo "distro not supported" + exit 1 + fi + downloader="wget" + downloader_installed="true" + fi + + if [ $downloader = "wget" ] ; then + wget -q $url -O $output_location + else + curl -sfL $url -o $output_location + fi + + # NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because + # alpine lack bash, and RETURN is not a valid signal under sh shell + if ! [ -z $downloader_installed ] ; then + if [ -x "/usr/bin/apt-get" ] ; then + _apt_get_cleanup $tempdir + elif [ -x "/sbin/apk" ] ; then + _apk_cleanup $tempdir + else + echo "distro not supported" + exit 1 + fi + fi +} + +# Ensure existence of the nanolayer cli program +ensure_nanolayer() { + local variable_name=$1 + local required_version=$2 + local __nanolayer_location="" + + # If possible - try to use an already installed nanolayer + if [ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]; then + if [ -z "${NANOLAYER_CLI_LOCATION}" ]; then + if type nanolayer >/dev/null 2>&1; then + echo "Found a pre-existing nanolayer in PATH" + __nanolayer_location=nanolayer + fi + elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ] ; then + __nanolayer_location=${NANOLAYER_CLI_LOCATION} + echo "Found a pre-existing nanolayer which were given in env variable: $__nanolayer_location" + fi + + # make sure its of the required version + if ! [ -z "${__nanolayer_location}" ]; then + local current_version + current_version=$($__nanolayer_location --version) + + + if ! [ $current_version == $required_version ]; then + echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)" + __nanolayer_location="" + fi + fi + fi + + # If not previous installation found, download it temporarly and delete at the end of the script + if [ -z "${__nanolayer_location}" ]; then + if [ "$(uname -sm)" = 'Linux x86_64' ] || [ "$(uname -sm)" = "Linux aarch64" ]; then + tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX) + + clean_up () { + ARG=$? + rm -rf $tmp_dir + exit $ARG + } + trap clean_up EXIT + + if [ -x "/sbin/apk" ] ; then + clib_type=musl + else + clib_type=gnu + fi + + tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz + + # clean download will minimize leftover in case a downloaderlike wget or curl need to be installed + clean_download https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename + + tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir" + chmod a+x $tmp_dir/nanolayer + __nanolayer_location=$tmp_dir/nanolayer + else + echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)" + exit 1 + fi + fi + + # Expose outside the resolved location + export ${variable_name}=$__nanolayer_location +} + +# nanolayer is a cli utility which keeps container layers as small as possible +# source code: https://github.com/devcontainers-extra/nanolayer +# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations, +# and if missing - will download a temporary copy that automatically get deleted at the end +# of the script +ensure_nanolayer nanolayer_location "v0.5.0" + +$nanolayer_location \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-extra/features/pipx-package:1.1.8" \ + --option package='yamlfix' --option version="$VERSION" + +echo 'Done!' diff --git a/test/_global/all_the_clis.sh b/test/_global/all_the_clis.sh index 377942a..e778c2e 100644 --- a/test/_global/all_the_clis.sh +++ b/test/_global/all_the_clis.sh @@ -15,6 +15,7 @@ check "check for hcloud" hcloud version check "check for infisical" infisical --version check "check for mailpit" mailpit version check "check for terraform-backend-git" terraform-backend-git version +check "check for yamlfix" yamlfix --version # Report results # If any of the checks above exited with a non-zero exit code, the test will fail. diff --git a/test/_global/scenarios.json b/test/_global/scenarios.json index 63f6694..a468215 100644 --- a/test/_global/scenarios.json +++ b/test/_global/scenarios.json @@ -6,7 +6,8 @@ "hcloud-cli": {}, "infisical-cli": {}, "mailpit": {}, - "terraform-backend-git": {} + "terraform-backend-git": {}, + "yamlfix": {} } } } diff --git a/test/yamlfix/debian.sh b/test/yamlfix/debian.sh new file mode 100644 index 0000000..eaa202c --- /dev/null +++ b/test/yamlfix/debian.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +# See https://github.com/devcontainers/cli/blob/HEAD/docs/features/test.md#dev-container-features-test-lib +# Provides the 'check' and 'reportResults' commands. +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release + +# Scenario-specific tests +# The 'check' command comes from the dev-container-features-test-lib. Syntax is... +# check