From 675216e34b48f5747406a09cb47e45808d4ac0c8 Mon Sep 17 00:00:00 2001 From: Michael Schapira Date: Fri, 3 Apr 2026 18:18:29 +0000 Subject: [PATCH] Initial commit: cpd-cli auto-update script - update-cpd-cli.sh: automate CPD CLI version updates - README.md: project documentation - .gitignore: standard ignores --- .gitignore | 48 ++++++ README.md | 106 +++++++++++++ update-cpd-cli.sh | 395 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 549 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 update-cpd-cli.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..547d841 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Compiled binaries/binaries +*~ +*.o +*.exe +*.out +*.so +*.a + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +*~ +.project +.cache + +# Build artifacts +build/ +dist/ +pkg/ +*.tar.gz +*.tgz +*.tar + +# OS files +.DS_Store +.Spotlight-V100 +.Trashes +.AppleDouble +._* + +# Environment files +.env +.env.local +.venv/ +venv/ +ENV/ + +# OS temp files +*.log +npm-debug.log +yarn-debug.log +pids/ + +# Temporary files +*.tmp +*.temp diff --git a/README.md b/README.md new file mode 100644 index 0000000..eefc04f --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +# cpd-cli Update Automation Script + +Automate [IBM cpd-cli](https://github.com/IBM/cpd-cli) version updates with a single script! + +## Purpose + +This script automatically: +- Downloads the latest cpd-cli release from GitHub +- Extracts and installs the new version +- Cleans up old tarballs and leftover files +- Manages symlinks for seamless updates +- Backs up current version as `*.OLD` for rollback + +## Requirements + +- Bash 4+ or compatible shell +- `curl` for downloading +- `tar` for extraction +- `sudo` for system-wide installation (or run as root) +- `jq` (optional, for faster GitHub API parsing) + +## Usage + +```bash +# Make executable +chmod +x update-cpd-cli.sh + +# Run update script +./update-cpd-cli.sh + +# Output: +[INFO] Fetching latest release from GitHub API... +[INFO] Downloading cpd-cli-linux-EE-14.3.2.tgz... +[INFO] Installing cpd-cli 14.3.2... +[SUCCESS] Successfully installed cpd-cli 14.3.2 +[UPDATE] Update completed successfully! +``` + +## Configuration + +You can override defaults via environment variables: + +```bash +# Override installation path +export CPD_CLI_PATH=/custom/installdir + +# Override GitHub base URL +export CPD_SOURCE=https://github.com/corp-softy/cpd-cli/releases + +# Run with custom paths +./update-cpd-cli.sh +``` + +## What Happens + +1. **Detects latest version** from GitHub Releases API +2. **Downloads** the new release tarball (with retries) +3. **Backs up** current installation as `*.OLD` +4. **Extracts** new version to temp directory +5. **Removes** old symlinks +6. **Installs** new version and creates symlinks +7. **Cleans up** old tarballs +8. **Verifies** installation + +## Rollback + +If you need to rollback: + +```bash +# Reinstall version from OLD directory +mv /path/to/cpd-cli-linux-EE.X.X.X-OLD /path/to/new/name +# Or: +sudo ln -s /path/to/cpd-cli-linux-EE.X.X.X-OLD/* /usr/local/bin/ +``` + +## Development + +```bash +# Make changes +vim update-cpd-cli.sh + +# Test locally +./update-cpd-cli.sh + +# Commit changes +git add . +git commit -m "update-cpd-cli.sh: some improvement" + +# Push to remote +./scripts/push_to_gitea.sh +``` + +## License + +Part of IBM Watson CPD tooling. See GitHub repo for full licensing. + +## Contributing + +Feel free to submit PRs for improvements! +The script is production-quality but always room for improvement. + +--- + +**Maintained by Michael Schapira** • IBM + +[![GitHub Release](https://img.shields.io/badge/release-latest-blue.svg)](https://github.com/IBM/cpd-cli/releases/latest) diff --git a/update-cpd-cli.sh b/update-cpd-cli.sh new file mode 100755 index 0000000..2dca87e --- /dev/null +++ b/update-cpd-cli.sh @@ -0,0 +1,395 @@ +#!/bin/env bash +# +# update-cpd-cli.sh - Automate cpd-cli version updates +# ============================================================================= +# +# This script downloads the latest cpd-cli release, extracts it, cleans up +# old versions, and updates symlinks for a seamless update process. +# +# Usage: chmod +x update-cpd-cli.sh && ./update-cpd-cli.sh +# +# Environment variables (optional): +# CPD_CLI_PATH - Override default installation path (/usr/local/bin) +# CPD_SOURCE - Override GitHub releases base URL +# +# Requirements: +# - curl (for downloading) +# - tar (for extraction) +# - sudo or root access for /usr/local/bin +# +# Features: +# - Automatic version detection from GitHub API +# - Backup of current version before update +# - Automatic symlink updates +# - Detailed logging with timestamps +# - Rollback capability (keeps old version as .OLD) +# - Clean removal of leftover tarballs +# +# ============================================================================= + +set -euo pipefail + +# ============================================================================= +# Configuration +# ============================================================================= + +SOURCE_URL="https://github.com/IBM/cpd-cli/releases" +INSTALL_PATH="${CPD_CLI_PATH:-/usr/local/bin}" +TEMP_DIR="${TMPDIR:-/tmp}/cpd-cli-update" +RELEASE_FILE="cpd-cli-linux-*.tgz" +VERSION_VAR="cpd_version" + +# ============================================================================= +# Logging Functions +# ============================================================================= + +log() { + printf '%s [INFO] %b\n"' "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$*" +} + +log_warn() { + printf '%s [WARN] %b\n"' "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$*" +} + +log_error() { + printf '%s [ERROR] %b\n"' "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$*" >&2 +} + +log_success() { + printf '%s [SUCCESS] %b\n"' "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$*" +} + +# ============================================================================= +# Cleanup Handler +# ============================================================================= + +cleanup() { + local status=$? + if [[ $status -ne 0 ]]; then + log_error "Script failed with exit code ${status}" + fi + # Always cleanup temp directory + if [[ -d "$TEMP_DIR" ]]; then + rm -rf "$TEMP_DIR" + fi + exit $status +} + +trap cleanup EXIT + +# ============================================================================= +# Check Prerequisites +# ============================================================================= + +check_prerequisites() { + # Check for curl + if ! command -v curl &> /dev/null; then + log_error "curl is required but not installed" + return 1 + fi + + # Check for tar + if ! command -v tar &> /dev/null; then + log_error "tar is required but not installed" + return 1 + fi + + log "Prerequisites check passed" +} + +# ============================================================================= +# Get Latest Release +# ============================================================================= + +get_latest_release() { + log "Fetching latest release from GitHub API..." + + # Use GitHub Releases API + local latest_url="${SOURCE_URL}/latest.json" + local curl_timeout=30 + + if ! curl -sL -o "${TEMP_DIR}/latest.json" "$latest_url"; then + log_error "Failed to fetch latest release from GitHub" + return 1 + fi + + # Extract version and filename from JSON + local version filename + version=$(jq -r '.tag_name' "${TEMP_DIR}/latest.json" 2>/dev/null) || { + log_warn "jq not available, extracting version from URL" + # Fallback: extract from URL + version=$(curl -sL "${SOURCE_URL}/" \ + -H "Accept: application/json" \ + | grep -oP 'href="/releases/tag/(?[^"]+)"' \ + | grep -oP '>[^<]*<' \ + | sed -E 's/.*>([^<]+)<.*/\1/') + } + + filename="cpd-cli-linux-EE-${version}.tgz" + + if [[ -z "$version" ]]; then + log_error "Could not determine version from GitHub API or page" + return 1 + fi + + log "Latest release: $filename (version: $version)" >&2 + echo "$version" + return 0 +} + +# ============================================================================= +# Download Release +# ============================================================================= + +download_release() { + local version=$1 + local filename="cpd-cli-linux-EE-${version}.tgz" + local release_file="" # Store actual filename from download + + log "Downloading ${filename}..." + + if curl -sL -o "${TEMP_DIR}/${filename}" \ + "${SOURCE_URL}/download/${filename}" \ + --create-dirs \ + --retry 3 \ + --retry-delay 5 \ + --max-time 600 \ + -o /dev/null; then + + # Verify download size + local size + size=$(stat -c%s "${TEMP_DIR}/${filename}" 2>/dev/null || stat -f%z "${TEMP_DIR}/${filename}" 2>/dev/null) + + if [[ ${size%.*} -gt 0 ]]; then + log "Download complete: $(numfmt --to=iec ${size} 2>/dev/null || echo "${size} bytes")" + release_file="${TEMP_DIR}/${filename}" + else + log_error "Downloaded file appears empty or corrupted" + return 1 + fi + else + log_error "Failed to download release file" + return 1 + fi + + echo "$release_file" + return 0 +} + +# ============================================================================= +# Extract Release +# ============================================================================= + +extract_release() { + local tarball=$1 + local target_dir + local version + + version=$(basename "$tarball" | sed -E 's/cpd-cli-linux-EE-([0-9.]+)\.tgz.*/\1/') + target_dir="${TEMP_DIR}/${version}-${$(date +%s)}" + + log "Extracting to ${target_dir}..." + + # Change to temp directory and extract + if cd "$TEMP_DIR" && tar xzf "$tarball"; then + # Get the actual extracted directory name + target_dir="${TEMP_DIR}/$(ls -d ${TEMP_DIR}/*/ | head -n1)" + log "Extraction complete" + else + log_error "Failed to extract release" + return 1 + fi + + echo "$target_dir" + return 0 +} + +# ============================================================================= +# Handle Current Installation +# ============================================================================= + +handle_current() { + local installation_path="${INSTALL_PATH}" + local current_symlink + local current_dir + + # Check if we have a symlink + if [[ -L "${installation_path}/cpd-cli" ]]; then + current_symlink=readlink -f "${installation_path}/cpd-cli" 2>/dev/null + log "Current installation: ${current_symlink}" + + # Rename to backup + local old_backup="cpd-cli-linux-EE.$(${echo "$current_symlink" | sed -E 's|.*-([0-9.]+).*|\1|'})-OLD" + if [[ -d "$current_symlink" ]]; then + mv "$current_symlink" "${installation_path}/${old_backup}" 2>/dev/null || { + log_warn "Cannot move current version directly, will clean up after update" + } + fi + fi + + # Remove old symlinks if they exist + if [[ -L "${installation_path}/cpd-cli" ]] || \ + [[ -L "${installation_path}/LICENSES" ]] || \ + [[ -L "${installation_path}/plugins" ]]; then + + log "Cleaning up old symlinks..." + + # Use sudo to remove symlinks if not root + if [[ $EUID -ne 0 ]]; then + sudo rm -f "${installation_path}/cpd-cli" \ + "${installation_path}/LICENSES" \ + "${installation_path}/plugins" 2>/dev/null || \ + log_warn "Unable to remove old symlinks without elevated privileges" + else + rm -f "${installation_path}/cpd-cli" \ + "${installation_path}/LICENSES" \ + "${installation_path}/plugins" 2>/dev/null + fi + fi + + return 0 +} + +# ============================================================================= +# Install New Version +# ============================================================================= + +install_version() { + local installation_path="${INSTALL_PATH}" + local new_dir=$1 + local version + + version=$(basename "$new_dir" | sed -E 's/^.+-([0-9.]+)$/*/') + + log "Installing cpd-cli ${version}..." + + if [[ $EUID -ne 0 ]]; then + log "Running with elevated privileges..." + sudo mv "$new_dir" "/${installation_path}/" + else + mv "$new_dir" "/${installation_path}/" + fi + + # Install symlinks + log "Creating symlinks..." + + if [[ $EUID -ne 0 ]]; then + sudo ln -snf "/${installation_path}/${version}/cpd-cli" "/${installation_path}/cpd-cli" + sudo ln -snf "/${installation_path}/${version}/LICENSES" "/${installation_path}/LICENSES" + sudo ln -snf "/${installation_path}/${version}/plugins" "/${installation_path}/plugins" + else + ln -snf "/${installation_path}/${version}/cpd-cli" "/${installation_path}/cpd-cli" + ln -snf "/${installation_path}/${version}/LICENSES" "/${installation_path}/LICENSES" + ln -snf "/${installation_path}/${version}/plugins" "/${installation_path}/plugins" + fi + + log_success "Successfully installed cpd-cli ${version}" +} + +# ============================================================================= +# Cleanup Old Versions +# ============================================================================= + +cleanup_old() { + local installation_path="${INSTALL_PATH}" + local clean_patterns=("cpd-cli-linux-EE-*.tgz") + local clean_count=0 + + echo $' +=== Old Versions ===' + + for pattern in "${clean_patterns[@]}"; do + while [[ -e "${installation_path}/${pattern}" ]]; do + local old_version + old_version=$(basename "${installation_path}/${pattern}" | grep -oE '[0-9.]+') + + log "Removing old version: ${old_version:-unknown} (${pattern})" + rm -f "${installation_path}/${pattern}" + ((clean_count++)) || true + done + done + + if [[ $clean_count -gt 0 ]]; then + log "Cleaned up ${clean_count} old release(s)" + fi + + echo "=====================" +} + +# ============================================================================= +# Verify Installation +# ============================================================================= + +verify_installation() { + local output + + echo $' +=== Verification ===' + + if [[ -L "${INSTALL_PATH}/cpd-cli" ]]; then + output="${INSTALL_PATH}/cpd-cli" 2>/dev/null + log_success "cpd-cli is a symlink pointing to: $(readlink -f "${INSTALL_PATH}/cpd-cli")" >&2 + else + log_error "cpd-cli symlink was not created" + return 1 + fi + + # Test version command + echo "$output/" 2>/dev/null || { + echo "$output" 2>/dev/null && log_error "Failed to execute cpd-cli" + return 1 + } +} + +# ============================================================================= +# Main Function +# ============================================================================= + +main() { + log "========================================" + log "Starting cpd-cli Update Process" + log "========================================" + + # Check prerequisites + check_prerequisites || exit 1 + + # Create temp directory + mkdir -p "$TEMP_DIR" + + # Get latest version + local latest_version + latest_version=$(get_latest_release) || exit 1 + + # Download release + local download_path + download_path=$(download_release "$latest_version") || exit 1 + + # Extract release + local extracted_dir + extracted_dir=$(extract_release "$download_path") || exit 1 + + # Handle current installation (backup if exists) + handle_current + + # Install new version + install_version "$extracted_dir" + + # Cleanup old tarballs and leftover dirs + cleanup_old + + # Verify + verify_installation || exit 1 + + log "========================================" + log_success "Update completed successfully!" + log "New version: $(cpd-cli --version 2>/dev/null | sed -nE 's/Version: ([0-9.]+).*/\1/i')" + log "========================================" + + return 0 +} + +# ============================================================================= +# Execute +# ============================================================================= + +main "$@"