From 5481c44f78aeef291e228759c1e685b25c5daf85 Mon Sep 17 00:00:00 2001
From: Tom Plant <tom@tplant.com.au>
Date: Tue, 15 Oct 2024 08:32:53 +0000
Subject: [PATCH 1/2] feat: add Namespace CLI via Github Releases

Signed-off-by: Tom Plant <tom@tplant.com.au>
---
 src/namespace-cli/README.md                 |  17 ++
 src/namespace-cli/devcontainer-feature.json |  20 +++
 src/namespace-cli/install.sh                |  21 +++
 src/namespace-cli/library_scripts.sh        | 173 ++++++++++++++++++++
 test/namespace-cli/scenarios.json           |   8 +
 test/namespace-cli/test.sh                  |  10 ++
 6 files changed, 249 insertions(+)
 create mode 100644 src/namespace-cli/README.md
 create mode 100644 src/namespace-cli/devcontainer-feature.json
 create mode 100644 src/namespace-cli/install.sh
 create mode 100644 src/namespace-cli/library_scripts.sh
 create mode 100644 test/namespace-cli/scenarios.json
 create mode 100644 test/namespace-cli/test.sh

diff --git a/src/namespace-cli/README.md b/src/namespace-cli/README.md
new file mode 100644
index 000000000..7c31f030b
--- /dev/null
+++ b/src/namespace-cli/README.md
@@ -0,0 +1,17 @@
+# Namespace CLI (via Github Releases)
+
+The CLI for [Namespace](https://namespace.so).
+
+## Example DevContainer Usage
+
+```json
+"features": {
+    "ghcr.io/devcontainers-extra/features/namespace-cli:1": {}
+}
+```
+
+## Options
+
+| Options Id | Description | Type | Default Value |
+|-----|-----|-----|-----|
+| version | Select the version to install. | string | latest |
diff --git a/src/namespace-cli/devcontainer-feature.json b/src/namespace-cli/devcontainer-feature.json
new file mode 100644
index 000000000..09cec1df8
--- /dev/null
+++ b/src/namespace-cli/devcontainer-feature.json
@@ -0,0 +1,20 @@
+{
+    "id": "namespace-cli",
+    "version": "1.0.0",
+    "name": "Namespace CLI (via Github Releases)",
+    "documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/namespace-cli",
+    "description": "The CLI for https://namespace.so",
+    "options": {
+        "version": {
+            "default": "latest",
+            "description": "Select the version to install.",
+            "proposals": [
+                "latest"
+            ],
+            "type": "string"
+        }
+    },
+    "installsAfter": [
+        "ghcr.io/devcontainers-extra/features/gh-release"
+    ]
+}
\ No newline at end of file
diff --git a/src/namespace-cli/install.sh b/src/namespace-cli/install.sh
new file mode 100644
index 000000000..0ce26c200
--- /dev/null
+++ b/src/namespace-cli/install.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+set -e
+
+source ./library_scripts.sh
+
+# nanolayer is a cli utility which keeps container layers as small as possible
+# source code: https://github.com/devcontainers-contrib/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.6"
+
+# Example nanolayer installation via devcontainer-feature
+$nanolayer_location \
+    install \
+    devcontainer-feature \
+    "ghcr.io/devcontainers-extra/features/gh-release:1" \
+    --option repo='namespacelabs/foundation' --option binaryNames='nsc,docker-credential-nsc' --option version="$VERSION" --option libName='docker-credential-nsc'
+
+echo 'Done!'
diff --git a/src/namespace-cli/library_scripts.sh b/src/namespace-cli/library_scripts.sh
new file mode 100644
index 000000000..496144c47
--- /dev/null
+++ b/src/namespace-cli/library_scripts.sh
@@ -0,0 +1,173 @@
+#!/usr/bin/env bash
+
+clean_download() {
+    # 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
+
+    url=$1
+    output_location=$2
+    tempdir=$(mktemp -d)
+    downloader_installed=""
+
+    function _apt_get_install() {
+        tempdir=$1
+
+        # copy current state of apt list - in order to revert back later (minimize contianer layer size)
+        cp -p -R /var/lib/apt/lists $tempdir
+        apt-get update -y
+        apt-get -y install --no-install-recommends wget ca-certificates
+    }
+
+    function _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
+    }
+
+    function _apk_install() {
+        tempdir=$1
+        # copy current state of apk cache - in order to revert back later (minimize contianer layer size)
+        cp -p -R /var/cache/apk $tempdir
+
+        apk add --no-cache wget
+    }
+
+    function _apk_cleanup() {
+        tempdir=$1
+
+        echo "removing wget"
+        apk del wget
+    }
+    # try to use either wget or curl if one of them already installer
+    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 temporarly
+    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_nanolayer() {
+    # Ensure existance of the nanolayer cli program
+    local variable_name=$1
+
+    local required_version=$2
+    # normalize version
+    if ! [[ $required_version == v* ]]; then
+        required_version=v$required_version
+    fi
+
+    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 == v* ]]; then
+                current_version=v$current_version
+            fi
+
+            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 previuse 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-contrib/cli/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
+    declare -g ${variable_name}=$nanolayer_location
+
+}
diff --git a/test/namespace-cli/scenarios.json b/test/namespace-cli/scenarios.json
new file mode 100644
index 000000000..79bd13623
--- /dev/null
+++ b/test/namespace-cli/scenarios.json
@@ -0,0 +1,8 @@
+{
+  "test": {
+      "image": "mcr.microsoft.com/devcontainers/base:debian",
+      "features": {
+          "namespace-cli": {}
+      }
+  }
+}
\ No newline at end of file
diff --git a/test/namespace-cli/test.sh b/test/namespace-cli/test.sh
new file mode 100644
index 000000000..1f4c5cee7
--- /dev/null
+++ b/test/namespace-cli/test.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+set -e
+
+source dev-container-features-test-lib
+
+check "nsc version" nsc version
+check "docker-credential-nsc --help" docker-credential-nsc --help
+
+reportResults

