327 lines
8.9 KiB
Plaintext
327 lines
8.9 KiB
Plaintext
#!/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<string> = []
|
||
--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<string> = []
|
||
] {
|
||
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!"
|
||
}
|