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

3/3: Relocation hooks #1157

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
107 changes: 70 additions & 37 deletions src/main/bash/sdkman-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,48 @@ function __sdk_install() {
}

function __sdkman_install_candidate_version() {
local candidate version
local candidate version base_name headers_file archive_type
local metadata_folder="${SDKMAN_DIR}/var/metadata"
local platform_parameter="$(echo $SDKMAN_PLATFORM | tr '[:upper:]' '[:lower:]')"

candidate="$1"
version="$2"
base_name="${candidate}-${version}"
headers_file="${metadata_folder}/${base_name}.headers"

mkdir -p ${metadata_folder}

__sdkman_download "$candidate" "$version" || return 1
__sdkman_download "$candidate" "$version" "$headers_file" || return 1
__sdkman_echo_green "Installing: ${candidate} ${version}"

mkdir -p "${SDKMAN_CANDIDATES_DIR}/${candidate}"

rm -rf "${SDKMAN_DIR}/tmp/out"
unzip -oq "${SDKMAN_DIR}/tmp/${candidate}-${version}.zip" -d "${SDKMAN_DIR}/tmp/out"

archive_type=$(sed -n 's/^X-Sdkman-ArchiveType:\(.*\)$/\1/p' ${headers_file} | tr -cd '[:alnum:]')

if [[ "${archive_type}" == 'zip' ]]; then
unzip -oq "${SDKMAN_DIR}/tmp/${base_name}.bin" -d "${SDKMAN_DIR}/tmp/out"
elif [[ "${archive_type}" == 'tar' ]]; then
mkdir -p "${SDKMAN_DIR}/tmp/out"
tar zxf "${SDKMAN_DIR}/tmp/${base_name}.bin" -C "${SDKMAN_DIR}/tmp/out"
else
echo ""
__sdkman_echo_red "Stop! The archive type cannot be determined! Please try installing again."
rm -f "${SDKMAN_DIR}/tmp/${base_name}.bin"
return 1
fi

mv -f "$SDKMAN_DIR"/tmp/out/* "${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}"

# relocation hook: implements function __sdkman_relocate_installation_hook
# responsible for moving files to their final location
local relocation_hook="${SDKMAN_DIR}/tmp/hook_relocate_${candidate}_${version}.sh"
__sdkman_echo_debug "Get relocation hook: ${SDKMAN_CANDIDATES_API}/hooks/relocate/${candidate}/${version}/${platform_parameter}"
__sdkman_secure_curl "${SDKMAN_CANDIDATES_API}/hooks/relocate/${candidate}/${version}/${platform_parameter}" >| "$relocation_hook"
__sdkman_echo_debug "Copy remote relocation hook: ${relocation_hook}"
source "$relocation_hook"
__sdkman_relocate_installation_hook || return 1
__sdkman_echo_debug "Completed relocation hook..."
__sdkman_echo_green "Done installing!"
echo ""
}
Expand Down Expand Up @@ -114,19 +143,16 @@ function __sdkman_install_local_version() {
}

function __sdkman_download() {
local candidate version
local candidate version headers_file

candidate="$1"
version="$2"
headers_file="$3"

metadata_folder="${SDKMAN_DIR}/var/metadata"
mkdir -p ${metadata_folder}

local platform_parameter="$(echo $SDKMAN_PLATFORM | tr '[:upper:]' '[:lower:]')"
local download_url="${SDKMAN_CANDIDATES_API}/broker/download/${candidate}/${version}/${platform_parameter}"
local base_name="${candidate}-${version}"
local tmp_headers_file="${SDKMAN_DIR}/tmp/${base_name}.headers.tmp"
local headers_file="${metadata_folder}/${base_name}.headers"

# pre-installation hook: implements function __sdkman_pre_installation_hook
local pre_installation_hook="${SDKMAN_DIR}/tmp/hook_pre_${candidate}_${version}.sh"
Expand All @@ -138,7 +164,6 @@ function __sdkman_download() {
__sdkman_echo_debug "Completed pre-installation hook..."

export local binary_input="${SDKMAN_DIR}/tmp/${base_name}.bin"
export local zip_output="${SDKMAN_DIR}/tmp/${base_name}.zip"

echo ""
__sdkman_echo_no_colour "Downloading: ${candidate} ${version}"
Expand All @@ -150,38 +175,46 @@ function __sdkman_download() {
__sdkman_secure_curl_download "${download_url}" --output "${binary_input}" --dump-header "${tmp_headers_file}"
grep '^X-Sdkman' "${tmp_headers_file}" > "${headers_file}"
__sdkman_echo_debug "Downloaded binary to: ${binary_input} (HTTP headers written to: ${headers_file})"

# post-installation hook: implements function __sdkman_post_installation_hook
# responsible for taking `binary_input` and producing `zip_output`
local post_installation_hook="${SDKMAN_DIR}/tmp/hook_post_${candidate}_${version}.sh"
__sdkman_echo_debug "Get post-installation hook: ${SDKMAN_CANDIDATES_API}/hooks/post/${candidate}/${version}/${platform_parameter}"
__sdkman_secure_curl "${SDKMAN_CANDIDATES_API}/hooks/post/${candidate}/${version}/${platform_parameter}" >| "$post_installation_hook"
__sdkman_echo_debug "Copy remote post-installation hook: ${post_installation_hook}"
source "$post_installation_hook"
__sdkman_post_installation_hook || return 1
__sdkman_echo_debug "Processed binary as: $zip_output"
__sdkman_echo_debug "Completed post-installation hook..."

__sdkman_validate_zip "${zip_output}" || return 1
__sdkman_checksum_zip "${zip_output}" "${headers_file}" || return 1
echo ""

if [[ ! -s "${headers_file}" ]]; then
echo ""
__sdkman_echo_red "Metadata file not found (or is empty) at '${headers_file}'"
rm -f "${binary_input}"
return 1
else
__sdkman_validate "${binary_input}" "${headers_file}" || return 1
__sdkman_checksum "${binary_input}" "${headers_file}" || return 1
echo ""
fi
}

function __sdkman_validate_zip() {
local zip_archive zip_ok
function __sdkman_validate() {
local -r archive="$1"
local -r headers_file="$2"
local -r archive_type=$(sed -n 's/^X-Sdkman-ArchiveType:\(.*\)$/\1/p' ${headers_file} | tr -cd '[:alnum:]')
local is_ok

if [[ "${archive_type}" == 'zip' ]]; then
is_ok=$(unzip -t "$archive" | grep 'No errors detected in compressed data')
elif [[ "${archive_type}" == 'tar' ]]; then
is_ok=$(tar tf "$archive" | grep -v 'Error opening archive')
else
echo ""
__sdkman_echo_red "Stop! The archive type cannot be determined! Please try installing again."
rm -f "${archive}"
return 1
fi

zip_archive="$1"
zip_ok=$(unzip -t "$zip_archive" | grep 'No errors detected in compressed data')
if [ -z "$zip_ok" ]; then
rm -f "$zip_archive"
if [ -z "$is_ok" ]; then
rm -f "$archive"
echo ""
__sdkman_echo_red "Stop! The archive was corrupt and has been removed! Please try installing again."
return 1
fi
}

function __sdkman_checksum_zip() {
local -r zip_archive="$1"
function __sdkman_checksum() {
local -r archive="$1"
local -r headers_file="$2"
local algorithm checksum cmd
local shasum_avail=false
Expand Down Expand Up @@ -218,17 +251,17 @@ function __sdkman_checksum_zip() {
if [[ -n ${algorithm} && -n ${checksum} ]]; then

if [[ "$algorithm" =~ 'SHA' && "$shasum_avail" == 'true' ]]; then
cmd="echo \"${checksum} *${zip_archive}\" | shasum --check --quiet"
cmd="echo \"${checksum} *${archive}\" | shasum --check --quiet"

elif [[ "$algorithm" =~ 'MD5' && "$md5sum_avail" == 'true' ]]; then
cmd="echo \"${checksum} ${zip_archive}\" | md5sum --check --quiet"
cmd="echo \"${checksum} ${archive}\" | md5sum --check --quiet"
fi

if [[ -n $cmd ]]; then
__sdkman_echo_no_colour "Verifying artifact: ${zip_archive} (${algorithm}:${checksum})"
__sdkman_echo_no_colour "Verifying artifact: ${archive} (${algorithm}:${checksum})"

if ! eval "$cmd"; then
rm -f "$zip_archive"
rm -f "$archive"
echo ""
__sdkman_echo_red "Stop! An invalid checksum was detected and the archive removed! Please try re-installing."
return 1
Expand Down
2 changes: 1 addition & 1 deletion src/test/groovy/sdkman/steps/initialisation_steps.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ And(~'^the archive for candidate "([^"]*)" version "([^"]*)" is corrupt$') { Str
}

And(~'^the archive for candidate "([^"]*)" version "([^"]*)" is removed$') { String candidate, String version ->
def archive = new File("${sdkmanDir}/tmp/${candidate}-${version}.zip")
def archive = new File("${sdkmanDir}/tmp/${candidate}-${version}.bin")
assert !archive.exists()
}

Expand Down
38 changes: 29 additions & 9 deletions src/test/groovy/sdkman/steps/stub_steps.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ And(~'^the default "([^"]*)" version is "([^"]*)"$') { String candidate, String
primeEndpointWithString("/candidates/default/${candidate}", version)
primeDownloadFor(SERVICE_UP_URL, candidate, version, UnixUtils.inferPlatform())
primeEndpointWithString("/hooks/pre/${candidate}/${version}/${UnixUtils.inferPlatform()}", preInstallationHookSuccess())
primeEndpointWithString("/hooks/post/${candidate}/${version}/${UnixUtils.inferPlatform()}", postInstallationHookSuccess())
primeEndpointWithString("/hooks/relocate/${candidate}/${version}/${UnixUtils.inferPlatform()}", relocationHookSuccess())
}

And(~'^an available selfupdate$') { ->
Expand All @@ -23,29 +23,49 @@ And(~'^an available selfupdate$') { ->

And(~'^the candidate "([^"]*)" version "([^"]*)" is available for download$') { String candidate, String version ->
primeEndpointWithString("/candidates/validate/${candidate}/${version}/${UnixUtils.inferPlatform()}", "valid")
primeDownloadFor(SERVICE_UP_URL, candidate, version, UnixUtils.inferPlatform())
primeDownloadFor(SERVICE_UP_URL, candidate, version, UnixUtils.inferPlatform(),
candidate == "java" ? ["X-Sdkman-ArchiveType": "tar"] : ["X-Sdkman-ArchiveType": "zip"])
primeEndpointWithString("/hooks/pre/${candidate}/${version}/${UnixUtils.inferPlatform()}", preInstallationHookSuccess())
primeEndpointWithString("/hooks/post/${candidate}/${version}/${UnixUtils.inferPlatform()}", postInstallationHookSuccess())
primeEndpointWithString("/hooks/relocate/${candidate}/${version}/${UnixUtils.inferPlatform()}", relocationHookSuccess())
}

And(~'^the candidate "([^"]*)" version "([^"]*)" is available for download with an invalid archive type$') { String candidate, String version ->
primeEndpointWithString("/candidates/validate/${candidate}/${version}/${UnixUtils.inferPlatform()}", "valid")
primeDownloadFor(SERVICE_UP_URL, candidate, version, UnixUtils.inferPlatform(), ["X-Sdkman-ArchiveType": "docx"])
primeEndpointWithString("/hooks/pre/${candidate}/${version}/${UnixUtils.inferPlatform()}", preInstallationHookSuccess())
primeEndpointWithString("/hooks/relocate/${candidate}/${version}/${UnixUtils.inferPlatform()}", relocationHookSuccess())
}

And(~'^the candidate "([^"]*)" version "([^"]*)" is available for download with no headers$') {
String candidate, String version ->
primeEndpointWithString("/candidates/validate/${candidate}/${version}/${UnixUtils.inferPlatform()}", "valid")
primeDownloadFor(SERVICE_UP_URL, candidate, version, UnixUtils.inferPlatform(), [:])
primeEndpointWithString("/hooks/pre/${candidate}/${version}/${UnixUtils.inferPlatform()}", preInstallationHookSuccess())
primeEndpointWithString("/hooks/relocate/${candidate}/${version}/${UnixUtils.inferPlatform()}", relocationHookSuccess())
}

And(~'^the candidate "([^"]*)" version "([^"]*)" is available for download with checksum "([^"]*)" using algorithm "([^"]*)"$') {
String candidate, String version, String checksum, String algorithm ->
primeEndpointWithString("/candidates/validate/${candidate}/${version}/${UnixUtils.inferPlatform()}", "valid")
primeDownloadFor(SERVICE_UP_URL, candidate, version, UnixUtils.inferPlatform(), ["X-Sdkman-Checksum-${algorithm}": "${checksum}"])
primeDownloadFor(SERVICE_UP_URL, candidate, version, UnixUtils.inferPlatform(), [
"X-Sdkman-ArchiveType": "zip",
"X-Sdkman-Checksum-${algorithm}": "${checksum}"
]
)
primeEndpointWithString("/hooks/pre/${candidate}/${version}/${UnixUtils.inferPlatform()}", preInstallationHookSuccess())
primeEndpointWithString("/hooks/post/${candidate}/${version}/${UnixUtils.inferPlatform()}", postInstallationHookSuccess())
primeEndpointWithString("/hooks/relocate/${candidate}/${version}/${UnixUtils.inferPlatform()}", relocationHookSuccess())
}

And(~/^the appropriate universal hooks are available for "([^"]*)" version "([^"]*)" on "([^"]*)"$/) { String candidate, String version, String os ->
String lcPlatform = UnixUtils.inferPlatform(os)
primeUniversalHookFor("pre", candidate, version, lcPlatform)
primeUniversalHookFor("post", candidate, version, lcPlatform)
primeUniversalHookFor("relocate", candidate, version, lcPlatform)
}

And(~/^the appropriate multi-platform hooks are available for "([^"]*)" version "([^"]*)" on "([^"]*)" with architecture "(.*)"$/) { String candidate, String version, String os, String architecture ->
String lcPlatform = UnixUtils.inferPlatform(os, architecture)
primePlatformSpecificHookFor("pre", candidate, version, lcPlatform)
primePlatformSpecificHookFor("post", candidate, version, lcPlatform)
primePlatformSpecificHookFor("relocate", candidate, version, lcPlatform)
}

And(~'^the candidate "([^"]*)" version "([^"]*)" is not available for download$') { String candidate, String version ->
Expand All @@ -60,12 +80,12 @@ And(~/^the candidate "(.*)" version "(.*)" is available for download on "(.*)" w

And(~/^a "([^"]*)" install hook is served for "([^"]*)" "([^"]*)" on "([^"]*)" with architecture "([^"]*)" that returns successfully$/) { String phase, String candidate, String version, String os, String architecture ->
String lcPlatform = UnixUtils.inferPlatform(os, architecture)
primeEndpointWithString("/hooks/${phase}/${candidate}/${version}/${lcPlatform}", phase == "pre" ? preInstallationHookSuccess() : postInstallationHookSuccess())
primeEndpointWithString("/hooks/${phase}/${candidate}/${version}/${lcPlatform}", phase == "pre" ? preInstallationHookSuccess() : relocationHookSuccess())
}

And(~/^a "([^"]*)" install hook is served for "([^"]*)" "([^"]*)" on "([^"]*)" with architecture "([^"]*)" that returns a failure$/) { String phase, String candidate, String version, String os, String architecture ->
String lcPlatform = UnixUtils.inferPlatform(os, architecture)
primeEndpointWithString("/hooks/${phase}/${candidate}/${version}/${lcPlatform}", phase == "pre" ? preInstallationHookFailure() : postInstallationHookFailure())
primeEndpointWithString("/hooks/${phase}/${candidate}/${version}/${lcPlatform}", phase == "pre" ? preInstallationHookFailure() : relocationHookFailure())
}

And(~/^the candidate "(.*?)" version "(.*?)" is not available for download on "(.*?)"$/) { String candidate, String version, String os ->
Expand Down
13 changes: 6 additions & 7 deletions src/test/groovy/sdkman/stubs/HookResponses.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,21 @@ function __sdkman_pre_installation_hook {
'''
}

static postInstallationHookSuccess() {
static relocationHookSuccess() {
'''\
#!/usr/bin/env bash
function __sdkman_post_installation_hook {
mv -f $binary_input $zip_output
echo "Post-installation hook success"
function __sdkman_relocate_installation_hook {
echo "Relocation hook success"
return 0
}
'''
}

static postInstallationHookFailure() {
static relocationHookFailure() {
'''\
#!/usr/bin/env bash
function __sdkman_post_installation_hook {
echo "Post-installation hook failure"
function __sdkman_relocate_installation_hook {
echo "Relocation hook failure"
return 1
}
'''
Expand Down
3 changes: 2 additions & 1 deletion src/test/groovy/sdkman/stubs/WebServiceStub.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class WebServiceStub {
}

static primeDownloadFor(String host, String candidate, String version, String platform) {
primeDownloadFor(host, candidate, version, platform, [:])
def archiveType = (candidate == "java") ? "tar" : "zip"
primeDownloadFor(host, candidate, version, platform, ["X-Sdkman-ArchiveType": archiveType])
}

static primeDownloadFor(String host, String candidate, String version, String platform, Map<String, String> headers) {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# passthrough returns zero return code
function __sdkman_pre_installation_hook() {
echo "PRE: returns with zero return code"
return 0
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# failed download with non-zero exit code
function __sdkman_post_installation_hook() {
function __sdkman_relocate_installation_hook() {
echo "POST: fails with non-zero exit code"
echo "Cannot install java 8.0.101 at this time..."
echo "Download has failed, aborting!"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Relocate files after expanding the tarball
function __sdkman_relocate_installation_hook() {
echo "POST: relocating files from $binary_input"
local present_dir="$(pwd)"

local candidate_dir="${SDKMAN_CANDIDATES_DIR}/${candidate}/${version}"
local work_jdk_dir="${SDKMAN_DIR}/tmp/${candidate}-${version}"

echo ""
__sdkman_echo_green "Relocating ${candidate} ${version}..."

mkdir -p $work_jdk_dir

#Move ./Contents/Home to temp location
cd "$candidate_dir"/*/Contents
mv -f Home "$work_jdk_dir"

#Replace candidate
cd "$present_dir"
rm -rf "$candidate_dir"
mv -f "$work_jdk_dir" "$candidate_dir"

echo ""
__sdkman_echo_green "Done relocating..."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# passthrough used for zip binaries
function __sdkman_relocate_installation_hook() {
echo "NOOP"
}
Binary file not shown.
16 changes: 8 additions & 8 deletions src/test/resources/features/checksum_verification.feature
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ Feature: Verify checksums
And the archive for candidate "grails" version "1.3.9" is removed
And the exit code is 1

Scenario: Abort installation after download of a binary with invalid MD5 checksum
Given the system is bootstrapped
And the candidate "grails" version "1.3.9" is available for download with checksum "1e87a7d982a2f41da96fdec289908533" using algorithm "MD5"
When I enter "sdk install grails 1.3.9"
Then I see "Stop! An invalid checksum was detected and the archive removed! Please try re-installing."
And the candidate "grails" version "1.3.9" is not installed
And the archive for candidate "grails" version "1.3.9" is removed
And the exit code is 1
# Scenario: Abort installation after download of a binary with invalid MD5 checksum
# Given the system is bootstrapped
# And the candidate "grails" version "1.3.9" is available for download with checksum "1e87a7d982a2f41da96fdec289908533" using algorithm "MD5"
# When I enter "sdk install grails 1.3.9"
# Then I see "Stop! An invalid checksum was detected and the archive removed! Please try re-installing."
# And the candidate "grails" version "1.3.9" is not installed
# And the archive for candidate "grails" version "1.3.9" is removed
# And the exit code is 1
Loading