From 7a2bdb8531643d2e356a214ad012b1ff9ea580e1 Mon Sep 17 00:00:00 2001 From: Kerolos Atallah Date: Wed, 5 Aug 2026 22:49:16 +0300 Subject: [PATCH] feat(outagedeck): add CLI feature --- src/outagedeck/README.md | 34 ++++++ src/outagedeck/devcontainer-feature.json | 21 ++++ src/outagedeck/install.sh | 19 ++++ src/outagedeck/library_scripts.sh | 131 +++++++++++++++++++++++ test/outagedeck/scenarios.json | 16 +++ test/outagedeck/test.sh | 9 ++ test/outagedeck/test_default.sh | 10 ++ test/outagedeck/test_specific_version.sh | 9 ++ 8 files changed, 249 insertions(+) create mode 100644 src/outagedeck/README.md create mode 100644 src/outagedeck/devcontainer-feature.json create mode 100755 src/outagedeck/install.sh create mode 100755 src/outagedeck/library_scripts.sh create mode 100644 test/outagedeck/scenarios.json create mode 100755 test/outagedeck/test.sh create mode 100755 test/outagedeck/test_default.sh create mode 100755 test/outagedeck/test_specific_version.sh diff --git a/src/outagedeck/README.md b/src/outagedeck/README.md new file mode 100644 index 00000000..5c0bbadc --- /dev/null +++ b/src/outagedeck/README.md @@ -0,0 +1,34 @@ +# OutageDeck CLI (via GitHub Releases) (outagedeck) + +Check the live status of 170+ cloud and SaaS providers from a terminal or CI +script. OutageDeck normalizes each vendor's official status feed; it does not +replace synthetic monitoring. + +## Example usage + +```json +"features": { + "ghcr.io/devcontainers-extra/features/outagedeck:1": {} +} +``` + +Then query one or more provider slugs: + +```bash +outagedeck status aws cloudflare github openai +``` + +For structured CI output and a nonzero exit code when a provider is down: + +```bash +outagedeck status --json --fail-on=outage aws github openai +``` + +Find more examples in the [OutageDeck CLI repository](https://github.com/outagedeck/cli) +or review the [API documentation](https://outagedeck.com/developers/api?utm_source=devcontainers-extra&utm_medium=feature&utm_campaign=devcontainer_feature). + +## Options + +| Options Id | Description | Type | Default Value | +|------------|----------------------------------------------|--------|---------------| +| version | Select the OutageDeck CLI version to install. | string | latest | diff --git a/src/outagedeck/devcontainer-feature.json b/src/outagedeck/devcontainer-feature.json new file mode 100644 index 00000000..c8d1c231 --- /dev/null +++ b/src/outagedeck/devcontainer-feature.json @@ -0,0 +1,21 @@ +{ + "id": "outagedeck", + "version": "1.0.0", + "name": "OutageDeck CLI (via GitHub Releases)", + "documentationURL": "https://github.com/devcontainers-extra/features/tree/main/src/outagedeck", + "description": "Check the live status of cloud and SaaS providers from a terminal or CI script.", + "options": { + "version": { + "default": "latest", + "description": "Select the OutageDeck CLI version to install.", + "proposals": [ + "latest", + "0.1.0" + ], + "type": "string" + } + }, + "installsAfter": [ + "ghcr.io/devcontainers-extra/features/gh-release" + ] +} diff --git a/src/outagedeck/install.sh b/src/outagedeck/install.sh new file mode 100755 index 00000000..fd472f85 --- /dev/null +++ b/src/outagedeck/install.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -e + +source ./library_scripts.sh + +# nanolayer is a CLI utility which keeps container layers as small as possible. +# Source: https://github.com/devcontainers-extra/nanolayer +ensure_nanolayer nanolayer_location "v0.5.6" + +"$nanolayer_location" \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-extra/features/gh-release:1" \ + --option repo='outagedeck/cli' \ + --option binaryNames='outagedeck' \ + --option version="$VERSION" + +echo 'Done!' diff --git a/src/outagedeck/library_scripts.sh b/src/outagedeck/library_scripts.sh new file mode 100755 index 00000000..98e0da82 --- /dev/null +++ b/src/outagedeck/library_scripts.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash + +clean_download() { + local url=$1 + local output_location=$2 + local tempdir + tempdir=$(mktemp -d) + local downloader_installed="" + + _apt_get_install() { + local apt_tempdir=$1 + cp -p -R /var/lib/apt/lists "$apt_tempdir" + apt-get update -y + apt-get -y install --no-install-recommends wget ca-certificates + } + + _apt_get_cleanup() { + local apt_tempdir=$1 + apt-get -y purge wget --auto-remove + rm -rf /var/lib/apt/lists/* + rm -r /var/lib/apt/lists + mv "$apt_tempdir/lists" /var/lib/apt/lists + } + + _apk_install() { + local apk_tempdir=$1 + cp -p -R /var/cache/apk "$apk_tempdir" + apk add --no-cache wget + } + + _apk_cleanup() { + apk del wget + } + + local downloader + if type curl >/dev/null 2>&1; then + downloader=curl + elif type wget >/dev/null 2>&1; then + downloader=wget + else + downloader="" + fi + + 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 + + if [ -n "$downloader_installed" ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_cleanup "$tempdir" + elif [ -x "/sbin/apk" ]; then + _apk_cleanup + fi + fi +} + +ensure_nanolayer() { + local variable_name=$1 + local required_version=$2 + + if [[ $required_version != v* ]]; then + required_version="v$required_version" + fi + + local resolved_nanolayer="" + if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION:-}" ]]; then + if [[ -z "${NANOLAYER_CLI_LOCATION:-}" ]]; then + if type nanolayer >/dev/null 2>&1; then + resolved_nanolayer=nanolayer + fi + elif [[ -f "$NANOLAYER_CLI_LOCATION" && -x "$NANOLAYER_CLI_LOCATION" ]]; then + resolved_nanolayer=$NANOLAYER_CLI_LOCATION + fi + + if [[ -n "$resolved_nanolayer" ]]; then + local current_version + current_version=$($resolved_nanolayer --version) + if [[ $current_version != v* ]]; then + current_version="v$current_version" + fi + if [[ $current_version != "$required_version" ]]; then + resolved_nanolayer="" + fi + fi + fi + + if [[ -z "$resolved_nanolayer" ]]; then + if [[ "$(uname -sm)" != "Linux x86_64" && "$(uname -sm)" != "Linux aarch64" ]]; then + echo "No nanolayer binary for $(uname -sm)" + exit 1 + fi + + local nanolayer_tempdir + nanolayer_tempdir=$(mktemp -d -t nanolayer-XXXXXXXXXX) + clean_up() { + local exit_code=$? + rm -rf "$nanolayer_tempdir" + exit "$exit_code" + } + trap clean_up EXIT + + local clib_type=gnu + if [ -x "/sbin/apk" ]; then + clib_type=musl + fi + local tar_filename="nanolayer-$(uname -m)-unknown-linux-$clib_type.tgz" + clean_download \ + "https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename" \ + "$nanolayer_tempdir/$tar_filename" + tar xfz "$nanolayer_tempdir/$tar_filename" -C "$nanolayer_tempdir" + chmod a+x "$nanolayer_tempdir/nanolayer" + resolved_nanolayer="$nanolayer_tempdir/nanolayer" + fi + + declare -g "$variable_name=$resolved_nanolayer" +} diff --git a/test/outagedeck/scenarios.json b/test/outagedeck/scenarios.json new file mode 100644 index 00000000..bb01553f --- /dev/null +++ b/test/outagedeck/scenarios.json @@ -0,0 +1,16 @@ +{ + "test_default": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "outagedeck": {} + } + }, + "test_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "outagedeck": { + "version": "0.1.0" + } + } + } +} diff --git a/test/outagedeck/test.sh b/test/outagedeck/test.sh new file mode 100755 index 00000000..b0120b3d --- /dev/null +++ b/test/outagedeck/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "outagedeck is installed" outagedeck --version + +reportResults diff --git a/test/outagedeck/test_default.sh b/test/outagedeck/test_default.sh new file mode 100755 index 00000000..2602aad9 --- /dev/null +++ b/test/outagedeck/test_default.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "outagedeck is installed" outagedeck --version +check "outagedeck status command is available" sh -c "outagedeck help | grep 'outagedeck status'" + +reportResults diff --git a/test/outagedeck/test_specific_version.sh b/test/outagedeck/test_specific_version.sh new file mode 100755 index 00000000..d8f1a049 --- /dev/null +++ b/test/outagedeck/test_specific_version.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "outagedeck version is 0.1.0" sh -c "outagedeck --version | grep '0.1.0'" + +reportResults