-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
679 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| #!/bin/bash | ||
| set -e # Exit immediately if a command exits with a non-zero status. | ||
|
|
||
| echo "--- Starting Tool Management Script (manage_tools.sh) ---" | ||
|
|
||
| # --- Configuration & Environment Variables --- | ||
| # Required environment variables: | ||
| # - TOOL_DEFINITIONS: JSON string defining tools (name, s3_key, checksum, archive_format, executable_path_in_archive) | ||
| # - ARTIFACTS_BUCKET: S3 bucket name for downloading tool archives | ||
| # - REQUIRED_TOOLS: Space-separated list of tool names to install (e.g., "terraform terragrunt") | ||
| # - CODEBUILD_SRC_DIR: Base directory for caching (CodeBuild specific, but adaptable) | ||
|
|
||
| TOOL_CACHE_DIR="${CODEBUILD_SRC_DIR}/.tool_cache" | ||
| INSTALL_DIR="/usr/local/bin" # Standard installation directory for executables | ||
|
|
||
| # --- Sanity Checks --- | ||
| if [ -z "$TOOL_DEFINITIONS" ]; then | ||
| echo "ERROR: TOOL_DEFINITIONS environment variable is not set or is empty." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if ! echo "$TOOL_DEFINITIONS" | jq empty > /dev/null 2>&1; then | ||
| echo "ERROR: TOOL_DEFINITIONS does not contain valid JSON." >&2 | ||
| echo "TOOL_DEFINITIONS content: $TOOL_DEFINITIONS" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [ -z "$ARTIFACTS_BUCKET" ]; then | ||
| echo "ERROR: ARTIFACTS_BUCKET environment variable is not set or is empty." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [ -z "$REQUIRED_TOOLS" ]; then | ||
| echo "WARNING: REQUIRED_TOOLS environment variable is not set or is empty. No tools will be installed by this script." >&2 | ||
| # exit 0 # or exit 1 depending on desired strictness | ||
| fi | ||
|
|
||
| if [ -z "$CODEBUILD_SRC_DIR" ]; then | ||
| echo "ERROR: CODEBUILD_SRC_DIR environment variable is not set or is empty." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # --- Ensure jq is available --- | ||
| if ! command -v jq &> /dev/null; then | ||
| echo "jq not found, attempting to install..." | ||
| if apt-get update -y && apt-get install -y jq; then | ||
| echo "jq installed successfully via apt-get." | ||
| elif yum install -y jq; then | ||
| echo "jq installed successfully via yum." | ||
| else | ||
| echo "ERROR: Failed to install jq. Please ensure jq is available in the CodeBuild image or install it manually." >&2 | ||
| exit 1 | ||
| fi | ||
| fi | ||
| echo "jq is available." | ||
|
|
||
| # --- Create necessary directories --- | ||
| mkdir -p "$TOOL_CACHE_DIR" | ||
| mkdir -p "$INSTALL_DIR" | ||
| export PATH="$INSTALL_DIR:$PATH" # Add install dir to PATH for this script's session | ||
|
|
||
| echo "Tool Cache Directory: $TOOL_CACHE_DIR" | ||
| echo "Installation Directory: $INSTALL_DIR" | ||
| echo "Updated PATH: $PATH" | ||
| echo "Required tools to process: $REQUIRED_TOOLS" | ||
| echo "TOOL_DEFINITIONS (first 200 chars): $(echo "$TOOL_DEFINITIONS" | cut -c 1-200)..." | ||
|
|
||
| # --- Tool Installation Loop --- | ||
| for tool_name_var in $REQUIRED_TOOLS; do | ||
| # Use a subshell for per-tool variables to avoid conflicts and ensure clean state | ||
| ( | ||
| tool_name="$tool_name_var" | ||
| echo "--------------------------------------------------" | ||
| echo "Processing tool: $tool_name" | ||
|
|
||
| tool_info=$(echo "$TOOL_DEFINITIONS" | jq -r --arg tn "$tool_name" '.[$tn]') | ||
|
|
||
| if [ -z "$tool_info" ] || [ "$tool_info" == "null" ] || [ "$tool_info" == "{}" ] ; then | ||
| echo "ERROR: Tool '$tool_name' not found or has null/empty definition in TOOL_DEFINITIONS." >&2 | ||
| exit 1 # Exit subshell, which will cause the main script to exit due to \`set -e\` if subshell fails | ||
| fi | ||
|
|
||
| # Extract tool details | ||
| # version=$(echo "$tool_info" | jq -r '.version // empty') # Version not strictly needed by script but good for logging | ||
| s3_key=$(echo "$tool_info" | jq -r '.s3_key // empty') | ||
| expected_checksum=$(echo "$tool_info" | jq -r '.checksum // empty') # SHA256 | ||
| archive_format=$(echo "$tool_info" | jq -r '.archive_format // empty') | ||
| # executable_path_in_archive is the path *inside* the archive to the executable file itself. | ||
| # If archive_format is 'binary', this is ignored. | ||
| # If archive_format is 'zip' or 'tar.gz' and this is empty/null, script defaults to tool_name. | ||
| executable_path_in_archive=$(echo "$tool_info" | jq -r '.executable_path_in_archive // empty') | ||
|
|
||
| # Validate extracted details | ||
| if [ -z "$s3_key" ] || [ -z "$expected_checksum" ] || [ -z "$archive_format" ]; then | ||
| echo "ERROR: Missing one or more critical fields (s3_key, checksum, archive_format) for tool '$tool_name'." >&2 | ||
| echo "Tool Info Found: $tool_info" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Determine the actual executable name within the archive if not specified | ||
| effective_executable_path_in_archive="$executable_path_in_archive" | ||
| if [[ "$archive_format" == "zip" || "$archive_format" == "tar.gz" ]] && \ | ||
| [[ -z "$executable_path_in_archive" || "$executable_path_in_archive" == "null" ]]; then | ||
| effective_executable_path_in_archive="$tool_name" | ||
| fi | ||
|
|
||
| archive_filename=$(basename "$s3_key") | ||
| cached_archive_path="$TOOL_CACHE_DIR/$archive_filename" | ||
| s3_source_path="s3://${ARTIFACTS_BUCKET}/${s3_key}" | ||
| target_executable_path="$INSTALL_DIR/$tool_name" # Final destination of the executable | ||
|
|
||
| echo "Details for $tool_name:" | ||
| # echo " Version: $version" | ||
| echo " S3 Key: $s3_key" | ||
| echo " Expected SHA256: $expected_checksum" | ||
| echo " Archive Format: $archive_format" | ||
| echo " Executable path in archive (effective): $effective_executable_path_in_archive" | ||
| echo " Archive filename: $archive_filename" | ||
| echo " Cached archive path: $cached_archive_path" | ||
| echo " Target executable path: $target_executable_path" | ||
|
|
||
| # --- Cache Check & Download --- | ||
| needs_download=true | ||
| if [ -f "$cached_archive_path" ]; then | ||
| echo "Cached archive $cached_archive_path found. Verifying checksum..." | ||
| actual_checksum=$(sha256sum "$cached_archive_path" | awk '{print $1}') | ||
| if [ "$actual_checksum" == "$expected_checksum" ]; then | ||
| echo "Checksum for cached $archive_filename is VALID." | ||
| needs_download=false | ||
| else | ||
| echo "Checksum MISMATCH for cached $archive_filename. Expected: $expected_checksum, Got: $actual_checksum. Re-downloading." | ||
| rm -f "$cached_archive_path" | ||
| fi | ||
| else | ||
| echo "Archive $archive_filename not found in cache: $cached_archive_path. Downloading." | ||
| fi | ||
|
|
||
| if [ "$needs_download" == true ]; then | ||
| echo "Downloading $tool_name from $s3_source_path to $cached_archive_path..." | ||
| if ! aws s3 cp "$s3_source_path" "$cached_archive_path"; then | ||
| echo "ERROR: Failed to download $tool_name from S3." >&2 | ||
| exit 1 | ||
| fi | ||
| echo "Download complete. Verifying checksum of downloaded file..." | ||
| actual_checksum=$(sha256sum "$cached_archive_path" | awk '{print $1}') | ||
| if [ "$actual_checksum" != "$expected_checksum" ]; then | ||
| echo "ERROR: Checksum MISMATCH for downloaded $archive_filename. Expected: $expected_checksum, Got: $actual_checksum." >&2 | ||
| rm -f "$cached_archive_path" | ||
| exit 1 | ||
| fi | ||
| echo "Checksum for downloaded $archive_filename is VALID." | ||
| fi | ||
|
|
||
| # --- Extraction & Installation --- | ||
| echo "Installing $tool_name from $cached_archive_path to $target_executable_path..." | ||
| # Ensure target is clean for binary moves/copies | ||
| rm -f "$target_executable_path" | ||
| # Create a temporary directory for extraction to keep $TOOL_CACHE_DIR clean from extracted files | ||
| temp_extract_dir=$(mktemp -d -p "$TOOL_CACHE_DIR" "tmp_extract_${tool_name}_XXXXXX") | ||
|
|
||
| extracted_executable_source_path="" # Path to the executable *after* extraction | ||
|
|
||
| if [ "$archive_format" == "zip" ]; then | ||
| unzip -o "$cached_archive_path" -d "$temp_extract_dir" > /dev/null | ||
| extracted_executable_source_path="$temp_extract_dir/$effective_executable_path_in_archive" | ||
| elif [ "$archive_format" == "tar.gz" ]; then | ||
| # tar -xzf "$cached_archive_path" -C "$temp_extract_dir" "$effective_executable_path_in_archive" # This only extracts the specific file | ||
| tar -xzf "$cached_archive_path" -C "$temp_extract_dir" > /dev/null # Extract all | ||
| extracted_executable_source_path="$temp_extract_dir/$effective_executable_path_in_archive" | ||
| elif [ "$archive_format" == "binary" ]; then | ||
| # For binary, the "archive" is the executable itself. Copy it to the temp dir first for consistency. | ||
| cp "$cached_archive_path" "$temp_extract_dir/$tool_name" | ||
| extracted_executable_source_path="$temp_extract_dir/$tool_name" | ||
| else | ||
| echo "ERROR: Unknown archive format '$archive_format' for $tool_name." >&2 | ||
| rm -rf "$temp_extract_dir" | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [ ! -f "$extracted_executable_source_path" ]; then | ||
| echo "ERROR: Executable for $tool_name not found at '$extracted_executable_source_path' after extraction." >&2 | ||
| echo "Contents of $temp_extract_dir:" >&2 | ||
| ls -lR "$temp_extract_dir" >&2 | ||
| rm -rf "$temp_extract_dir" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Moving '$extracted_executable_source_path' to '$target_executable_path'" | ||
| mv "$extracted_executable_source_path" "$target_executable_path" | ||
| chmod +x "$target_executable_path" | ||
|
|
||
| # Clean up temporary extraction directory | ||
| rm -rf "$temp_extract_dir" | ||
|
|
||
| echo "$tool_name installed successfully to $target_executable_path." | ||
|
|
||
| # --- Verification (Optional but Recommended) --- | ||
| echo "Verifying $tool_name installation..." | ||
| if command -v $tool_name &> /dev/null; then | ||
| echo "Attempting to get version for $tool_name..." | ||
| # Try common version flags, redirect stderr to stdout for capture, take first line | ||
| if $tool_name --version &> /dev/null; then | ||
| echo "$($tool_name --version 2>&1 | head -n 1)" | ||
| elif $tool_name version &> /dev/null; then | ||
| echo "$($tool_name version 2>&1 | head -n 1)" | ||
| elif $tool_name -version &> /dev/null; then # e.g. Java | ||
| echo "$($tool_name -version 2>&1 | head -n 1)" | ||
| elif $tool_name -v &> /dev/null; then # e.g. Go | ||
| echo "$($tool_name -v 2>&1 | head -n 1)" | ||
| else | ||
| echo "$tool_name is callable, but version command is unknown or failed. Assuming successful installation." | ||
| fi | ||
| else | ||
| echo "ERROR: $tool_name command not found in PATH after installation attempt to $target_executable_path." >&2 | ||
| exit 1 | ||
| fi | ||
| ) || exit 1 # If subshell fails, exit the main script | ||
| done | ||
|
|
||
| echo "--- Tool Management Script (manage_tools.sh) Finished Successfully ---" |
Oops, something went wrong.