From 80e575f4a32eeddee269081f05906db6b2f713d9 Mon Sep 17 00:00:00 2001
From: Tom Plant <tom@tplant.com.au>
Date: Sat, 19 Oct 2024 02:21:11 +0000
Subject: [PATCH 2/2] fix: namespace-cli add version test

Signed-off-by: Tom Plant <tom@tplant.com.au>
---
 test/namespace-cli/scenarios.json           | 14 +++++++++++---
 test/namespace-cli/test.sh                  |  4 ++--
 test/namespace-cli/test_specific_version.sh | 11 +++++++++++
 3 files changed, 24 insertions(+), 5 deletions(-)
 create mode 100644 test/namespace-cli/test_specific_version.sh

diff --git a/test/namespace-cli/scenarios.json b/test/namespace-cli/scenarios.json
index 79bd13623..54688441a 100644
--- a/test/namespace-cli/scenarios.json
+++ b/test/namespace-cli/scenarios.json
@@ -1,8 +1,16 @@
 {
   "test": {
-      "image": "mcr.microsoft.com/devcontainers/base:debian",
-      "features": {
-          "namespace-cli": {}
+    "image": "mcr.microsoft.com/devcontainers/base:debian",
+    "features": {
+      "namespace-cli": {}
+    }
+  },
+  "test_specific_version": {
+    "image": "mcr.microsoft.com/devcontainers/base:debian",
+    "features": {
+      "namespace-cli": {
+        "version": "v0.0.393"
       }
+    }
   }
 }
\ No newline at end of file
diff --git a/test/namespace-cli/test.sh b/test/namespace-cli/test.sh
index 1f4c5cee7..bf05e8c6c 100644
--- a/test/namespace-cli/test.sh
+++ b/test/namespace-cli/test.sh
@@ -4,7 +4,7 @@ set -e
 
 source dev-container-features-test-lib
 
-check "nsc version" nsc version
-check "docker-credential-nsc --help" docker-credential-nsc --help
+check "namespace-cli is installed" nsc version
+check "namespace-cli docker credential helper is installed" docker-credential-nsc --help
 
 reportResults
diff --git a/test/namespace-cli/test_specific_version.sh b/test/namespace-cli/test_specific_version.sh
new file mode 100644
index 000000000..e35848959
--- /dev/null
+++ b/test/namespace-cli/test_specific_version.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+set -e
+
+source dev-container-features-test-lib
+
+check "namespace-cli version is equal to v0.0.393" sh -c "nsc version | grep 'v0.0.393'"
+# docker-credential-nsc doesn't have a version indicator as of v0.0.393
+# check "namespace-cli docker credential helper version is equal to v0.0.393" sh -c "docker-credential-nsc --help | grep 'v0.0.393'"
+
+reportResults
\ No newline at end of file