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

feat: Add yq to act and custom #117

Merged
merged 6 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
19 changes: 19 additions & 0 deletions linux/ubuntu/scripts/act.sh
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,25 @@ for ver in "${NODE[@]}"; do
"${NODEPATH}"/bin/npm -v
done

case "$(uname -m)" in
'aarch64')
scripts=(
yq
)
;;
'x86_64')
scripts=(
yq
)
;;
*) exit 1 ;;
esac

for SCRIPT in "${scripts[@]}"; do
printf "\n\t🧨 Executing %s.sh 🧨\t\n" "${SCRIPT}"
"/imagegeneration/installers/${SCRIPT}.sh"
done
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this breaks building on any arch other than aarch64/x86_64

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Especially a problem, because the act base image is built for armhf, aarch64 and x86_64.

The flavour images are not built for armhf and usually don't work on any arch other than aarch64 (rust image not built due to cpu intensive compiling in qemu)/x86_64.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good point, I looked at the wrong matrix. The yq script already supports arm/v7.
I'll have a look at updating the cases.

Does this approach otherwise look OK?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the missing arch


printf "\n\t🐋 Cleaning image 🐋\t\n"
apt-get clean
rm -rf /var/cache/* /var/log/* /var/lib/apt/lists/* /tmp/* || echo 'Failed to delete directories'
Expand Down
4 changes: 3 additions & 1 deletion linux/ubuntu/scripts/custom.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ case "$(uname -m)" in
go
js
dotnet
yq
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you want to install yq twice in the custom image?

act-latest is the base of custom-latest, js-latest, go-latest and so on

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. That's just me being unfamiliar in the codebase 😂

)
;;
'x86_64')
'x86_64')
scripts=(
basic
gh
Expand All @@ -53,6 +54,7 @@ case "$(uname -m)" in
rust
vcpkg
dotnet
yq
)
;;
*) exit 1 ;;
Expand Down
225 changes: 184 additions & 41 deletions linux/ubuntu/scripts/helpers/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,52 @@
## File: install.sh
## Desc: Helper functions for installing tools
################################################################################
# source: https://github.com/actions/runner-images/blob/5d6938f680075d63fa71f8aa70990866cd12884b/images/linux/scripts/helpers/install.sh

download_with_retries() {
# Due to restrictions of bash functions, positional arguments are used here.
# In case if you using latest argument NAME, you should also set value to all previous parameters.
# Example: download_with_retries $ANDROID_SDK_URL "." "android_sdk.zip"
local URL="$1"
local DEST="${2:-.}"
local NAME="${3:-${URL##*/}}"
local COMPRESSED="${4:-}"

if [[ $COMPRESSED == "compressed" ]]; then
local COMMAND="curl $URL -4 -sL --compressed -o '$DEST/$NAME' -w '%{http_code}'"
else
local COMMAND="curl $URL -4 -sL -o '$DEST/$NAME' -w '%{http_code}'"
fi

echo "Downloading '$URL' to '${DEST}/${NAME}'..."
retries=20
interval=30
while [ $retries -gt 0 ]; do
((retries--))
# Temporary disable exit on error to retry on non-zero exit code
set +e
http_code=$(eval "$COMMAND")
exit_code=$?
if [ "$http_code" -eq 200 ] && [ $exit_code -eq 0 ]; then
echo "Download completed"
return 0
# Due to restrictions of bash functions, positional arguments are used here.
# In case if you using latest argument NAME, you should also set value to all previous parameters.
# Example: download_with_retries $ANDROID_SDK_URL "." "android_sdk.zip"
local URL="$1"
local DEST="${2:-.}"
local NAME="${3:-${URL##*/}}"
local COMPRESSED="$4"

if [[ $COMPRESSED == "compressed" ]]; then
local COMMAND="curl $URL -4 -sL --compressed -o '$DEST/$NAME' -w '%{http_code}'"
else
echo "Error — Either HTTP response code for '$URL' is wrong - '$http_code' or exit code is not 0 - '$exit_code'. Waiting $interval seconds before the next attempt, $retries attempts left"
sleep 30
local COMMAND="curl $URL -4 -sL -o '$DEST/$NAME' -w '%{http_code}'"
fi
# Enable exit on error back
set -e
done

echo "Could not download $URL"
return 1
# Save current errexit state and disable it to prevent unexpected exit on error
if echo $SHELLOPTS | grep '\(^\|:\)errexit\(:\|$\)' > /dev/null;
then
local ERR_EXIT_ENABLED=true
else
local ERR_EXIT_ENABLED=false
fi
set +e
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you add ERR_EXIT_ENABLED if it is ignored in the first iteration due to unconditional set +e?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes to this script aren't my own. They're from the upstream repository.

