Skip to content

Commit c92fa01

Browse files
committed
fix(homebrew-package): fix tests and unarchive
1 parent 8a08851 commit c92fa01

File tree

10 files changed

+332
-22
lines changed

10 files changed

+332
-22
lines changed

archive/test/homebrew-package/test_git_based_version.sh

Lines changed: 0 additions & 11 deletions
This file was deleted.
File renamed without changes.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "Homebrew Package",
3+
"id": "homebrew-package",
4+
"version": "1.0.7",
5+
"description": "Installs a Homebrew package.",
6+
"documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/homebrew-package",
7+
"installsAfter": [
8+
"ghcr.io/meaningful-ooo/devcontainer-features/homebrew:2"
9+
],
10+
"options": {
11+
"package": {
12+
"type": "string",
13+
"proposals": [
14+
"typescript",
15+
"vtop",
16+
"fkill-cli"
17+
],
18+
"default": "",
19+
"description": "Select the Homebrew package to install."
20+
},
21+
"version": {
22+
"type": "string",
23+
"proposals": [
24+
"latest"
25+
],
26+
"default": "latest",
27+
"description": "Select the version of the Homebrew package to install."
28+
},
29+
"installation_flags": {
30+
"type": "string",
31+
"proposals": [
32+
"--ignore-dependencies"
33+
],
34+
"default": "",
35+
"description": "Additional installation flags. These would be used as extra arguments to the brew command (`brew install <installation_flags> <package>@<version>`)"
36+
}
37+
}
38+
}

