Created
October 7, 2025 05:03
-
-
Save jth0/bcebb77b4817c472008f83e31d3e7f85 to your computer and use it in GitHub Desktop.
Shell script to build & deploy a docker image for Posit Workbench in GCP Artifact Registry (GAR)
This file contains hidden or 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
| #!/usr/bin/env bash | |
| # Script for building & pushing a docker image to Google Cloud (GCP) Artifact Registry (GAR) | |
| # Not a ton of "special sauce" here, and you may be able to tell by the emoji that it's been | |
| # AI-enhanced. Major happy paths tested, not all edge cases have been fully vetted though. | |
| # So far only found one hallucination and one set of parameters where code was added but the | |
| # help output and runtime didn't actually reference the var/param or run the code... | |
| set -euo pipefail | |
| # Script configuration and defaults | |
| SCRIPT_NAME="$(basename "$0")" | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| # Default values - these are all overridable with CLI parameters | |
| DEFAULT_REGION="us-central1" # Let's go, No Coast! | |
| DEFAULT_PROJECT="MyProjectNameHere" | |
| DEFAULT_REPO="MyhRepoNameHere" | |
| DEFAULT_IMAGE="MyImageNameHere" | |
| DEFAULT_TAG="test" # A novel idea... | |
| DEFAULT_R_VERSION="4.4.3" # Manually put here, but should get parsed/updated based on base image selected and/or latest found | |
| DEFAULT_PYTHON_VERSION="3.12.11" # ditto | |
| # Initialize variables with defaults | |
| REGION="${DEFAULT_REGION}" | |
| PROJECT="${DEFAULT_PROJECT}" | |
| REPO="${DEFAULT_REPO}" | |
| IMAGE="${DEFAULT_IMAGE}" | |
| TAG="${DEFAULT_TAG}" | |
| R_VERSION="${DEFAULT_R_VERSION}" | |
| PYTHON_VERSION="${DEFAULT_PYTHON_VERSION}" | |
| # Variables for Dockerfile parsing | |
| DOCKERFILE_BASE_IMAGE="" | |
| DOCKERFILE_R_VERSION="" | |
| DOCKERFILE_PYTHON_VERSION="" | |
| DOCKERFILE_UPDATED=false | |
| # Configuration flags | |
| SKIP_CONFIRMATION=false | |
| SKIP_PUSH=false | |
| NO_CACHE=true | |
| PULL=true | |
| PLATFORM="linux/amd64" | |
| AUTO_UPDATE=false | |
| # Track if versions were explicitly specified | |
| R_VERSION_SPECIFIED=false | |
| PYTHON_VERSION_SPECIFIED=false | |
| # Colors for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| CYAN='\033[0;36m' | |
| MAGENTA='\033[0;35m' | |
| NC='\033[0m' # No Color | |
| # Logging functions | |
| log_info() { | |
| echo -e "${BLUE}ℹ️ $1${NC}" | |
| } | |
| log_success() { | |
| echo -e "${GREEN}✅ $1${NC}" | |
| } | |
| log_warning() { | |
| echo -e "${YELLOW}⚠️ $1${NC}" | |
| } | |
| log_error() { | |
| echo -e "${RED}❌ $1${NC}" >&2 | |
| } | |
| log_header() { | |
| echo -e "${CYAN}🐳 $1${NC}" | |
| } | |
| log_update() { | |
| echo -e "${MAGENTA}🔄 $1${NC}" | |
| } | |
| # Help function | |
| show_help() { | |
| cat << EOF | |
| ${SCRIPT_NAME} - Build and deploy Docker images to Google Artifact Registry | |
| USAGE: | |
| ${SCRIPT_NAME} [OPTIONS] | |
| OPTIONS: | |
| -t, --tag TAG Docker image tag (default: ${DEFAULT_TAG}) | |
| -r, --region REGION GCP region (default: ${DEFAULT_REGION}) | |
| -p, --project PROJECT GCP project ID (default: ${DEFAULT_PROJECT}) | |
| -R, --repo REPO Artifact Registry repository (default: ${DEFAULT_REPO}) | |
| -i, --image IMAGE Image name (default: ${DEFAULT_IMAGE}) | |
| --r-version VERSION R version (default: ${DEFAULT_R_VERSION}) | |
| --python-version VERSION Python version (default: ${DEFAULT_PYTHON_VERSION}) | |
| --platform PLATFORM Target platform (default: ${PLATFORM}) | |
| --cache Use Docker cache (default: no-cache) | |
| --no-pull Don't pull base images | |
| --skip-push Build only, don't push to registry | |
| --auto-update Automatically check for and prompt to update base image | |
| --yes Skip confirmation prompt | |
| -h, --help Show this help message | |
| EXAMPLES: | |
| ${SCRIPT_NAME} --tag v1.2.3 | |
| ${SCRIPT_NAME} --tag latest --yes | |
| ${SCRIPT_NAME} --tag dev --r-version 4.4.1 --python-version 3.11.10 | |
| ${SCRIPT_NAME} --skip-push --tag local-test --auto-update | |
| EOF | |
| } | |
| # Parse command line arguments | |
| parse_args() { | |
| while [[ $# -gt 0 ]]; do | |
| case $1 in | |
| -t|--tag) | |
| TAG="$2" | |
| shift 2 | |
| ;; | |
| -r|--region) | |
| REGION="$2" | |
| shift 2 | |
| ;; | |
| -p|--project) | |
| PROJECT="$2" | |
| shift 2 | |
| ;; | |
| -R|--repo) | |
| REPO="$2" | |
| shift 2 | |
| ;; | |
| -i|--image) | |
| IMAGE="$2" | |
| shift 2 | |
| ;; | |
| --r-version) | |
| R_VERSION="$2" | |
| R_VERSION_SPECIFIED=true | |
| shift 2 | |
| ;; | |
| --python-version) | |
| PYTHON_VERSION="$2" | |
| PYTHON_VERSION_SPECIFIED=true | |
| shift 2 | |
| ;; | |
| --platform) | |
| PLATFORM="$2" | |
| shift 2 | |
| ;; | |
| --cache) | |
| NO_CACHE=false | |
| shift | |
| ;; | |
| --no-pull) | |
| PULL=false | |
| shift | |
| ;; | |
| --skip-push) | |
| SKIP_PUSH=true | |
| shift | |
| ;; | |
| --auto-update) | |
| AUTO_UPDATE=true | |
| shift | |
| ;; | |
| --yes) | |
| SKIP_CONFIRMATION=true | |
| shift | |
| ;; | |
| -h|--help) | |
| show_help | |
| exit 0 | |
| ;; | |
| *) | |
| log_error "Unknown option: $1" | |
| echo "Use --help for usage information." | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| } | |
| # Parse Dockerfile to extract base image and versions | |
| parse_dockerfile() { | |
| local dockerfile="${SCRIPT_DIR}/Dockerfile" | |
| if [[ ! -f "$dockerfile" ]]; then | |
| log_error "Dockerfile not found in ${SCRIPT_DIR}" | |
| exit 1 | |
| fi | |
| log_info "Parsing Dockerfile for base image and versions..." | |
| # Extract base image from FROM instruction (get the active one, not commented) | |
| DOCKERFILE_BASE_IMAGE=$(grep -E "^FROM " "$dockerfile" | head -1 | awk '{print $2}' | tr -d '\r') | |
| if [[ -z "$DOCKERFILE_BASE_IMAGE" ]]; then | |
| log_error "Could not find FROM instruction in Dockerfile" | |
| exit 1 | |
| fi | |
| # Parse versions from the base image tag | |
| # Format: rstudio/workbench-session:ubuntu2204-r4.4.3_4.3.3-py3.12.11_3.11.13 | |
| local image_tag="${DOCKERFILE_BASE_IMAGE##*:}" | |
| # Extract R versions (primary and secondary) | |
| # Pattern: r4.4.3_4.3.3 -> we want the first one (4.4.3) | |
| if [[ "$image_tag" =~ r([0-9]+\.[0-9]+\.[0-9]+)_ ]]; then | |
| DOCKERFILE_R_VERSION="${BASH_REMATCH[1]}" | |
| else | |
| log_warning "Could not parse R version from base image tag: $image_tag" | |
| DOCKERFILE_R_VERSION="${DEFAULT_R_VERSION}" | |
| fi | |
| # Extract Python versions (primary and secondary) | |
| # Pattern: py3.12.11_3.11.13 -> we want the first one (3.12.11) | |
| if [[ "$image_tag" =~ py([0-9]+\.[0-9]+\.[0-9]+)_ ]]; then | |
| DOCKERFILE_PYTHON_VERSION="${BASH_REMATCH[1]}" | |
| else | |
| log_warning "Could not parse Python version from base image tag: $image_tag" | |
| DOCKERFILE_PYTHON_VERSION="${DEFAULT_PYTHON_VERSION}" | |
| fi | |
| log_success "Dockerfile parsed successfully" | |
| echo " • Base Image: ${DOCKERFILE_BASE_IMAGE}" | |
| echo " • R Version (from tag): ${DOCKERFILE_R_VERSION}" | |
| echo " • Python Version (from tag): ${DOCKERFILE_PYTHON_VERSION}" | |
| echo | |
| } | |
| # Update Dockerfile with new base image | |
| update_dockerfile_base_image() { | |
| local new_base_image="$1" | |
| local dockerfile="${SCRIPT_DIR}/Dockerfile" | |
| log_update "Updating Dockerfile with new base image: ${new_base_image}" | |
| # Create backup | |
| cp "$dockerfile" "${dockerfile}.backup.$(date +%Y%m%d_%H%M%S)" | |
| # Update the FROM line | |
| sed -i.tmp "s|^FROM .*|FROM ${new_base_image}|" "$dockerfile" | |
| rm -f "${dockerfile}.tmp" | |
| DOCKERFILE_BASE_IMAGE="$new_base_image" | |
| DOCKERFILE_UPDATED=true | |
| log_success "Dockerfile updated successfully" | |
| } | |
| # Check version specifications and warn if unnecessary | |
| check_version_warnings() { | |
| local warnings_shown=false | |
| if [[ "$R_VERSION_SPECIFIED" = true ]]; then | |
| if [[ "$R_VERSION" = "$DOCKERFILE_R_VERSION" ]]; then | |
| log_warning "R version ${R_VERSION} matches the base image version" | |
| log_info " The --r-version parameter is not needed - the base image already has R ${DOCKERFILE_R_VERSION}" | |
| warnings_shown=true | |
| else | |
| log_info "Custom R version specified: ${R_VERSION} (will override base image's ${DOCKERFILE_R_VERSION})" | |
| fi | |
| fi | |
| if [[ "$PYTHON_VERSION_SPECIFIED" = true ]]; then | |
| if [[ "$PYTHON_VERSION" = "$DOCKERFILE_PYTHON_VERSION" ]]; then | |
| log_warning "Python version ${PYTHON_VERSION} matches the base image version" | |
| log_info " The --python-version parameter is not needed - the base image already has Python ${DOCKERFILE_PYTHON_VERSION}" | |
| warnings_shown=true | |
| else | |
| log_info "Custom Python version specified: ${PYTHON_VERSION} (will override base image's ${DOCKERFILE_PYTHON_VERSION})" | |
| fi | |
| fi | |
| if [[ "$warnings_shown" = true ]]; then | |
| echo | |
| log_info "💡 Tip: Remove unnecessary version parameters for faster builds that use base image versions" | |
| echo | |
| fi | |
| } | |
| # Validate prerequisites | |
| validate_prerequisites() { | |
| log_info "Validating prerequisites..." | |
| # Check if Docker is available | |
| if ! command -v docker &> /dev/null; then | |
| log_error "Docker is not installed or not in PATH" | |
| exit 1 | |
| fi | |
| # Check if gcloud is available | |
| if ! command -v gcloud &> /dev/null; then | |
| log_error "gcloud CLI is not installed or not in PATH" | |
| exit 1 | |
| fi | |
| # Check if Dockerfile exists | |
| if [[ ! -f "${SCRIPT_DIR}/Dockerfile" ]]; then | |
| log_error "Dockerfile not found in ${SCRIPT_DIR}" | |
| exit 1 | |
| fi | |
| # Check Docker daemon | |
| if ! docker info &> /dev/null; then | |
| log_error "Docker daemon is not running" | |
| exit 1 | |
| fi | |
| # Check for optional tools | |
| if ! command -v jq &> /dev/null; then | |
| log_warning "jq not found - base image update checks will be limited" | |
| fi | |
| if ! command -v curl &> /dev/null; then | |
| log_warning "curl not found - base image update checks will be limited" | |
| fi | |
| log_success "Prerequisites validated" | |
| } | |
| # Build full image name | |
| build_image_name() { | |
| FULL_IMAGE="${REGION}-docker.pkg.dev/${PROJECT}/${REPO}/${IMAGE}:${TAG}" | |
| } | |
| # Show build summary | |
| show_summary() { | |
| log_header "Docker Build Summary" | |
| echo | |
| echo "📋 Build Configuration:" | |
| echo " • Image Name: ${FULL_IMAGE}" | |
| echo " • Base Image: ${DOCKERFILE_BASE_IMAGE}$([ "$DOCKERFILE_UPDATED" = true ] && echo " (updated)" || echo "")" | |
| echo " • Platform: ${PLATFORM}" | |
| echo " • R Version: ${R_VERSION}$([ "$R_VERSION_SPECIFIED" = false ] && echo " (default)" || echo "")" | |
| echo " • Python Version: ${PYTHON_VERSION}$([ "$PYTHON_VERSION_SPECIFIED" = false ] && echo " (default)" || echo "")" | |
| echo " • Build Context: ${SCRIPT_DIR}" | |
| echo " • Use Cache: $([ "$NO_CACHE" = true ] && echo "No" || echo "Yes")" | |
| echo " • Pull Base: $([ "$PULL" = true ] && echo "Yes" || echo "No")" | |
| echo | |
| echo "🚀 Actions to be performed:" | |
| echo " 1. Configure Docker authentication for Artifact Registry" | |
| echo " 2. Build Docker image with specified parameters" | |
| if [[ "$SKIP_PUSH" = false ]]; then | |
| echo " 3. Push image to Google Artifact Registry" | |
| else | |
| echo " 3. Skip pushing to registry (--skip-push specified)" | |
| fi | |
| echo | |
| } | |
| # Confirm before proceeding | |
| confirm_action() { | |
| if [[ "$SKIP_CONFIRMATION" = true ]]; then | |
| return 0 | |
| fi | |
| echo -n "Do you want to proceed with the build? [y/N]: " | |
| read -r response | |
| case "$response" in | |
| [yY][eE][sS]|[yY]) | |
| return 0 | |
| ;; | |
| *) | |
| log_info "Operation cancelled by user" | |
| exit 0 | |
| ;; | |
| esac | |
| } | |
| # Configure Docker authentication to GCP Artifact Registry (GAR) | |
| # If this fails, most likely just need to run `gcloud auth login` or `glcoud auth application-default login` -- not sure which | |
| configure_docker_auth() { | |
| log_info "Configuring Docker for Artifact Registry..." | |
| if ! gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet; then | |
| log_error "Failed to configure Docker authentication" | |
| exit 1 | |
| fi | |
| log_success "Docker authentication configured" | |
| } | |
| # Build Docker image | |
| build_image() { | |
| log_info "Building image ${FULL_IMAGE}..." | |
| # Build docker command | |
| local docker_cmd=( | |
| "docker" "build" | |
| "--platform=${PLATFORM}" | |
| "--provenance=false" # disable attestations / extra images in deployment | |
| "--build-arg" "R_VERSION=${R_VERSION}" # the Dockerfile refers to this parameter when installing/updating R pkgs | |
| "--build-arg" "PYTHON_VERSION=${PYTHON_VERSION}" # the Dockerfile doesn't touch this but I left it anyway | |
| "-t" "${FULL_IMAGE}" | |
| ) | |
| if [[ "$NO_CACHE" = true ]]; then | |
| docker_cmd+=("--no-cache") | |
| fi | |
| if [[ "$PULL" = true ]]; then | |
| docker_cmd+=("--pull") | |
| fi | |
| docker_cmd+=("${SCRIPT_DIR}") | |
| # Execute build command | |
| if ! "${docker_cmd[@]}"; then | |
| log_error "Docker build failed" | |
| exit 1 | |
| fi | |
| log_success "Image built successfully" | |
| } | |
| # Push image to registry | |
| push_image() { | |
| if [[ "$SKIP_PUSH" = true ]]; then | |
| log_info "Skipping image push (--skip-push specified)" | |
| return 0 | |
| fi | |
| log_info "Pushing image to Artifact Registry..." | |
| if ! docker push "${FULL_IMAGE}"; then | |
| log_error "Failed to push image to registry" | |
| exit 1 | |
| fi | |
| log_success "Image pushed successfully" | |
| } | |
| # Enhanced base image update checking with version parsing | |
| check_base_image_updates() { | |
| if [[ -z "$DOCKERFILE_BASE_IMAGE" ]]; then | |
| log_warning "Could not determine base image from Dockerfile" | |
| return 0 | |
| fi | |
| log_info "Current base image: ${DOCKERFILE_BASE_IMAGE}" | |
| log_info " • Current R version: ${DOCKERFILE_R_VERSION}" | |
| log_info " • Current Python version: ${DOCKERFILE_PYTHON_VERSION}" | |
| echo | |
| # Ask user if they want to check for newer versions | |
| if [[ "$AUTO_UPDATE" = false && "$SKIP_CONFIRMATION" = false ]]; then | |
| echo -n "Would you like to check for newer versions of the base image? [y/N]: " | |
| read -r response | |
| case "$response" in | |
| [yY][eE][sS]|[yY]) | |
| check_available_versions | |
| ;; | |
| *) | |
| log_info "Skipping version check - continuing with current base image" | |
| return 0 | |
| ;; | |
| esac | |
| elif [[ "$AUTO_UPDATE" = true ]]; then | |
| log_info "Auto-update enabled - checking for newer versions..." | |
| check_available_versions | |
| fi | |
| } | |
| # Function to check and list available versions | |
| check_available_versions() { | |
| local image_name tag_part | |
| if [[ "$DOCKERFILE_BASE_IMAGE" == *":"* ]]; then | |
| image_name="${DOCKERFILE_BASE_IMAGE%:*}" | |
| tag_part="${DOCKERFILE_BASE_IMAGE##*:}" | |
| else | |
| image_name="$DOCKERFILE_BASE_IMAGE" | |
| tag_part="latest" | |
| fi | |
| log_info "Checking available versions for ${image_name}..." | |
| # Method 1: Try using Docker Hub API for rstudio images | |
| if [[ "$image_name" == "rstudio/workbench-session" ]]; then | |
| check_rstudio_versions "$image_name" "$tag_part" | |
| else | |
| # Method 2: Try using skopeo for other registries | |
| check_versions_with_skopeo "$image_name" "$tag_part" | |
| fi | |
| } | |
| # Check rstudio/workbench-session versions using Docker Hub API | |
| check_rstudio_versions() { | |
| local image_name="$1" | |
| local current_tag="$2" | |
| log_info "Querying Docker Hub for ${image_name} tags..." | |
| # Use Docker Hub API to get tags | |
| local api_url="https://hub.docker.com/v2/repositories/${image_name}/tags/?page_size=100" | |
| local available_tags | |
| if command -v curl &> /dev/null && command -v jq &> /dev/null; then | |
| available_tags=$(curl -s "$api_url" | jq -r '.results[].name' 2>/dev/null) | |
| if [[ $? -eq 0 && -n "$available_tags" ]]; then | |
| # Filter for ubuntu2204 tags and extract version info | |
| local ubuntu_tags=$(echo "$available_tags" | grep "ubuntu2204-r" | sort -V) | |
| if [[ -n "$ubuntu_tags" ]]; then | |
| log_success "Found available versions:" | |
| echo | |
| local current_found=false | |
| local newer_versions=() | |
| local current_r_version current_python_version | |
| # Parse current versions for comparison | |
| if [[ "$current_tag" =~ r([0-9]+\.[0-9]+\.[0-9]+)_ ]]; then | |
| current_r_version="${BASH_REMATCH[1]}" | |
| fi | |
| if [[ "$current_tag" =~ py([0-9]+\.[0-9]+\.[0-9]+)_ ]]; then | |
| current_python_version="${BASH_REMATCH[1]}" | |
| fi | |
| echo "Available tags (showing last 10):" | |
| echo "$ubuntu_tags" | tail -10 | while read -r tag; do | |
| local tag_r_version tag_python_version | |
| # Parse versions from tag | |
| if [[ "$tag" =~ r([0-9]+\.[0-9]+\.[0-9]+)_ ]]; then | |
| tag_r_version="${BASH_REMATCH[1]}" | |
| fi | |
| if [[ "$tag" =~ py([0-9]+\.[0-9]+\.[0-9]+)_ ]]; then | |
| tag_python_version="${BASH_REMATCH[1]}" | |
| fi | |
| # Mark current version and potential newer versions | |
| if [[ "$tag" == "$current_tag" ]]; then | |
| echo " ${tag} (CURRENT) - R:${tag_r_version}, Python:${tag_python_version}" | |
| current_found=true | |
| else | |
| echo " ${tag} - R:${tag_r_version}, Python:${tag_python_version}" | |
| # Simple version comparison (newer if R or Python version is higher) | |
| if [[ -n "$tag_r_version" && -n "$current_r_version" ]] && | |
| version_greater_than "$tag_r_version" "$current_r_version"; then | |
| newer_versions+=("$tag") | |
| elif [[ -n "$tag_python_version" && -n "$current_python_version" ]] && | |
| version_greater_than "$tag_python_version" "$current_python_version"; then | |
| newer_versions+=("$tag") | |
| fi | |
| fi | |
| done | |
| echo | |
| # Show newer versions if found | |
| if [[ ${#newer_versions[@]} -gt 0 ]]; then | |
| log_update "Potentially newer versions detected:" | |
| for version in "${newer_versions[@]}"; do | |
| echo " • $version" | |
| done | |
| echo | |
| prompt_for_update "$image_name" | |
| else | |
| log_success "Your current version appears to be up to date!" | |
| fi | |
| else | |
| log_warning "No ubuntu2204 tags found" | |
| fallback_version_check "$image_name" | |
| fi | |
| else | |
| log_warning "Failed to query Docker Hub API" | |
| fallback_version_check "$image_name" | |
| fi | |
| else | |
| log_warning "curl or jq not available for API queries" | |
| fallback_version_check "$image_name" | |
| fi | |
| } | |
| # Check versions using skopeo (for non-Docker Hub registries) | |
| check_versions_with_skopeo() { | |
| local image_name="$1" | |
| local current_tag="$2" | |
| if command -v skopeo &> /dev/null && command -v jq &> /dev/null; then | |
| log_info "Using skopeo to check available tags..." | |
| local available_tags | |
| available_tags=$(skopeo list-tags "docker://${image_name}" 2>/dev/null | jq -r '.Tags[]' 2>/dev/null) | |
| if [[ $? -eq 0 && -n "$available_tags" ]]; then | |
| echo "Available tags:" | |
| echo "$available_tags" | sort -V | tail -10 | |
| echo | |
| prompt_for_update "$image_name" | |
| else | |
| log_warning "Failed to query registry with skopeo" | |
| fallback_version_check "$image_name" | |
| fi | |
| else | |
| log_warning "skopeo or jq not available" | |
| fallback_version_check "$image_name" | |
| fi | |
| } | |
| # Fallback when automatic checking fails | |
| fallback_version_check() { | |
| local image_name="$1" | |
| log_info "💡 To manually check for newer versions:" | |
| if [[ "$image_name" == "rstudio/workbench-session" ]]; then | |
| echo " Visit: https://hub.docker.com/r/rstudio/workbench-session/tags" | |
| else | |
| echo " Run: docker search ${image_name}" | |
| echo " Or visit the registry where this image is hosted" | |
| fi | |
| echo | |
| echo -n "Have you checked and found a newer version you'd like to use? [y/N]: " | |
| read -r response | |
| case "$response" in | |
| [yY][eE][sS]|[yY]) | |
| prompt_for_manual_update "$image_name" | |
| ;; | |
| *) | |
| log_info "Continuing with current base image" | |
| ;; | |
| esac | |
| } | |
| # Prompt user to select or manually enter a new version | |
| prompt_for_update() { | |
| local image_name="$1" | |
| echo -n "Would you like to update to a newer version? [y/N]: " | |
| read -r response | |
| case "$response" in | |
| [yY][eE][sS]|[yY]) | |
| prompt_for_manual_update "$image_name" | |
| ;; | |
| *) | |
| log_info "Continuing with current base image" | |
| ;; | |
| esac | |
| } | |
| # Manual update prompt | |
| prompt_for_manual_update() { | |
| local image_name="$1" | |
| echo -n "Enter the new tag (e.g., ubuntu2204-r4.4.3_4.3.3-py3.12.11_3.11.13): " | |
| read -r new_tag | |
| if [[ -n "$new_tag" ]]; then | |
| local new_image="${image_name}:${new_tag}" | |
| log_info "New base image will be: ${new_image}" | |
| echo -n "Proceed with this update? [y/N]: " | |
| read -r confirm | |
| case "$confirm" in | |
| [yY][eE][sS]|[yY]) | |
| update_dockerfile_base_image "$new_image" | |
| # Re-parse to get new versions | |
| parse_dockerfile | |
| ;; | |
| *) | |
| log_info "Update cancelled" | |
| ;; | |
| esac | |
| else | |
| log_info "No tag provided - continuing with current image" | |
| fi | |
| } | |
| # Simple version comparison function | |
| version_greater_than() { | |
| local version1="$1" | |
| local version2="$2" | |
| # Convert versions to comparable format | |
| local v1_major v1_minor v1_patch | |
| local v2_major v2_minor v2_patch | |
| IFS='.' read -r v1_major v1_minor v1_patch <<< "$version1" | |
| IFS='.' read -r v2_major v2_minor v2_patch <<< "$version2" | |
| # Compare major version | |
| if [[ "$v1_major" -gt "$v2_major" ]]; then | |
| return 0 | |
| elif [[ "$v1_major" -lt "$v2_major" ]]; then | |
| return 1 | |
| fi | |
| # Compare minor version | |
| if [[ "$v1_minor" -gt "$v2_minor" ]]; then | |
| return 0 | |
| elif [[ "$v1_minor" -lt "$v2_minor" ]]; then | |
| return 1 | |
| fi | |
| # Compare patch version | |
| if [[ "$v1_patch" -gt "$v2_patch" ]]; then | |
| return 0 | |
| else | |
| return 1 | |
| fi | |
| } | |
| # Main execution | |
| main() { | |
| log_header "Docker Build & Deploy Script" | |
| echo | |
| # Parse command line arguments | |
| parse_args "$@" | |
| # Build full image name | |
| build_image_name | |
| # Parse Dockerfile for base image and versions | |
| parse_dockerfile | |
| # Check for base image updates | |
| check_base_image_updates | |
| # Check for version warnings | |
| check_version_warnings | |
| # Validate prerequisites | |
| validate_prerequisites | |
| # Show summary and confirm | |
| show_summary | |
| confirm_action | |
| echo | |
| log_header "Starting build process..." | |
| # Execute build steps | |
| configure_docker_auth | |
| build_image | |
| push_image | |
| echo | |
| log_success "Build process completed successfully!" | |
| log_success "Image ready: ${FULL_IMAGE}" | |
| if [[ "$DOCKERFILE_UPDATED" = true ]]; then | |
| log_info "📝 Dockerfile was updated during this build" | |
| log_info " Backup saved with timestamp" | |
| fi | |
| if [[ "$SKIP_PUSH" = false ]]; then | |
| echo | |
| log_info "You can now use this image in your deployments:" | |
| echo " docker pull ${FULL_IMAGE}" | |
| echo " docker run ${FULL_IMAGE}" | |
| fi | |
| } | |
| # Run main function with all arguments | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Buyer beware - make sure your AI checks my AI's work.