Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite the micromamba Feature #3

Merged
merged 41 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e3ebe3d
Add comments, questions, and tweaks
maresb Dec 10, 2022
5d5c75b
Refactor into utils
maresb Dec 11, 2022
69dcd38
Turn micromamba_destination into a constant
maresb Dec 11, 2022
36f8ea1
Create separate function for curl download
maresb Dec 11, 2022
38b3770
Don't reinstall in case micromamba exists
maresb Dec 11, 2022
1979e84
Add more tests
maresb Dec 11, 2022
41f6109
Move "set -e" to very top
maresb Dec 11, 2022
cfb2e57
Ensure execution from feature directory
maresb Dec 11, 2022
cb13df7
Add ensure_download_prerequisites
maresb Dec 11, 2022
13be8ee
Add detect_user function
maresb Dec 11, 2022
60c1dbd
Add strict channel priority
maresb Dec 11, 2022
675afa0
Add addCondaForge option
maresb Dec 11, 2022
6c997e3
Fix echo which came too early
maresb Dec 11, 2022
a16c3f0
Add more status messages
maresb Dec 11, 2022
09bcd94
Add micromamba_as_user function
maresb Dec 11, 2022
b1e85e0
Reorder definitions
maresb Dec 11, 2022
2455718
Improve skipping logic
maresb Dec 11, 2022
c180cd9
Initialize Bash and zsh
maresb Dec 11, 2022
9d5d732
More status messages
maresb Dec 11, 2022
d60ba30
Move USERNAME definition
maresb Dec 11, 2022
0b9a02b
Add MAMBA_ROOT_PREFIX and update PATH
maresb Dec 11, 2022
7e527d1
Initialize root prefix
maresb Dec 11, 2022
a3a2066
Define run_as_user
maresb Dec 11, 2022
0407f51
Activate the shell
maresb Dec 12, 2022
f6b3d2a
reinstall → allowReinstall
maresb Dec 12, 2022
f66e99d
Revert end message to "Done!"
maresb Dec 12, 2022
e2d7f04
Keep track of apt_cache_state
maresb Dec 12, 2022
91dd806
Add a few more status messages
maresb Dec 12, 2022
9766733
Add comment about apt-cache
maresb Dec 12, 2022
c41f055
Fix typo in test-reinstall
maresb Dec 12, 2022
b5dd079
Add and improve tests
maresb Dec 12, 2022
aae384b
Fix stringified boolean
maresb Dec 13, 2022
484d8c6
Update version description
maresb Dec 13, 2022
52ec95c
Move functions towards top
maresb Dec 13, 2022
028b737
Make channels a comma-separated list
maresb Dec 13, 2022
becd27d
Add more explanation about apt_cache_state
maresb Dec 14, 2022
97d9c70
Move require_running_root to unify apt functions
maresb Dec 14, 2022
70f7e8b
Add missing comment
maresb Dec 14, 2022
fe8432e
Adapt conda notice
maresb Dec 14, 2022
db14896
Update src/micromamba/NOTES.md
maresb Dec 14, 2022
5c58be8
Update src/micromamba/NOTES.md
eitsupi Dec 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .shellcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
external-sources=true
18 changes: 16 additions & 2 deletions src/micromamba/devcontainer-feature.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,26 @@
"type": "string",
"proposals": [
"latest",
"1"
"1.0.0"
eitsupi marked this conversation as resolved.
Show resolved Hide resolved
],
"default": "latest",
"description": "Select version of micromamba."
"description": "Exact version of Micromamba to install (must be X.Y.Z)"
},
"allowReinstall": {
"type": "boolean",
"default": "false",
maresb marked this conversation as resolved.
Show resolved Hide resolved
"description": "Reinstall in case Micromamba already exists"
},
"addCondaForge": {
"type": "boolean",
"default": false,
"description": "Add conda-forge channel to the config?"
eitsupi marked this conversation as resolved.
Show resolved Hide resolved
}
},
"containerEnv": {
"MAMBA_ROOT_PREFIX": "/opt/conda",
"PATH": "/opt/conda/bin:${PATH}"
},
"installsAfter": [
"ghcr.io/devcontainers/features/common-utils"
]
Expand Down
179 changes: 89 additions & 90 deletions src/micromamba/install.sh
Original file line number Diff line number Diff line change
@@ -1,101 +1,43 @@
#!/usr/bin/env bash

VERSION=${VERSION:-"latest"}

USERNAME=${USERNAME:-"automatic"}

set -e
FEATURE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "${FEATURE_DIR}"
maresb marked this conversation as resolved.
Show resolved Hide resolved