src/homebrew-package/install.sh

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env bash
2+
set -ex
3+
4+
source ./library_scripts.sh
5+
6+
PACKAGE=${PACKAGE:-""}
7+
VERSION=${VERSION:-"latest"}
8+
INSTALLATION_FLAGS=${INSTALLATION_FLAGS:-""}
9+
10+
if [ -z "$PACKAGE" ]; then
11+
echo -e "'package' variable is empty, skipping"
12+
exit 0
13+
fi
14+
15+
if [ "$(id -u)" -ne 0 ]; then
16+
echo -e 'Script must be run as
17+
root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
18+
exit 1
19+
fi
20+
21+
check_packages() {
22+
if ! dpkg -s "$@" >/dev/null 2>&1; then
23+
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
24+
echo "Running apt-get update..."
25+
apt-get update -y
26+
fi
27+
apt-get -y install --no-install-recommends "$@"
28+
fi
29+
}
30+
31+
ensure_curl() {
32+
if ! type curl >/dev/null 2>&1; then
33+
apt-get update -y && apt-get -y install --no-install-recommends curl ca-certificates
34+
fi
35+
}
36+
37+
install_via_homebrew() {
38+
package=$1
39+
version=$2
40+
installation_flags=$3
41+
42+
# install Homebrew if does not exists
43+
if ! type brew >/dev/null 2>&1; then
44+
echo "Installing Homebrew..."
45+
46+
# nanolayer is a cli utility which keeps container layers as small as possible
47+
# source code: https://github.com/devcontainers-extra/nanolayer
48+
# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations,
49+
# and if missing - will download a temporary copy that automatically get deleted at the end
50+
# of the script
51+
ensure_nanolayer nanolayer_location "v0.4.29"
52+
53+
$nanolayer_location \
54+
install \
55+
devcontainer-feature \
56+
"ghcr.io/meaningful-ooo/devcontainer-features/homebrew:2.0.4" \
57+
--option shallow_clone='true' --option update="true"
58+
source /etc/profile.d/nanolayer-homebrew.sh
59+
fi
60+
61+
if [ "$version" = "latest" ]; then
62+
package_full="$package"
63+
else
64+
package_full="${package}@${version}"
65+
fi
66+
# Solves CVE-2022-24767 mitigation in Git >2.35.2
67+
# For more information: https://github.blog/2022-04-12-git-security-vulnerability-announced/
68+
git config --system --add safe.directory "$(brew --prefix)/Homebrew/Library/Taps/homebrew/homebrew-core"
69+
70+
su - "$_REMOTE_USER" <<EOF
71+
set -e
72+
73+
brew_safe_install() {
74+
local installation_flags=$1
75+
local package_full=$2
76+
77+
# The reason for "--overwrite" flag is to not fail when a similarly
78+
# named binary is already linked
79+
brew install $installation_flags --overwrite "$package_full" --only-dependencies
80+
81+
# The reason we first installing dependencies and only then the main
82+
# package is that some packages are big enough to reach the linux
83+
# open file limit. While normally this limit can be changed, the current
84+
# devcontainer feature building phase run unprivileged and therfore
85+
# cannot change the hard nofile limit from host machine during feature
86+
# build time.
87+
brew install $installation_flags --overwrite "$package_full"
88+
}
89+
90+
91+
if brew desc --eval-all --formulae "$package_full"; then
92+
# If a version is exists then install it the regular way
93+
94+
brew_safe_install $installation_flags "$package_full"
95+
else
96+
# unshallow and extract as last resort
97+
echo "Unshallowing homebrew-core. This could take a while."
98+
git -C "$(brew --prefix)/Homebrew/Library/Taps/homebrew/homebrew-core" fetch --unshallow
99+
brew extract --force --version="$version" "$package" homebrew/cask
100+
101+
brew_safe_install $installation_flags "$package_full"
102+
103+
# attempt to remove tap in order to save disk space
104+
set +e
105+
brew untap homebrew/cask --force
106+
set -e
107+
fi
108+
109+
brew link --overwrite --force "$package_full"
110+
EOF
111+
}
112+
113+
install_via_homebrew "$PACKAGE" "$VERSION" "$INSTALLATION_FLAGS"
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/bin/bash -i
2+
3+
4+
clean_download() {
5+
# The purpose of this function is to download a file with minimal impact on container layer size
6+
# this means if no valid downloader is found (curl or wget) then we install a downloader (currently wget) in a
7+
# temporary manner, and making sure to
8+
# 1. uninstall the downloader at the return of the function
9+
# 2. revert back any changes to the package installer database/cache (for example apt-get lists)
10+
# The above steps will minimize the leftovers being created while installing the downloader
11+
# Supported distros:
12+
# debian/ubuntu/alpine
13+
14+
url=$1
15+
output_location=$2
16+
tempdir=$(mktemp -d)
17+
downloader_installed=""
18+
19+
function _apt_get_install() {
20+
tempdir=$1
21+
22+
# copy current state of apt list - in order to revert back later (minimize contianer layer size)
23+
cp -p -R /var/lib/apt/lists $tempdir
24+
apt-get update -y
25+
apt-get -y install --no-install-recommends wget ca-certificates
26+
}
27+
28+
function _apt_get_cleanup() {
29+
tempdir=$1
30+
31+
echo "removing wget"
32+
apt-get -y purge wget --auto-remove
33+
34+
echo "revert back apt lists"
35+
rm -rf /var/lib/apt/lists/*
36+
rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists
37+
}
38+
39+
function _apk_install() {
40+
tempdir=$1
41+
# copy current state of apk cache - in order to revert back later (minimize contianer layer size)
42+
cp -p -R /var/cache/apk $tempdir
43+
44+
apk add --no-cache wget
45+
}
46+
47+
function _apk_cleanup() {
48+
tempdir=$1
49+
50+
echo "removing wget"
51+
apk del wget
52+
}
53+
# try to use either wget or curl if one of them already installer
54+
if type curl >/dev/null 2>&1; then
55+
downloader=curl
56+
elif type wget >/dev/null 2>&1; then
57+
downloader=wget
58+
else
59+
downloader=""
60+
fi
61+
62+
# in case none of them is installed, install wget temporarly
63+
if [ -z $downloader ] ; then
64+
if [ -x "/usr/bin/apt-get" ] ; then
65+
_apt_get_install $tempdir
66+
elif [ -x "/sbin/apk" ] ; then
67+
_apk_install $tempdir
68+
else
69+
echo "distro not supported"
70+
exit 1
71+
fi
72+
downloader="wget"
73+
downloader_installed="true"
74+
fi
75+
76+
if [ $downloader = "wget" ] ; then
77+
wget -q $url -O $output_location
78+
else
79+
curl -sfL $url -o $output_location
80+
fi
81+
82+
# NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because
83+
# alpine lack bash, and RETURN is not a valid signal under sh shell
84+
if ! [ -z $downloader_installed ] ; then
85+
if [ -x "/usr/bin/apt-get" ] ; then
86+
_apt_get_cleanup $tempdir
87+
elif [ -x "/sbin/apk" ] ; then
88+
_apk_cleanup $tempdir
89+
else
90+
echo "distro not supported"
91+
exit 1
92+
fi
93+
fi
94+
95+
}
96+
97+
98+
ensure_nanolayer() {
99+
# Ensure existance of the nanolayer cli program
100+
local variable_name=$1
101+
102+
local required_version=$2
103+
# normalize version
104+
if ! [[ $required_version == v* ]]; then
105+
required_version=v$required_version
106+
fi
107+
108+
local nanolayer_location=""
109+
110+
# If possible - try to use an already installed nanolayer
111+
if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]]; then
112+
if [[ -z "${NANOLAYER_CLI_LOCATION}" ]]; then
113+
if type nanolayer >/dev/null 2>&1; then
114+
echo "Found a pre-existing nanolayer in PATH"
115+
nanolayer_location=nanolayer
116+
fi
117+
elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ] ; then
118+
nanolayer_location=${NANOLAYER_CLI_LOCATION}
119+
echo "Found a pre-existing nanolayer which were given in env variable: $nanolayer_location"
120+
fi
121+
122+
# make sure its of the required version
123+
if ! [[ -z "${nanolayer_location}" ]]; then
124+
local current_version
125+
current_version=$($nanolayer_location --version)
126+
if ! [[ $current_version == v* ]]; then
127+
current_version=v$current_version
128+
fi
129+
130+
if ! [ $current_version == $required_version ]; then
131+
echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)"
132+
nanolayer_location=""
133+
fi
134+
fi
135+
136+
fi
137+
138+
# If not previuse installation found, download it temporarly and delete at the end of the script
139+
if [[ -z "${nanolayer_location}" ]]; then
140+
141+
if [ "$(uname -sm)" == "Linux x86_64" ] || [ "$(uname -sm)" == "Linux aarch64" ]; then
142+
tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX)
143+
144+
clean_up () {
145+
ARG=$?
146+
rm -rf $tmp_dir
147+
exit $ARG
148+
}
149+
trap clean_up EXIT
150+
151+
152+
if [ -x "/sbin/apk" ] ; then
153+
clib_type=musl
154+
else
155+
clib_type=gnu
156+
fi
157+
158+
tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz
159+
160+
# clean download will minimize leftover in case a downloaderlike wget or curl need to be installed
161+
clean_download https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename
162+
163+
tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir"
164+
chmod a+x $tmp_dir/nanolayer
165+
nanolayer_location=$tmp_dir/nanolayer
166+
167+
168+
else
169+
echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)"
170+
exit 1
171+
fi
172+
fi
173+
174+
# Expose outside the resolved location
175+
declare -g ${variable_name}=$nanolayer_location
176+
177+
}
178+
179+

archive/test/homebrew-package/scenarios.json renamed to test/homebrew-package/scenarios.json

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,11 @@
2121
"image": "mcr.microsoft.com/devcontainers/base:debian",
2222
"features": {
2323
"homebrew-package": {
24-
"version": "14",
24+
"version": "22",
2525
"package": "node"
2626
}
2727
}
2828
},
29-
"test_git_based_version": {
30-
"image": "mcr.microsoft.com/devcontainers/base:debian",
31-
"features": {
32-
"homebrew-package": {
33-
"version": "7.80.0",
34-
"package": "curl"
35-
}
36-
}
37-
},
3829
"test_universal": {
3930
"image": "mcr.microsoft.com/devcontainers/universal:2",
4031
"features": {
File renamed without changes.
File renamed without changes.

archive/test/homebrew-package/test_specific_version.sh renamed to test/homebrew-package/test_specific_version.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ source dev-container-features-test-lib
66

77
check "type node" type node
88

9-
check "node version is 14" node --version | grep "v14.*"
9+
check "node version is 22" sh -c "node --version | grep 'v22.*'"
1010

1111
reportResults
File renamed without changes.

0 commit comments

Comments
 (0)