Primary reason I found the sync was necessary was because checksum validation code was added and used for the yq download.

I'll have to defer to the blame upstream for your questions though :]

Here it's actions/runner-images#8352

Copy link
Contributor Author

@Beanow Beanow Nov 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear, I'm not defending the decisions upstream. I don't have an opinion on the suggestions you made here.

But I think there's maintenance tradeoffs between how closely you want to track the other repo. So I feel the decision to diverge and track our own patches here should be up to the maintainers and not me :]

Feel free to edit, or request changes.


echo "Downloading '$URL' to '${DEST}/${NAME}'..."
retries=20
interval=30
while [ $retries -gt 0 ]; do
((retries--))
test "$ERR_EXIT_ENABLED" = true && set +e
http_code=$(eval $COMMAND)
exit_code=$?
test "$ERR_EXIT_ENABLED" = true && set -e
if [ $http_code -eq 200 ] && [ $exit_code -eq 0 ]; then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove quotes for http_code? We don't control the output of curl, it may be able to inject stuff.

For example exit_code can only be a number, because that comes from bash itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

echo "Download completed"
return 0
else
echo "Error — Either HTTP response code for '$URL' is wrong - '$http_code' or exit code is not 0 - '$exit_code'. Waiting $interval seconds before the next attempt, $retries attempts left"
sleep $interval
fi
done

echo "Could not download $URL"
return 1
}

