From 5262d042f8f63ff0b03d8bcd91c835f2af05cc22 Mon Sep 17 00:00:00 2001 From: sHa Date: Sun, 28 Sep 2025 19:22:40 +0300 Subject: [PATCH] Refactor: Enhance container image management with intelligent auto-discovery and multi-architecture support --- README.md | 95 +++++++++++--- core.just | 134 ++++++++++++++++++++ images/README.md | 301 ++++++++++++++++++++++++++++++++++++++++++++ images/mod.just | 322 ++++++++++++++++++++++++++++++----------------- 4 files changed, 717 insertions(+), 135 deletions(-) create mode 100644 images/README.md diff --git a/README.md b/README.md index d82c29c..9624949 100644 --- a/README.md +++ b/README.md @@ -178,18 +178,25 @@ just volumes list [pattern] # List volumes (option ### 🐋 Images Module (`images`) -**Universal image operations with automatic registry detection** +**Universal image operations with intelligent auto-discovery and cross-platform builds** ```bash -just images build [tag] # Build project image -just images push [tag] # Push image to registry -just images pull [tag] # Pull image from registry +just images build [project] [tag] # Build project image (auto-discovers project and Containerfile) +just images push [project] [tag] # Push image to registry (auto-discovers project) +just images pull [project] [tag] # Pull image from registry (auto-discovers project) just images tag # Tag existing image -just images info [tag] # Show image information -just images clean # Remove project images (with confirmation) -just images build-all # Build all projects with Containerfiles +just images info [project] [tag] # Show image information (auto-discovers project) +just images clean [project] # Remove project images (auto-discovers project, with confirmation) +just images build-all # Build all projects with Containerfiles (uses discovery logic) ``` +**Auto-Discovery Features:** +- **Project Detection**: Automatically uses directory basename when project not specified +- **Containerfile Discovery**: Finds `Containerfile`/`Dockerfile` in current directory or `./docker/` folder +- **Cross-Platform Builds**: Handles macOS compatibility for Linux containers automatically +- **Smart Parameter Parsing**: `just images push latest` correctly interprets "latest" as tag, not project +- **Unified Discovery Logic**: Consistent behavior whether using auto-discovery or explicit project directories + ### 🔐 Registry Module (`registry`) **Container registry authentication management** @@ -202,14 +209,24 @@ just registry check # Check authentication ### 🔧 Core Utilities -**Shared functions and environment checking** +**Shared functions, auto-discovery, and environment checking** ```bash just env-check # Check container runtime availability just _detect_runtime # Internal: Detect Docker/Podman just _detect_compose # Internal: Detect compose command +just _discover_project # Internal: Auto-discover project name from directory +just _discover_containerfile_in [dir] # Internal: Discover Containerfile/Dockerfile in directory +just _discover_containerfile # Internal: Discover Containerfile/Dockerfile in current directory ``` +**Auto-Discovery Functions:** +- **`_discover_project`**: Returns `$(basename $(pwd))` - the current directory name as project name +- **`_discover_containerfile_in`**: Searches for `Containerfile`/`Dockerfile` in specified directory, also checks `./docker/` subfolder when searching current directory +- **`_discover_containerfile`**: Convenience wrapper for current directory discovery + +These functions enable consistent auto-discovery behavior across all modules. + ## 🎨 Modern Just Features ### Built-in Colors @@ -235,12 +252,47 @@ Destructive operations use `[confirm]` attribute: ## 🌍 Environment Configuration -### Required for Registry Operations +### Registry Configuration + +**Authentication vs Registry Namespace** + +The system separates authentication credentials from registry image paths, allowing you to authenticate with your personal account while pushing to organization namespaces. + ```bash -# .env file -GITHUB_USERNAME=your-username -GITHUB_TOKEN=ghp_your-token -REGISTRY=ghcr.io +# .env file - Registry settings +GITHUB_USERNAME=your-auth-username # For authentication (just registry login) +GITHUB_TOKEN=ghp_your-token # For authentication +REGISTRY_NAMESPACE=org-or-username # For image paths (ghcr.io/NAMESPACE/project:tag) +REGISTRY=ghcr.io # Registry URL (optional, defaults to ghcr.io) +``` + +**Configuration Hierarchy:** + +1. **Registry Namespace** (for image paths): + - `REGISTRY_NAMESPACE` (if set) - highest priority + - `GITHUB_USERNAME` (fallback) - backward compatibility + - Error if neither is set + +2. **Registry URL**: + - `REGISTRY` (if set) - custom registry + - `ghcr.io` (default) - GitHub Container Registry + +**Examples:** + +```bash +# Organization setup +GITHUB_USERNAME=john-doe # Personal auth +REGISTRY_NAMESPACE=my-company # Company namespace +# Result: ghcr.io/my-company/project:tag + +# Personal setup +GITHUB_USERNAME=john-doe # Personal auth and namespace +# Result: ghcr.io/john-doe/project:tag + +# Custom registry +REGISTRY=docker.io +REGISTRY_NAMESPACE=myorg +# Result: docker.io/myorg/project:tag ``` ### Optional Overrides @@ -263,17 +315,18 @@ just container start postgres # Start PostgreSQL service just container logs postgres # View logs just postgres check # Test database connection -# Development operations +# Development operations (with auto-discovery) just postgres sql "CREATE DATABASE myapp;" # Create development database -just images build myapp # Build application image -just images push myapp dev # Push development version +just images build # Build application image (auto-discovers project and Containerfile) +just images push dev # Push development version (auto-discovers project, "dev" as tag) +just images push # Push with auto-generated commit tag ``` ### Production Deployment ```bash -# Pull latest images -just images pull myapp latest -just images pull postgres 15 +# Pull latest images (with auto-discovery) +just images pull latest # Pull latest version (auto-discovers project) +just images pull postgres 15 # Pull specific PostgreSQL version # Database operations just postgres restore latest-backup.sql # Restore from backup (with confirmation) @@ -282,6 +335,10 @@ just postgres sql "ANALYZE;" # Optimize database # Volume management just volumes list "production_*" # List production volumes just volumes clean-all compose.prod.yml # Clean up old volumes (with confirmation) + +# Build and deployment +just images build-all # Build all projects with Containerfiles (auto-discovery) +just images info # Show image information (auto-discovers project) ``` ### Multi-Database Setup diff --git a/core.just b/core.just index 6599498..0e89497 100644 --- a/core.just +++ b/core.just @@ -5,6 +5,127 @@ _discover_project: #!/usr/bin/env bash echo "$(basename $(pwd))" +# Enhanced project name detection with priority order +# Priority: PROJECT_NAME env var > auto-detection > folder name +_get_project_name: + #!/usr/bin/env bash + set -euo pipefail + + # 1. Check PROJECT_NAME environment variable (highest priority) + if [ -n "${PROJECT_NAME:-}" ]; then + echo "$PROJECT_NAME" + exit 0 + fi + + # 2. Auto-detection logic + project_name="" + + # Check if we're in root with single Containerfile + if [ -f "Containerfile" ] || [ -f "Dockerfile" ] || [ -f "docker/Containerfile" ] || [ -f "docker/Dockerfile" ]; then + # Check for multiple projects (subdirectories with Containerfiles) + subproject_count=0 + for dir in */; do + if [ -d "$dir" ] && ([ -f "${dir}Containerfile" ] || [ -f "${dir}Dockerfile" ]); then + subproject_count=$((subproject_count + 1)) + fi + done + + if [ $subproject_count -eq 0 ]; then + # Single project in root + project_name="$(basename $(pwd))" + else + # Multiple subprojects detected - this should be handled per-project + project_name="$(basename $(pwd))" + fi + else + # No Containerfile in root, use current directory name + project_name="$(basename $(pwd))" + fi + + # 3. Fallback to current folder basename + if [ -z "$project_name" ]; then + project_name="$(basename $(pwd))" + fi + + echo "$project_name" + +# Get full image name with priority logic +# Priority: IMAGE_NAME env var > built from components +_get_image_name tag="": + #!/usr/bin/env bash + set -euo pipefail + + tag="{{tag}}" + + # 1. Check IMAGE_NAME environment variable (highest priority) + if [ -n "${IMAGE_NAME:-}" ]; then + if [ -n "$tag" ]; then + echo "${IMAGE_NAME}:$tag" + else + echo "$IMAGE_NAME" + fi + exit 0 + fi + + # 2. Build from components + registry="${REGISTRY:-ghcr.io}" + namespace="${REGISTRY_NAMESPACE:-${GITHUB_USERNAME:-}}" + project_name=$(just _get_project_name) + + if [ -z "$namespace" ]; then + echo "Error: REGISTRY_NAMESPACE or GITHUB_USERNAME must be set" >&2 + echo "Set REGISTRY_NAMESPACE in .env file for registry operations" >&2 + exit 1 + fi + + if [ -n "$tag" ]; then + echo "$registry/$namespace/$project_name:$tag" + else + echo "$registry/$namespace/$project_name" + fi + +# Generate default tag based on git or timestamp +_generate_default_tag: + #!/usr/bin/env bash + set -euo pipefail + + if git rev-parse --git-dir >/dev/null 2>&1; then + echo "commit-$(git rev-parse --short HEAD)" + else + echo "build-$(date '+%Y%m%d-%H%M%S')" + fi + +# Detect platforms from Containerfile +_detect_platforms containerfile="": + #!/usr/bin/env bash + set -euo pipefail + + containerfile="{{containerfile}}" + default_platforms="${DEFAULT_PLATFORMS:-linux/amd64,linux/arm64}" + + if [ -z "$containerfile" ]; then + echo "$default_platforms" + exit 0 + fi + + if [ ! -f "$containerfile" ]; then + echo "$default_platforms" + exit 0 + fi + + # Check for --platform specification in FROM statements + if grep -q "FROM.*--platform" "$containerfile"; then + # Extract platform from first FROM statement with --platform + platform=$(grep "FROM.*--platform" "$containerfile" | head -1 | sed -n 's/.*--platform=\([^ ]*\).*/\1/p') + if [ -n "$platform" ]; then + echo "$platform" + else + echo "$default_platforms" + fi + else + echo "$default_platforms" + fi + # Auto-discover containerfile and return path and project info # Usage: _discover_containerfile [directory] # Returns: containerfile_path project_name build_context @@ -49,6 +170,19 @@ _discover_containerfile: # Detect container runtime (Docker or Podman) _detect_runtime: #!/usr/bin/env bash + + # Check if DOCKER environment variable is set (highest priority) + if [ -n "${DOCKER:-}" ]; then + if command -v "$DOCKER" >/dev/null 2>&1; then + echo "$DOCKER" + exit 0 + else + echo "Error: Specified runtime '$DOCKER' not found" >&2 + exit 1 + fi + fi + + # Auto-detect available runtime if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then echo "docker" elif command -v podman >/dev/null 2>&1; then diff --git a/images/README.md b/images/README.md new file mode 100644 index 0000000..98fe1f5 --- /dev/null +++ b/images/README.md @@ -0,0 +1,301 @@ +# Container Images Module + +The images module provides comprehensive container image management capabilities including building, pushing, pulling, tagging, and cleaning images with support for multi-architecture builds and flexible naming conventions. + +## Quick Start + +```bash +# Build image with default settings +just images build + +# Push with auto-generated commit tag +just images push + +# Push latest (tags and pushes all architectures as latest) +just images push latest + +# Show image information +just images info + +# List all project images +just images list + +# Clean project images +just images clean +``` + +## Image Naming Logic + +The module uses a flexible naming system with clear priority order: + +### 1. Full Image Name Override (Highest Priority) +```bash +# .env +IMAGE_NAME=ghcr.io/shkafnik/nikamebel-info +``` + +When `IMAGE_NAME` is set, it's used exactly as specified for all operations. + +### 2. Component-Based Naming (Default) +```bash +# .env +REGISTRY=ghcr.io +REGISTRY_NAMESPACE=shkafnik +PROJECT_NAME=nikamebel-info +``` + +Image name is built as: `{REGISTRY}/{REGISTRY_NAMESPACE}/{PROJECT_NAME}:{TAG}` + +### 3. Project Name Detection Priority +1. **`PROJECT_NAME`** from `.env` (highest priority) +2. **Auto-detection logic**: + - If single Containerfile in root → use root folder name + - If multiple Containerfiles in subfolders → use subfolder name + - If `docker/` subfolder detected → use root folder name +3. **Current folder basename** (fallback) + +## Multi-Architecture Support + +### Default Behavior +- **Always builds**: `linux/amd64` and `linux/arm64` +- **Automatic detection**: Checks Containerfile for platform specifications +- **Platform override**: If `--platform` found in Containerfile, builds only that platform + +### Environment Configuration +```bash +# .env - Override default platforms +DEFAULT_PLATFORMS=linux/arm64,linux/amd64 + +# Single platform builds +DEFAULT_PLATFORMS=linux/amd64 +``` + +### Platform Detection Examples +```dockerfile +# This will build only for linux/amd64 +FROM --platform=linux/amd64 alpine:latest + +# This will build for default platforms (arm64 + amd64) +FROM alpine:latest +``` + +## Tag Management + +### Default Tagging +- **Regular builds**: `commit-{short-git-hash}` (e.g., `commit-a1b2c3d`) +- **Non-git repos**: `build-{timestamp}` (e.g., `build-20240315-143022`) + +### Manual Tags +```bash +# Build with custom tag +just images build "" "v1.2.3" + +# Push specific tag +just images push "" "v1.2.3" +``` + +### Latest Tag Handling +```bash +# Special latest command - finds all existing tags and: +# 1. Tags them as 'latest' +# 2. Pushes all 'latest' images (all architectures) +just images push latest +``` + +## Environment Variables + +### Registry Configuration +```bash +# Registry settings +REGISTRY=ghcr.io # Default: ghcr.io +REGISTRY_NAMESPACE=shkafnik # Required for registry operations +GITHUB_USERNAME=shkafnik # Fallback for REGISTRY_NAMESPACE +GITHUB_TOKEN=ghp_xxxx # Required for private registries + +# Full override (highest priority) +IMAGE_NAME=ghcr.io/custom/path # Overrides all other naming logic +``` + +### Project Configuration +```bash +# Project identification +PROJECT_NAME=nikamebel-info # Override auto-detection + +# Build configuration +DEFAULT_PLATFORMS=linux/arm64,linux/amd64 # Default platforms to build +``` + +### Runtime Configuration +```bash +# Container runtime +DOCKER=podman # Default: docker +DOCKER_COMPOSE=podman-compose # Default: docker compose +``` + +## Commands Reference + +### Build Commands +```bash +# Auto-discover and build with commit tag +just images build + +# Build specific project +just images build myproject + +# Build with custom tag +just images build "" "v1.2.3" + +# Build specific project with tag +just images build myproject "v1.2.3" +``` + +### Push Commands +```bash +# Push with auto-generated tag +just images push + +# Push specific tag +just images push "" "v1.2.3" + +# Push latest (special handling) +just images push latest + +# Push specific project +just images push myproject +``` + +### Pull Commands +```bash +# Pull latest tag +just images pull + +# Pull specific tag +just images pull "" "v1.2.3" + +# Pull specific project +just images pull myproject "v1.2.3" +``` + +### Management Commands +```bash +# Tag existing image +just images tag myproject "new-tag" + +# Show image information +just images info + +# List all project images +just images list + +# Clean project images (with confirmation) +just images clean + +# Build all projects with Containerfiles +just images build-all +``` + +## Architecture Detection + +The module automatically detects the build context and Containerfile location: + +1. **Current directory**: `./Containerfile` or `./Dockerfile` +2. **Docker subdirectory**: `./docker/Containerfile` or `./docker/Dockerfile` +3. **Project subdirectories**: `{project}/Containerfile` or `{project}/Dockerfile` + +## Multi-Platform Build Process + +### For Multi-Platform Images +```bash +# Creates manifest with multiple architectures +docker buildx build --platform linux/amd64,linux/arm64 \ + -t ghcr.io/shkafnik/nikamebel-info:commit-abc123 \ + --push . +``` + +### For Single Platform Images +```bash +# Regular build for detected platform +docker build -t ghcr.io/shkafnik/nikamebel-info:commit-abc123 . +``` + +## Latest Tag Workflow + +When you run `just images push latest`: + +1. **Discovery**: Auto-discover project name +2. **Search**: Find all existing images for the project +3. **Tag**: Tag all found images with `latest` +4. **Push**: Push all `latest` tagged images (all architectures) + +This ensures that `latest` always points to your most recent build across all supported architectures. + +## Error Handling + +### Common Issues and Solutions + +**Missing registry credentials:** +```bash +# Set in .env +REGISTRY_NAMESPACE=your-namespace +GITHUB_TOKEN=your-token +``` + +**Platform build failures:** +```bash +# Check buildx setup +docker buildx create --use +docker buildx inspect --bootstrap +``` + +**Image not found:** +```bash +# List available images +just images list + +# Check project name detection +just images info +``` + +## Examples + +### Basic Workflow +```bash +# Build and push commit-tagged image +just images build +just images push + +# Later, mark as latest and push +just images push latest +``` + +### Custom Project Setup +```bash +# .env configuration +PROJECT_NAME=my-custom-name +REGISTRY_NAMESPACE=myorg +DEFAULT_PLATFORMS=linux/amd64 + +# Build and push +just images build +just images push +``` + +### Multi-Project Repository +```bash +# Build specific project +just images build frontend +just images build backend + +# Build all projects +just images build-all +``` + +## Integration with just-commons + +This module integrates seamlessly with other just-commons modules: + +- **Registry module**: For authentication (`just registry login`) +- **Container module**: For runtime management +- **Core utilities**: For project discovery and runtime detection + +For complete project setup, see the main project documentation. \ No newline at end of file diff --git a/images/mod.just b/images/mod.just index 3bd343a..f6a28a2 100644 --- a/images/mod.just +++ b/images/mod.just @@ -1,6 +1,7 @@ -# Universal container image operations +# Container images module - comprehensive image management +# Supports multi-architecture builds, flexible naming, and registry operations -# Build project image (auto-discovers Dockerfile if project not specified) +# Build project image with multi-architecture support [no-cd] build project="" tag="": #!/usr/bin/env bash @@ -9,10 +10,12 @@ build project="" tag="": project="{{project}}" tag="{{tag}}" - # Determine discovery directory + # Determine discovery directory and project if [ -z "$project" ]; then discovery_dir="." - echo -e "{{BLUE}}🔍 Auto-discovering Dockerfile/Containerfile...{{NORMAL}}" + echo -e "{{BLUE}}🔍 Auto-discovering project...{{NORMAL}}" + project=$(just _get_project_name) + echo -e "{{GREEN}}✓ Using project: $project{{NORMAL}}" else # Check if project directory exists if [ ! -d "$project" ]; then @@ -20,63 +23,90 @@ build project="" tag="": exit 1 fi discovery_dir="$project" - echo -e "{{BLUE}}🔍 Auto-discovering Dockerfile/Containerfile in $project...{{NORMAL}}" + echo -e "{{BLUE}}🔍 Building project: $project{{NORMAL}}" fi # Use unified discovery logic discovery_result=$(just _discover_containerfile_in "$discovery_dir") read -r containerfile discovered_project build_context <<< "$discovery_result" - # Use discovered project name if not explicitly set - if [ -z "$project" ]; then - project="$discovered_project" - fi - echo -e "{{GREEN}}✓ Found $containerfile{{NORMAL}}" # Generate tag if not provided if [ -z "$tag" ]; then - if git rev-parse --git-dir >/dev/null 2>&1; then - tag="commit-$(git rev-parse --short HEAD)" - else - tag="build-$(date '+%Y%m%d-%H%M%S')" - fi + tag=$(just _generate_default_tag) fi - runtime=$(just _detect_runtime) - registry="${REGISTRY:-ghcr.io}" - username="${GITHUB_USERNAME:-}" + # Get image name using new naming logic + image_name=$(just _get_image_name "$tag") - if [ -n "$username" ]; then - image_name="$registry/$username/$project:$tag" - else - image_name="$project:$tag" - fi + # Detect platforms + platforms=$(just _detect_platforms "$containerfile") echo -e "{{BLUE}}Building image: $image_name{{NORMAL}}" echo -e "{{YELLOW}}Using: $containerfile{{NORMAL}}" echo -e "{{YELLOW}}Build context: $build_context{{NORMAL}}" + echo -e "{{YELLOW}}Platforms: $platforms{{NORMAL}}" - # Handle cross-platform builds with macOS compatibility workaround - if grep -q "FROM.*--platform" "$containerfile"; then - echo -e "{{YELLOW}}Cross-platform build detected, using macOS compatibility workaround{{NORMAL}}" + runtime=$(just _detect_runtime) - # Create temporary Containerfile without platform specification for local build - temp_containerfile="/tmp/Containerfile.local.$$" - sed 's/FROM --platform=[^ ]* /FROM /' "$containerfile" > "$temp_containerfile" + # Check if multi-platform build is needed + if [[ "$platforms" == *","* ]]; then + echo -e "{{BLUE}}Multi-platform build detected{{NORMAL}}" - echo -e "{{BLUE}}Building without platform emulation (will be multi-arch compatible){{NORMAL}}" - $runtime build -f "$temp_containerfile" -t "$image_name" --load "$build_context" + # Use buildx for multi-platform + if ! $runtime buildx version >/dev/null 2>&1; then + echo "{{BOLD}}{{RED}}Error:{{NORMAL}} buildx required for multi-platform builds" >&2 + echo "{{YELLOW}}Run: $runtime buildx create --use{{NORMAL}}" >&2 + exit 1 + fi - # Clean up temp file - rm -f "$temp_containerfile" + # Ensure buildx builder exists and is active + if ! $runtime buildx inspect >/dev/null 2>&1; then + echo -e "{{BLUE}}Setting up buildx builder...{{NORMAL}}" + $runtime buildx create --use --name just-commons-builder >/dev/null 2>&1 || true + fi + + # For multi-platform builds, we can't load locally, so build for local platform only + local_platform="linux/$(uname -m)" + if [ "$(uname -m)" = "x86_64" ]; then + local_platform="linux/amd64" + elif [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then + local_platform="linux/arm64" + fi + + echo -e "{{YELLOW}}Multi-platform builds cannot be loaded locally{{NORMAL}}" + echo -e "{{BLUE}}Building for local platform: $local_platform{{NORMAL}}" + + $runtime buildx build \ + --platform "$local_platform" \ + -f "$containerfile" \ + -t "$image_name" \ + --load \ + "$build_context" else - $runtime build -f "$containerfile" -t "$image_name" --load "$build_context" + echo -e "{{BLUE}}Single platform build{{NORMAL}}" + + # Handle single platform builds with potential platform specification + if grep -q "FROM.*--platform" "$containerfile"; then + echo -e "{{YELLOW}}Platform specified in Containerfile, using compatibility mode{{NORMAL}}" + + # Create temporary Containerfile without platform specification for local build + temp_containerfile="/tmp/Containerfile.local.$$" + sed 's/FROM --platform=[^ ]* /FROM /' "$containerfile" > "$temp_containerfile" + + $runtime build -f "$temp_containerfile" -t "$image_name" "$build_context" + + # Clean up temp file + rm -f "$temp_containerfile" + else + $runtime build -f "$containerfile" -t "$image_name" "$build_context" + fi fi echo -e "{{GREEN}}✓ Successfully built: $image_name{{NORMAL}}" -# Push project image to registry +# Push project image to registry with smart latest handling [no-cd] push project="" tag="": #!/usr/bin/env bash @@ -85,39 +115,85 @@ push project="" tag="": project="{{project}}" tag="{{tag}}" + # Handle case where user provides tag without project (e.g., "just images push latest") + if [ -n "$project" ] && [ -z "$tag" ] && [[ "$project" =~ ^(latest|commit-.*|[0-9]+\.[0-9]+.*|v[0-9]+.*)$ ]]; then + # First parameter looks like a tag, treat it as such + tag="$project" + project="" + fi + # Auto-discover project if not specified if [ -z "$project" ]; then echo -e "{{BLUE}}🔍 Auto-discovering project...{{NORMAL}}" - project=$(just _discover_project) + project=$(just _get_project_name) echo -e "{{GREEN}}✓ Using project: $project{{NORMAL}}" fi # Generate tag if not provided if [ -z "$tag" ]; then - if git rev-parse --git-dir >/dev/null 2>&1; then - tag="commit-$(git rev-parse --short HEAD)" - else - tag="latest" - fi + tag=$(just _generate_default_tag) fi runtime=$(just _detect_runtime) - registry="${REGISTRY:-ghcr.io}" - username="${GITHUB_USERNAME:-}" - if [ -z "$username" ]; then - echo "{{BOLD}}{{RED}}Error:{{NORMAL}} GITHUB_USERNAME not set" >&2 - echo "{{YELLOW}}Set GITHUB_USERNAME in .env file{{NORMAL}}" >&2 - exit 1 + # Special handling for latest tag + if [ "$tag" = "latest" ]; then + echo -e "{{BLUE}}🔍 Latest tag requested - finding existing images to tag...{{NORMAL}}" + + # Get base image name without tag + image_base=$(just _get_image_name "") + + # Find only commit-tagged images for this project (most recent build) + commit_images=$($runtime images | grep "^$image_base" | grep " commit-" | awk '{print $1":"$2}' || true) + + if [ -z "$commit_images" ]; then + echo "{{BOLD}}{{RED}}Error:{{NORMAL}} No commit-tagged images found to tag as latest" >&2 + echo "{{YELLOW}}Build an image first with: just images build{{NORMAL}}" >&2 + exit 1 + fi + + # Get the most recent commit image (first in the list since images are sorted by creation time) + latest_commit_image=$(echo "$commit_images" | head -1) + + echo -e "{{BLUE}}Found latest commit image: $latest_commit_image{{NORMAL}}" + + # Tag only the latest commit image as latest + latest_image_name=$(just _get_image_name "latest") + + # First push the commit-tagged image to registry + echo -e "{{BLUE}}Pushing commit image: $latest_commit_image{{NORMAL}}" + $runtime push "$latest_commit_image" + + echo -e "{{BLUE}}📋 Tagging $latest_commit_image as latest{{NORMAL}}" + $runtime tag "$latest_commit_image" "$latest_image_name" + + echo -e "{{GREEN}}✓ Tagged latest commit image as latest{{NORMAL}}" + + # Push latest tag + echo -e "{{BLUE}}Pushing latest tag: $latest_image_name{{NORMAL}}" + $runtime push "$latest_image_name" + + echo -e "{{GREEN}}✓ Successfully pushed both commit and latest tags{{NORMAL}}" + else + # Regular tag push + image_name=$(just _get_image_name "$tag") + + # Check if the image exists locally + if ! $runtime images "$image_name" >/dev/null 2>&1; then + echo "{{BOLD}}{{RED}}Error:{{NORMAL}} Image $image_name not found locally" >&2 + echo "{{YELLOW}}Build the image first with: just images build{{NORMAL}}" >&2 + echo "{{YELLOW}}Available images:{{NORMAL}}" >&2 + image_base=$(just _get_image_name "") + $runtime images | grep "$image_base" || echo "No images found for project: $project" >&2 + exit 1 + fi + + echo -e "{{BLUE}}Pushing image: $image_name{{NORMAL}}" + $runtime push "$image_name" + + echo -e "{{GREEN}}✓ Successfully pushed: $image_name{{NORMAL}}" fi - image_name="$registry/$username/$project:$tag" - - echo -e "{{BLUE}}Pushing image: $image_name{{NORMAL}}" - $runtime push "$image_name" - - echo -e "{{GREEN}}✓ Successfully pushed: $image_name{{NORMAL}}" - # Pull project image from registry [no-cd] pull project="" tag="latest": @@ -130,64 +206,49 @@ pull project="" tag="latest": # Auto-discover project if not specified if [ -z "$project" ]; then echo -e "{{BLUE}}🔍 Auto-discovering project...{{NORMAL}}" - project=$(just _discover_project) + project=$(just _get_project_name) echo -e "{{GREEN}}✓ Using project: $project{{NORMAL}}" fi + image_name=$(just _get_image_name "$tag") runtime=$(just _detect_runtime) - registry="${REGISTRY:-ghcr.io}" - username="${GITHUB_USERNAME:-}" - - if [ -z "$username" ]; then - echo "{{BOLD}}{{RED}}Error:{{NORMAL}} GITHUB_USERNAME not set" >&2 - echo "{{YELLOW}}Set GITHUB_USERNAME in .env file{{NORMAL}}" >&2 - exit 1 - fi - - image_name="$registry/$username/$project:$tag" echo -e "{{BLUE}}Pulling image: $image_name{{NORMAL}}" $runtime pull "$image_name" echo -e "{{GREEN}}✓ Successfully pulled: $image_name{{NORMAL}}" -# Tag existing image +# Tag existing image with new tag [no-cd] -tag project new_tag: +tag source_tag new_tag: #!/usr/bin/env bash set -euo pipefail - project="{{project}}" + source_tag="{{source_tag}}" new_tag="{{new_tag}}" - if [ -z "$project" ] || [ -z "$new_tag" ]; then - echo "{{BOLD}}{{RED}}Error:{{NORMAL}} Project name and new tag are required" >&2 - echo "{{YELLOW}}Usage:{{NORMAL}} just images tag myproject newtag" >&2 + if [ -z "$source_tag" ] || [ -z "$new_tag" ]; then + echo "{{BOLD}}{{RED}}Error:{{NORMAL}} Both source and new tags are required" >&2 + echo "{{YELLOW}}Usage:{{NORMAL}} just images tag commit-abc123 v1.2.3" >&2 exit 1 fi runtime=$(just _detect_runtime) - registry="${REGISTRY:-ghcr.io}" - username="${GITHUB_USERNAME:-}" - if [ -n "$username" ]; then - old_image="$registry/$username/$project" - new_image="$registry/$username/$project:$new_tag" - else - old_image="$project" - new_image="$project:$new_tag" - fi + source_image=$(just _get_image_name "$source_tag") + new_image=$(just _get_image_name "$new_tag") - # Find the most recent tag for the project - latest_image=$($runtime images | grep "^$old_image" | awk '{print $1":"$2}' | head -1) - - if [ -z "$latest_image" ]; then - echo "{{BOLD}}{{RED}}Error:{{NORMAL}} No images found for project: $project" >&2 + # Check if source image exists + if ! $runtime images "$source_image" >/dev/null 2>&1; then + echo "{{BOLD}}{{RED}}Error:{{NORMAL}} Source image not found: $source_image" >&2 + echo "{{YELLOW}}Available images:{{NORMAL}}" >&2 + image_base=$(just _get_image_name "") + $runtime images | grep "$image_base" || echo "No images found" >&2 exit 1 fi - echo -e "{{BLUE}}Tagging image: $latest_image → $new_image{{NORMAL}}" - $runtime tag "$latest_image" "$new_image" + echo -e "{{BLUE}}Tagging image: $source_image → $new_image{{NORMAL}}" + $runtime tag "$source_image" "$new_image" echo -e "{{GREEN}}✓ Successfully tagged: $new_image{{NORMAL}}" @@ -203,37 +264,73 @@ info project="" tag="": # Auto-discover project if not specified if [ -z "$project" ]; then echo -e "{{BLUE}}🔍 Auto-discovering project...{{NORMAL}}" - project=$(just _discover_project) + project=$(just _get_project_name) echo -e "{{GREEN}}✓ Using project: $project{{NORMAL}}" fi runtime=$(just _detect_runtime) - registry="${REGISTRY:-ghcr.io}" - username="${GITHUB_USERNAME:-}" - - if [ -n "$username" ]; then - image_base="$registry/$username/$project" - else - image_base="$project" - fi if [ -n "$tag" ]; then - image_name="$image_base:$tag" + image_name=$(just _get_image_name "$tag") else - image_name="$image_base" + image_name=$(just _get_image_name "") fi echo "" echo -e "{{BLUE}}Image information for: $image_name{{NORMAL}}" + echo "" + echo -e "{{BLUE}}Configuration:{{NORMAL}}" + echo -e "{{YELLOW}}Project:{{NORMAL}} $project" + echo -e "{{YELLOW}}Registry:{{NORMAL}} ${REGISTRY:-ghcr.io}" + echo -e "{{YELLOW}}Namespace:{{NORMAL}} ${REGISTRY_NAMESPACE:-${GITHUB_USERNAME:-'Not set'}}" + echo -e "{{YELLOW}}Image Name Override:{{NORMAL}} ${IMAGE_NAME:-'Not set'}" + echo -e "{{YELLOW}}Default Platforms:{{NORMAL}} ${DEFAULT_PLATFORMS:-linux/amd64,linux/arm64}" + echo "" # Show image details - $runtime images "$image_name" + if [ -n "$tag" ]; then + echo -e "{{BLUE}}Local image details:{{NORMAL}}" + $runtime images "$image_name" 2>/dev/null || echo "Image not found locally: $image_name" + else + echo -e "{{BLUE}}All project images:{{NORMAL}}" + $runtime images | grep "$image_name" 2>/dev/null || echo "No images found for project: $project" + fi + +# List all project images +[no-cd] +list project="": + #!/usr/bin/env bash + set -euo pipefail + + project="{{project}}" + + # Auto-discover project if not specified + if [ -z "$project" ]; then + echo -e "{{BLUE}}🔍 Auto-discovering project...{{NORMAL}}" + project=$(just _get_project_name) + echo -e "{{GREEN}}✓ Using project: $project{{NORMAL}}" + fi + + runtime=$(just _detect_runtime) + image_base=$(just _get_image_name "") echo "" - echo -e "{{BLUE}}Image history:{{NORMAL}}" - $runtime history "$image_name" 2>/dev/null || echo "Image not found locally" + echo -e "{{BLUE}}Images for project: $project{{NORMAL}}" + echo -e "{{BLUE}}Base name: $image_base{{NORMAL}}" + echo "" -# Remove project images + # List all images for this project + images=$($runtime images | grep "$image_base" || true) + + if [ -z "$images" ]; then + echo -e "{{YELLOW}}No images found for project: $project{{NORMAL}}" + echo -e "{{YELLOW}}Build an image with: just images build{{NORMAL}}" + else + echo "REPOSITORY TAG SIZE CREATED" + echo "$images" + fi + +# Remove project images with confirmation [confirm] [no-cd] clean project="": @@ -245,24 +342,17 @@ clean project="": # Auto-discover project if not specified if [ -z "$project" ]; then echo -e "{{BLUE}}🔍 Auto-discovering project...{{NORMAL}}" - project=$(just _discover_project) + project=$(just _get_project_name) echo -e "{{GREEN}}✓ Using project: $project{{NORMAL}}" fi runtime=$(just _detect_runtime) - registry="${REGISTRY:-ghcr.io}" - username="${GITHUB_USERNAME:-}" - - if [ -n "$username" ]; then - image_pattern="$registry/$username/$project" - else - image_pattern="$project" - fi + image_base=$(just _get_image_name "") echo -e "{{YELLOW}}Removing all images for project: $project{{NORMAL}}" # Find and remove all images for the project - images=$($runtime images --format "{{{{.Repository}}}}:{{{{.Tag}}}}" | grep "^$image_pattern" || true) + images=$($runtime images | grep "^$image_base" | awk '{print $1":"$2}' || true) if [ -z "$images" ]; then echo -e "{{YELLOW}}No images found for project: $project{{NORMAL}}" @@ -282,7 +372,7 @@ clean project="": echo -e "{{GREEN}}✓ Project images cleaned{{NORMAL}}" -# Build all known projects +# Build all projects with Containerfiles [no-cd] build-all: #!/usr/bin/env bash @@ -292,11 +382,11 @@ build-all: # Find all directories with Containerfile or Dockerfile using discovery logic projects="" + has_current_project=false # Check current directory first (handles docker/ subfolder case) - has_current_project=false if just _discover_containerfile_in "." >/dev/null 2>&1; then - current_project=$(just _discover_project) + current_project=$(just _get_project_name) has_current_project=true echo -e "{{GREEN}}✓ Found project in current directory: $current_project{{NORMAL}}" fi @@ -341,4 +431,4 @@ build-all: fi done - echo -e "{{GREEN}}✓ All projects built successfully{{NORMAL}}" + echo -e "{{GREEN}}✓ All projects built successfully{{NORMAL}}" \ No newline at end of file