# Clean up
rm -rf /var/lib/apt/lists/*
# Options
VERSION=${VERSION:-"latest"}
ALLOW_REINSTALL=${ALLOWREINSTALL:-"false"}
ADD_CONDA_FORGE=$ADDCONDAFORGE

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
# Constants
MAMBA_ROOT_PREFIX="/opt/conda"
micromamba_destination="/usr/local/bin"

architecture="$(dpkg --print-architecture)"
if [ "${architecture}" != "amd64" ] && [ "${architecture}" != "arm64" ]; then
echo "(!) Architecture $architecture unsupported"
exit 1
fi
# shellcheck source=./utils.sh
source ./utils.sh

# Determine the appropriate non-root user
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
USERNAME=""
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do
if id -u "${CURRENT_USER}" >/dev/null 2>&1; then
USERNAME=${CURRENT_USER}
break
fi
done
if [ "${USERNAME}" = "" ]; then
USERNAME=root
fi
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} >/dev/null 2>&1; then
USERNAME=root
fi
# Note: The apt-cache is cleared on-demand.
# Thus we don't need here "rm -rf /var/lib/apt/lists/*".

apt_get_update() {
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
fi
}
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
detect_user USERNAME

# Checks if packages are installed and installs them if not
check_packages() {
if ! dpkg -s "$@" >/dev/null 2>&1; then
apt_get_update
apt-get -y install --no-install-recommends "$@"
fi
}
require_running_as_root

check_git() {
if [ ! -x "$(command -v git)" ]; then
check_packages git
fi
ensure_download_prerequisites() {
# This is the only place we need to use apt, so we can scope clean_up_apt tightly:
check_packages curl ca-certificates bzip2
}

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
check_git
check_packages ca-certificates
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}"
download_with_curl() {
local url=$1
local destination=$2
curl -sL "${url}" | tar -xj -C "${destination}" --strip-components=1 bin/micromamba
eitsupi marked this conversation as resolved.
Show resolved Hide resolved
}

install_micromamba() {
local version=$1
local destination=$2
local arch
local url
arch="$(uname -m)"
Expand All @@ -104,19 +46,76 @@ install_micromamba() {
fi
url="https://micro.mamba.pm/api/micromamba/linux-${arch}/${version}"

check_packages curl ca-certificates bzip2
echo "Downloading micromamba..."
curl -sL "${url}" | tar -xj -C /usr/local/bin/ --strip-components=1 bin/micromamba
echo "Installing prerequisites for downloading micromamba..."
ensure_download_prerequisites
echo "Downloading micromamba from ${url}..."
download_with_curl "${url}" "${destination}"
echo "Micromamba download complete."
}

run_as_user() {
su "${USERNAME}" "${@}"
}

micromamba_as_user() {
run_as_user bash -c "micromamba $*"
}

export DEBIAN_FRONTEND=noninteractive

# Soft version matching
find_version_from_git_tags VERSION "https://github.com/mamba-org/mamba" "tags/micromamba-"
ensure_path_for_login_shells

if [ "${ALLOW_REINSTALL}" = "false" ]; then
if type micromamba > /dev/null 2>&1; then
echo "Detected existing micromamba: $(micromamba --version)."
echo "The allowReinstall argument is false, so not overwriting."
skip_install="true"
fi
fi

if [ "${skip_install}" != "true" ]; then
install_micromamba "${VERSION}" "${micromamba_destination}"
echo "Micromamba executable installed."
fi

add_conda_group() {
if ! cat /etc/group | grep -e "^conda:" > /dev/null 2>&1; then
groupadd -r conda
fi
}

initialize_root_prefix() {
mkdir -p "${MAMBA_ROOT_PREFIX}/conda-meta"
touch "${MAMBA_ROOT_PREFIX}/conda-meta/history"
add_conda_group
usermod -a -G conda "${USERNAME}"
chown -R "${USERNAME}:conda" "${MAMBA_ROOT_PREFIX}"
chmod -R g+r+w "${MAMBA_ROOT_PREFIX}"
find "${MAMBA_ROOT_PREFIX}" -type d -print0 | xargs -n 1 -0 chmod g+s
}
maresb marked this conversation as resolved.
Show resolved Hide resolved

initialize_root_prefix

if [ "${ADD_CONDA_FORGE}" = "true" ]; then
echo "Appending 'conda-forge' to channels"
micromamba_as_user config append channels conda-forge
fi

echo "Setting channel_priority to strict"
micromamba_as_user config set channel_priority strict

echo "Initializing Bash shell"
micromamba_as_user shell init --shell=bash
su -c "if ! grep -q 'micromamba activate # added by micromamba devcontainer feature' ~/.bashrc; then echo 'micromamba activate # added by micromamba devcontainer feature' >> ~/.bashrc; fi" - "${USERNAME}"

if type zsh > /dev/null 2>&1; then
echo "Initializing zsh shell"
micromamba_as_user shell init --shell=zsh
su -c "if ! grep -q 'micromamba activate # added by micromamba devcontainer feature' ~/.zshrc; then echo 'micromamba activate # added by micromamba devcontainer feature' >> ~/.zshrc; fi" - "${USERNAME}"
fi

install_micromamba "${VERSION##*-}"
echo "Micromamba configured."

# Clean up
rm -rf /var/lib/apt/lists/*
clean_up_apt_if_updated

echo "Done!"
70 changes: 70 additions & 0 deletions src/micromamba/utils.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash

# Assume the apt cache is dirty
apt_cache_state="unaccessed"
# Other possible values are "updated" and "clean".

clean_up_apt() {
rm -rf /var/lib/apt/lists/*
apt_cache_state="clean"
}

clean_up_apt_if_updated() {
if [ "${apt_cache_state}" = "updated" ]; then
clean_up_apt
fi
}

require_running_as_root() {
local error_message="${1:-Script must be run as root. Use sudo, su, or add \"USER root\" to your Dockerfile before running this script.}"
if [ "$(id -u)" -ne 0 ]; then
echo -e "${error_message}"
exit 1
fi
}

apt_get_update() {
if [ "${apt_cache_state}" = "unaccessed" ]; then
clean_up_apt
fi
if [ "${apt_cache_state}" = "clean" ]; then
echo "Running apt-get update..."
apt-get update -y
apt_cache_state="updated"
fi
}

# Checks if packages are installed and installs them if not
check_packages() {
if ! dpkg -s "$@" >/dev/null 2>&1; then
apt_get_update
apt-get -y install --no-install-recommends "$@"
fi
}

# Source:
# <https://github.com/devcontainers/features/blob/7b009e661f13085629b19fc157b577916587f6bc/src/nix/utils.sh#L67-L83>
# If in automatic mode, determine if a user already exists, if not use root
detect_user() {
local user_variable_name=${1:-username}
local possible_users=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
if [ "${!user_variable_name}" = "auto" ] || [ "${!user_variable_name}" = "automatic" ]; then
declare -g ${user_variable_name}=""
for current_user in ${possible_users[@]}; do
if id -u "${current_user}" > /dev/null 2>&1; then
declare -g ${user_variable_name}="${current_user}"
break
fi
done
fi
if [ "${!user_variable_name}" = "" ] || [ "${!user_variable_name}" = "none" ] || ! id -u "${!user_variable_name}" > /dev/null 2>&1; then
declare -g ${user_variable_name}=root
fi
}

ensure_path_for_login_shells() {
# Ensure that login shells get the correct path if the user updated the PATH using ENV.
rm -f /etc/profile.d/00-restore-env.sh
echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
chmod +x /etc/profile.d/00-restore-env.sh
}
7 changes: 4 additions & 3 deletions test/micromamba/install-specific-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ set -e
# shellcheck source=/dev/null
source dev-container-features-test-lib

# Feature-specific tests
check "version" micromamba --version | grep "0.27.0"
specific_version() {
micromamba --version | grep "0.27.0"
}
check "specific version" specific_version

# Report result
reportResults
33 changes: 32 additions & 1 deletion test/micromamba/scenarios.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,39 @@
"image": "debian:stable-slim",
"features": {
"micromamba": {
"version": "0"
"version": "0.27.0"
}
}
},
"test-no-reinstall": {
"image": "mambaorg/micromamba:0.26.0",
"features": {
"micromamba": {
"version": "1.0.0"
}
}
},
"test-reinstall": {
"image": "mambaorg/micromamba:0.25.0",
"features": {
"micromamba": {
"version": "1.0.0",
"allowReinstall": true
}
}
},
"test-conda-forge": {
"image": "debian:stable-slim",
"features": {
"micromamba": {
"addCondaForge": true
}
}
},
"test-python-install": {
"image": "debian:stable-slim",
"features": {
"micromamba": {}
}
}
}
13 changes: 13 additions & 0 deletions test/micromamba/test-conda-forge.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

set -e

# shellcheck source=/dev/null
source dev-container-features-test-lib

conda_forge() {
micromamba config get channels | grep -q conda-forge
}
check "conda-forge" conda_forge

reportResults
13 changes: 13 additions & 0 deletions test/micromamba/test-no-reinstall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

set -e

# shellcheck source=/dev/null
source dev-container-features-test-lib

test_no_reinstall() {
micromamba --version | grep "0.26.0"
}
check "test-no-reinstall" test_no_reinstall

reportResults
Loading