## Use dpkg to figure out if a package has already been installed
Expand All @@ -49,21 +57,156 @@ download_with_retries() {
## echo "packageName is not installed!"
## fi
IsPackageInstalled() {
dpkg -S "$1" &>/dev/null
dpkg -S $1 &> /dev/null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary to remove quotes?

Removing quotes in bash is dangerous.

Example:

listargs() { while [[ -n "$1" ]]; do echo $1; shift; done };
IsPackageInstalled() { listargs -S $1; } ;
IsPackageInstalled "hello or";

argument hello or ends up beeing interpreted by bash.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems upstream this was never quoted.

I found this, but is just a move. The code before was initial commit.
actions/runner-images@a7ee8ab

}

verlte() {
sortedVersion=$(echo -e "$1\n$2" | sort -V | head -n1)
[ "$1" = "$sortedVersion" ]
sortedVersion=$(echo -e "$1\n$2" | sort -V | head -n1)
[ "$1" = "$sortedVersion" ]
}

get_toolset_path() {
echo "/imagegeneration/toolset.json"
echo "/imagegeneration/toolset.json"
}

get_toolset_value() {
local toolset_path
toolset_path=$(get_toolset_path)
local query=$1
jq -r "$query" "$toolset_path"
local toolset_path=$(get_toolset_path)
local query=$1
echo "$(jq -r "$query" $toolset_path)"
}

get_github_package_download_url() {
local REPO_ORG=$1
local FILTER=$2
local VERSION=$3
local SEARCH_IN_COUNT="100"

json=$(curl -fsSL "https://api.github.com/repos/${REPO_ORG}/releases?per_page=${SEARCH_IN_COUNT}")

if [ -n "$VERSION" ]; then
tagName=$(echo $json | jq -r '.[] | select(.prerelease==false).tag_name' | sort --unique --version-sort | egrep -v ".*-[a-z]|beta" | egrep "\w*${VERSION}" | tail -1)
else
tagName=$(echo $json | jq -r '.[] | select((.prerelease==false) and (.assets | length > 0)).tag_name' | sort --unique --version-sort | egrep -v ".*-[a-z]|beta" | tail -1)
fi

downloadUrl=$(echo $json | jq -r ".[] | select(.tag_name==\"${tagName}\").assets[].browser_download_url | select(${FILTER})" | head -n 1)
if [ -z "$downloadUrl" ]; then
echo "Failed to parse a download url for the '${tagName}' tag using '${FILTER}' filter"
exit 1
fi
echo $downloadUrl
}

get_github_package_hash() {
local repo_owner=$1
local repo_name=$2
local file_name=$3
local url=$4
local version=${5:-"latest"}
local prerelease=${6:-false}
local delimiter=${7:-'|'}
local word_number=${8:-2}

if [[ -z "$file_name" ]]; then
echo "File name is not specified."
exit 1
fi

if [[ -n "$url" ]]; then
release_url="$url"
else
if [ "$version" == "latest" ]; then
release_url="https://api.github.com/repos/${repo_owner}/${repo_name}/releases/latest"
else
json=$(curl -fsSL "https://api.github.com/repos/${repo_owner}/${repo_name}/releases?per_page=100")
tags=$(echo "$json" | jq -r --arg prerelease "$prerelease" '.[] | select(.prerelease == ($prerelease | test("true"; "i"))) | .tag_name')
tag=$(echo "$tags" | grep -o "$version")
if [[ "$(echo "$tag" | wc -l)" -gt 1 ]]; then
echo "Multiple tags found matching the version $version. Please specify a more specific version."
exit 1
fi
if [[ -z "$tag" ]]; then
echo "Failed to get a tag name for version $version."
exit 1
fi
release_url="https://api.github.com/repos/${repo_owner}/${repo_name}/releases/tags/$tag"
fi
fi

body=$(curl -fsSL "$release_url" | jq -r '.body' | tr -d '`')
matching_line=$(echo "$body" | grep "$file_name")
if [[ "$(echo "$matching_line" | wc -l)" -gt 1 ]]; then
echo "Multiple lines found included the file $file_name. Please specify a more specific file name."
exit 1
fi
if [[ -z "$matching_line" ]]; then
echo "File name '$file_name' not found in release body."
exit 1
fi

result=$(echo "$matching_line" | cut -d "$delimiter" -f "$word_number" | tr -d -c '[:alnum:]')
if [[ -z "$result" ]]; then
echo "Empty result. Check parameters delimiter and/or word_number for the matching line."
exit 1
fi

echo "$result"
}

get_hash_from_remote_file() {
local url=$1
local keywords=("$2" "$3")
local delimiter=${4:-' '}
local word_number=${5:-1}

if [[ -z "${keywords[0]}" || -z "$url" ]]; then
echo "File name and/or URL is not specified."
exit 1
fi

matching_line=$(curl -fsSL "$url" | sed 's/ */ /g' | tr -d '`')
for keyword in "${keywords[@]}"; do
matching_line=$(echo "$matching_line" | grep "$keyword")
done

if [[ "$(echo "$matching_line" | wc -l)" -gt 1 ]]; then
echo "Multiple lines found including the words: ${keywords[*]}. Please use a more specific filter."
exit 1
fi

if [[ -z "$matching_line" ]]; then
echo "Keywords (${keywords[*]}) not found in the file with hashes."
exit 1
fi

result=$(echo "$matching_line" | cut -d "$delimiter" -f "$word_number" | tr -d -c '[:alnum:]')
if [[ ${#result} -ne 64 && ${#result} -ne 128 ]]; then
echo "Invalid result length. Expected 64 or 128 characters. Please check delimiter and/or word_number parameters."
echo "Result: $result"
exit 1
fi

echo "$result"
}

use_checksum_comparison() {
local file_path=$1
local checksum=$2
local sha_type=${3:-"256"}

echo "Performing checksum verification"

if [[ ! -f "$file_path" ]]; then
echo "File not found: $file_path"
exit 1
fi

local_file_hash=$(shasum --algorithm "$sha_type" "$file_path" | awk '{print $1}')

if [[ "$local_file_hash" != "$checksum" ]]; then
echo "Checksum verification failed. Expected hash: $checksum; Actual hash: $local_file_hash."
exit 1
else
echo "Checksum verification passed"
fi
}
29 changes: 29 additions & 0 deletions linux/ubuntu/scripts/yq.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash -e
################################################################################
## File: yq.sh
## Desc: Installs YQ
## Supply chain security: YQ - checksum validation
################################################################################
# source: https://github.com/actions/runner-images/blob/5d6938f680075d63fa71f8aa70990866cd12884b/images/linux/scripts/installers/yq.sh

# Source the helpers for use with the script
. /imagegeneration/installers/helpers/install.sh

yq_arch() {
case "$(uname -m)" in
'aarch64') echo 'arm64' ;;
'x86_64') echo 'amd64' ;;
'armv7l') echo 'arm' ;;
*) exit 1 ;;
esac
}

# Download YQ
base_url="https://github.com/mikefarah/yq/releases/latest/download"
filename="yq_linux_$(yq_arch)"
download_with_retries "${base_url}/${filename}" "/tmp" "yq"
# Supply chain security - YQ
external_hash=$(get_hash_from_remote_file "${base_url}/checksums" "${filename} " "" " " "19")
use_checksum_comparison "/tmp/yq" "${external_hash}"
# Install YQ
sudo install /tmp/yq /usr/bin/yq
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe /usr/local/bin/yq to avoid conflicts with apt?

Copy link
Contributor Author

@Beanow Beanow Nov 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise this script I tried to make minimal changes to compared to upstream.

I'm introducing different architectures here compared to upstream, which only support amd64.

It seems to be a deliberate workflow compatibility choice to install it here though.
actions/runner-images#3768