diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f806607..2920c6f 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -81,5 +81,5 @@ jobs: ~/.local/share/containers/storage/ key: ${{ runner.os }}-${{ runner.arch }}-podman - # Plain cargo tests + # Run various tests - run: nix develop -c make test \ No newline at end of file diff --git a/Makefile b/Makefile index 07aed8d..f42d57d 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ build-nix: nix build -o build/nix .PHONY: test -test: test-docker test-doc test-clippy test-cargo test-cli +test: test-docker test-doc test-clippy test-cargo test-cli test-install .PHONY: test-docker test-docker: @@ -36,6 +36,10 @@ test-clippy: test-doc: doc git diff --exit-code docs/schema/config-schema.json +.PHONY: test-install +test-install: + tests/install/test-install.sh + # Build doc with mdBook and json-schema-for-humans # See: # - https://github.com/actions/starter-workflows/blob/main/pages/mdbook.yml diff --git a/install.sh b/install.sh index b5fa2f8..666db81 100755 --- a/install.sh +++ b/install.sh @@ -5,6 +5,93 @@ INSTALL_DIR="/usr/local/bin" YELLOW='\e[0;33m' RESET='\033[0m' +# +# From (and credit to) https://github.com/client9/shlib +# + +# +# is_command: returns true if command exists +# +# `which` is not portable, in particular is often +# not available on RedHat/CentOS systems. +# +# `type` is implemented in many shells but technically not +# part of the posix spec. +# +# `command -v` +# +is_command() { + command -v "$1" >/dev/null + #type "$1" > /dev/null 2> /dev/null +} + +# hash_sha256_verify validates a binary against a checksum.txt file +hash_sha256_verify() { + TARGET=$1 + checksums=$2 + + if [ -z "$checksums" ]; then + echo "hash_sha256_verify checksum file not specified in arg2" >&2 + return 1 + fi + + # http://stackoverflow.com/questions/2664740/extract-file-basename-without-path-and-extension-in-bash + BASENAME=${TARGET##*/} + + want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) + + # if file does not exist $want will be empty + if [ -z "$want" ]; then + echo "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" >&2 + return 1 + fi + got=$(hash_sha256 "$TARGET") + if [ "$want" != "$got" ]; then + echo "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" >&2 + return 1 + fi +} + +# hash_sha256: compute SHA256 of $1 or stdin +# +# ## Example +# +# ```bash +# $ hash_sha256 foobar.tar.gz +# 237982738471928379137 +# ``` +# +# note lack of pipes to make sure errors are +# caught regardless of shell settings +# sha256sum NOFILE | cut ... +# won't fail unless setpipefail is on +# +hash_sha256() { + TARGET=${1:-/dev/stdin} + if is_command gsha256sum; then + # mac homebrew, others + hash=$(gsha256sum "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command sha256sum; then + # gnu, busybox + hash=$(sha256sum "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command shasum; then + # darwin, freebsd? + hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 + echo "$hash" | cut -d ' ' -f 1 + elif is_command openssl; then + hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 + echo "$hash" | cut -d ' ' -f a + else + log_crit "hash_sha256 unable to find command to compute sha-256 hash" + return 1 + fi +} + +# End https://github.com/client9/shlib + + # Check required tools are available if ! curl --version > /dev/null then @@ -51,14 +138,16 @@ NOVOPS_BIN_TMP_PATH="${TMP_INSTALL_DIR}/novops" ZIP_URL="https://github.com/PierreBeucher/novops/releases/latest/download/${ZIP_NAME}" CHECKSUM_URL="https://github.com/PierreBeucher/novops/releases/latest/download/${CHECKSUM_NAME}" -# Download and unzip the package +echo "Downloading Novops release..." + mkdir -p $TMP_INSTALL_DIR -curl -L "${ZIP_URL}" -o "${ZIP_PATH}" -unzip -o "${ZIP_PATH}" -d "${TMP_INSTALL_DIR}" +curl -s -L "${ZIP_URL}" -o "${ZIP_PATH}" +curl -s -L "${CHECKSUM_URL}" -o "${CHECKSUM_PATH}" + +echo "Extracting and veryfing checksum..." -# Checksum -curl -L "${CHECKSUM_URL}" -o "${CHECKSUM_PATH}" -sha256sum -c "${CHECKSUM_PATH}" +unzip -q -o "${ZIP_PATH}" -d "${TMP_INSTALL_DIR}" +hash_sha256_verify "${NOVOPS_BIN_TMP_PATH}" "${CHECKSUM_PATH}" if [ $? -eq 0 ]; then echo "Checksum verification succeeded." @@ -67,6 +156,8 @@ else exit 1 fi +echo "Copying to ${INSTALL_DIR}..." + # Only need sudo to copy to install dir if [ "$(id -u)" -eq 0 ]; then mv "${NOVOPS_BIN_TMP_PATH}" "${INSTALL_DIR}" @@ -74,18 +165,20 @@ else sudo mv "${NOVOPS_BIN_TMP_PATH}" "${INSTALL_DIR}" fi +echo "Cleanup temporary files..." + rm "${ZIP_PATH}" rm "${CHECKSUM_PATH}" -# Check if /usr/local/bin is in the PATH +# Check install dir is in the PATH # Use case statement for POSIX-compliant pattern matching case ":${PATH}:" in - *:/usr/local/bin:*) - echo "Novops installed successfully." + *:${INSTALL_DIR}:*) + novops --version + echo "Novops has been successfully installed ✨" ;; *) - echo "${YELLOW}Warning: /usr/local/bin is not in your PATH, novops commands may not work.${RESET}" >&2 + echo -e "${YELLOW}Warning: ${INSTALL_DIR} is not in your PATH, novops commands may not work.${RESET}" >&2 ;; esac - diff --git a/tests/install/install.sh b/tests/install/install.sh deleted file mode 100755 index b360882..0000000 --- a/tests/install/install.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env sh - -set -x - -# -# Manual script to test Novops installation script with various shells -# - -# bash dash zsh fish ksh tcsh csh curl unzip - - -function test_novops_install() { - local image=$1 - shift # Shift the first argument to get rid of the image name, leaving only commands - - # Run the docker container with the specified image and commands - if docker run -it --rm -v "$PWD:/local" -w /local "$image" /bin/sh -c "$*"; then - echo "OK: $image" - else - echo "NOT OK: $image" - exit 1 - fi -} - -test_novops_install alpine:3.19.1 "apk update && apk add curl unzip && ./install.sh && novops --version" -test_novops_install debian:12.5-slim "apt update && apt install curl unzip -y && ./install.sh && novops --version" -test_novops_install ubuntu:22.04 "apt update && apt install curl unzip -y && ./install.sh && novops --version" - diff --git a/tests/install/test-install.sh b/tests/install/test-install.sh new file mode 100755 index 0000000..4e797b9 --- /dev/null +++ b/tests/install/test-install.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env sh + +# +# Test Novops installation script with various shells and OSes +# + + +function test_novops_install() { + local image=$1 + shift # Shift the first argument to get rid of the image name, leaving only commands + + # Run the docker container with the specified image and commands + if podman run -it --rm -v "$PWD:/local" -w /local "$image" /bin/sh -c "$*"; then + echo "OK: $image" + else + echo "NOT OK: $image" + exit 1 + fi +} + +test_novops_install docker.io/library/alpine:3.19.1 "apk update && apk add curl unzip && ./install.sh && novops --version" +test_novops_install docker.io/library/debian:12.5-slim "apt update && apt install curl unzip -y && ./install.sh && novops --version" +test_novops_install docker.io/library/ubuntu:22.04 "apt update && apt install curl unzip -y && ./install.sh && novops --version" +