diff --git a/src/kubectx-kubens/devcontainer-feature.json b/src/kubectx-kubens/devcontainer-feature.json index 5797411d0..792bc344e 100644 --- a/src/kubectx-kubens/devcontainer-feature.json +++ b/src/kubectx-kubens/devcontainer-feature.json @@ -1,7 +1,7 @@ { "name": "Kubectx and Kubens (via Github Releases)", "id": "kubectx-kubens", - "version": "1.0.2", + "version": "1.0.3", "description": "kubectx is a tool to switch between contexts (clusters) on kubectl faster. kubens is a tool to switch between Kubernetes namespaces (and configure them for kubectl) easily.", "documentationURL": "https://github.com/devcontainers-contrib/features/tree/main/src/kubectx-kubens", "installsAfter": [ diff --git a/src/kubectx-kubens/install.sh b/src/kubectx-kubens/install.sh index 968cf76f1..30fc2b01b 100755 --- a/src/kubectx-kubens/install.sh +++ b/src/kubectx-kubens/install.sh @@ -1,72 +1,33 @@ #!/usr/bin/env bash -KUBCTX_KUBENS_VERSION="${VERSION:-"latest"}" +KUBECTX_KUBENS_VERSION="${VERSION:-"latest"}" set -e -# Clean up -rm -rf /var/lib/apt/lists/* +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.4.45" if [ "$(id -u)" -ne 0 ]; then echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' exit 1 fi -# Checks if packages are installed and installs them if not -check_packages() { - if ! dpkg -s "$@" >/dev/null 2>&1; then - if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then - echo "Running apt-get update..." - apt-get update -y - fi - apt-get -y install --no-install-recommends "$@" - fi -} - -# Figure out correct version of a three part version number is not passed -find_version_from_git_tags() { - local variable_name=$1 - local requested_version=${!variable_name} - if [ "${requested_version}" = "none" ]; then return; fi - local repository=$2 - local prefix=${3:-"tags/v"} - local separator=${4:-"."} - local last_part_optional=${5:-"false"} - if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then - local escaped_separator=${separator//./\\.} - local last_part - if [ "${last_part_optional}" = "true" ]; then - last_part="(${escaped_separator}[0-9]+)?" - else - last_part="${escaped_separator}[0-9]+" - fi - local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$" - local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)" - if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then - declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)" - else - set +e - declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")" - set -e - fi - fi - if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" >/dev/null 2>&1; then - echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2 - exit 1 - fi - echo "${variable_name}=${!variable_name}" -} - -find_version_from_git_tags KUBCTX_KUBENS_VERSION 'https://github.com/ahmetb/kubectx' - -check_packages ca-certificates curl - -curl -L -o /usr/local/bin/kubectx https://raw.githubusercontent.com/ahmetb/kubectx/v${KUBCTX_KUBENS_VERSION}/kubectx && - curl -L -o /usr/local/bin/kubens https://raw.githubusercontent.com/ahmetb/kubectx/v${KUBCTX_KUBENS_VERSION}/kubens && - chmod +x /usr/local/bin/kubectx && - chmod +x /usr/local/bin/kubens - -# Clean up -rm -rf /var/lib/apt/lists/* +$nanolayer_location \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-contrib/features/gh-release:1" \ + --option repo='ahmetb/kubectx' --option binaryNames='kubectx' --option version="$KUBECTX_KUBENS_VERSION" --option assetRegex="kubectx.*" + +$nanolayer_location \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-contrib/features/gh-release:1" \ + --option repo='ahmetb/kubectx' --option binaryNames='kubens' --option version="$KUBECTX_KUBENS_VERSION" --option assetRegex="kubens.*" echo "Done!" diff --git a/src/kubectx-kubens/library_scripts.sh b/src/kubectx-kubens/library_scripts.sh new file mode 100644 index 000000000..cff083da2 --- /dev/null +++ b/src/kubectx-kubens/library_scripts.sh @@ -0,0 +1,179 @@ +#!/bin/bash -i + + +clean_download() { + # The purpose of this function is to download a file with minimal impact on contaier 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/kubectx-kubens/test.sh b/test/kubectx-kubens/test.sh index 16a77b252..b8338c2e8 100755 --- a/test/kubectx-kubens/test.sh +++ b/test/kubectx-kubens/test.sh @@ -4,9 +4,9 @@ set -e source dev-container-features-test-lib -# we are simply checking existance for now. +# we are simply version for now. # full operability depends on the existance of kubectl. -check "kubectx existance" which kubectx -check "kubens existance" which kubens +check "kubectx version" kubectx --version +check "kubens version" kubens --version reportResults