#!/usr/bin/env nu # Migrate images between registries export def main [ --source-registry: string = "localhost:5000" --source-type: string = "zot" --dest-registry: string = "localhost:5001" --dest-type: string = "harbor" --namespaces: list = [] --dry-run --use-skopeo ] { print "šŸ”„ Migrating registry images..." print $" Source: ($source_registry) \(($source_type)\)" print $" Destination: ($dest_registry) \(($dest_type)\)" if $dry_run { print " Mode: DRY RUN (no actual migration)" } print "" # Get source catalog print "šŸ“‹ Fetching source catalog..." let source_repos = get-registry-repos $source_registry if ($source_repos | is-empty) { print "āŒ No repositories found in source registry" return } print $" Found ($source_repos | length) repositories" # Filter by namespaces if specified let repos_to_migrate = if ($namespaces | is-empty) { $source_repos } else { $source_repos | where { |repo| let ns = ($repo | split row "/" | first) $ns in $namespaces } } print $" Will migrate ($repos_to_migrate | length) repositories\n" # Migrate each repository mut success = 0 mut failed = 0 for repo in $repos_to_migrate { print $"Migrating: ($repo)" # Get tags for repository let tags = get-repo-tags $source_registry $repo if ($tags | is-empty) { print " āš ļø No tags found, skipping" continue } print $" Tags: ($tags | str join ', ')" # Migrate each tag for tag in $tags { let source_image = $"($source_registry)/($repo):($tag)" let dest_image = $"($dest_registry)/($repo):($tag)" if $dry_run { print $" [DRY RUN] Would migrate: ($source_image) → ($dest_image)" $success = ($success + 1) } else { if $use_skopeo { # Use skopeo for migration if (migrate-with-skopeo $source_image $dest_image) { print $" āœ“ Migrated: ($tag)" $success = ($success + 1) } else { print $" āœ— Failed: ($tag)" $failed = ($failed + 1) } } else { # Use Docker for migration if (migrate-with-docker $source_image $dest_image) { print $" āœ“ Migrated: ($tag)" $success = ($success + 1) } else { print $" āœ— Failed: ($tag)" $failed = ($failed + 1) } } } } print "" } # Summary print "=" * 60 print $"Migration Summary:" print $" Successfully migrated: ($success) images" print $" Failed: ($failed) images" if $dry_run { print "\nℹ This was a dry run. Use without --dry-run to actually migrate." } if $failed > 0 { print "\nāš ļø Some migrations failed. Check logs above." } else { print "\nāœ… All migrations completed successfully!" } } def get-registry-repos [registry_url: string] -> list { let result = (do { http get $"http://($registry_url)/v2/_catalog" } | complete) if $result.exit_code != 0 { return [] } let catalog = ($result.stdout | from json) $catalog.repositories } def get-repo-tags [registry_url: string, repo: string] -> list { let result = (do { http get $"http://($registry_url)/v2/($repo)/tags/list" } | complete) if $result.exit_code != 0 { return [] } let tags_data = ($result.stdout | from json) $tags_data.tags | default [] } def migrate-with-docker [source: string, dest: string] -> bool { # Pull from source let pull_result = (^docker pull $source | complete) if $pull_result.exit_code != 0 { print $" Error pulling: ($pull_result.stderr)" return false } # Tag for destination let tag_result = (^docker tag $source $dest | complete) if $tag_result.exit_code != 0 { print $" Error tagging: ($tag_result.stderr)" return false } # Push to destination let push_result = (^docker push $dest | complete) if $push_result.exit_code != 0 { print $" Error pushing: ($push_result.stderr)" return false } # Cleanup local image ^docker rmi $source | ignore ^docker rmi $dest | ignore return true } def migrate-with-skopeo [source: string, dest: string] -> bool { # Check if skopeo is available let skopeo_check = (^skopeo --version | complete) if $skopeo_check.exit_code != 0 { print " Error: skopeo not found" return false } # Use skopeo copy let copy_result = (^skopeo copy \ $"docker://($source)" \ $"docker://($dest)" \ --insecure-policy | complete) if $copy_result.exit_code != 0 { print $" Error: ($copy_result.stderr)" return false } return true } # Sync specific namespace export def "sync namespace" [ namespace: string --source-registry: string = "localhost:5000" --dest-registry: string = "localhost:5001" --dry-run ] { main --source-registry $source_registry \ --dest-registry $dest_registry \ --namespaces [$namespace] \ --dry-run=$dry_run } # Sync all namespaces export def "sync all" [ --source-registry: string = "localhost:5000" --dest-registry: string = "localhost:5001" --dry-run ] { let namespaces = [ "provisioning-extensions" "provisioning-kcl" "provisioning-platform" "provisioning-test" ] for ns in $namespaces { print $"\nšŸ”„ Syncing namespace: ($ns)" sync namespace $ns --source-registry $source_registry \ --dest-registry $dest_registry \ --dry-run=$dry_run } } # Backup registry to tar files export def "backup to-tar" [ --registry-url: string = "localhost:5000" --output-dir: string = "./registry-backup" --namespaces: list = [] ] { print "šŸ’¾ Backing up registry to tar files..." print $" Registry: ($registry_url)" print $" Output: ($output_dir)\n" # Create output directory mkdir $output_dir # Get repositories let repos = get-registry-repos $registry_url let repos_to_backup = if ($namespaces | is-empty) { $repos } else { $repos | where { |repo| let ns = ($repo | split row "/" | first) $ns in $namespaces } } print $"Backing up ($repos_to_backup | length) repositories...\n" for repo in $repos_to_backup { print $"Backing up: ($repo)" let tags = get-repo-tags $registry_url $repo for tag in $tags { let image = $"($registry_url)/($repo):($tag)" let filename = $"($output_dir)/($repo | str replace '/' '_')_($tag).tar" # Pull image ^docker pull $image | ignore # Save to tar let save_result = (^docker save -o $filename $image | complete) if $save_result.exit_code == 0 { print $" āœ“ Saved: ($filename)" } else { print $" āœ— Failed: ($tag)" } } } print "\nāœ… Backup complete!" } # Restore registry from tar files export def "restore from-tar" [ --input-dir: string = "./registry-backup" --registry-url: string = "localhost:5000" ] { print "šŸ“„ Restoring registry from tar files..." print $" Input: ($input_dir)" print $" Registry: ($registry_url)\n" # Find all tar files let tar_files = (ls $"($input_dir)/*.tar" | get name) print $"Found ($tar_files | length) tar files\n" for tar_file in $tar_files { print $"Restoring: ($tar_file)" # Load from tar let load_result = (^docker load -i $tar_file | complete) if $load_result.exit_code != 0 { print $" āœ— Failed to load" continue } # Extract image name from docker load output # Format: "Loaded image: localhost:5000/repo:tag" let image = ($load_result.stdout | str trim | split row ": " | last) # Retag if needed and push let target_image = $"($registry_url)/($image | split row '/' | skip 1 | str join '/')" ^docker tag $image $target_image | ignore let push_result = (^docker push $target_image | complete) if $push_result.exit_code == 0 { print $" āœ“ Restored and pushed" } else { print $" āœ— Failed to push" } # Cleanup ^docker rmi $image | ignore ^docker rmi $target_image | ignore } print "\nāœ… Restore complete!" }