# Migration Tool: Monorepo to Multi-Repo with OCI # Helps migrate from monorepo structure to OCI-based dependency management # Version: 1.0.0 use ../core/nulib/lib_provisioning/config/loader.nu get-config use ../core/nulib/lib_provisioning/oci/client.nu * use std log # Migrate workspace from local extensions to OCI export def migrate-workspace [ workspace_name: string # Workspace to migrate --registry: string = "localhost:5000" # Target OCI registry --namespace: string = "provisioning-extensions" # Target namespace --publish # Publish extensions to OCI during migration --dry-run # Show what would be done without making changes ] -> record { log info $"Migrating workspace: ($workspace_name) to OCI registry" let config = (get-config) # Find workspace directory let workspace_path = ($config.paths.base | path join ".." $"workspace_($workspace_name)") if not ($workspace_path | path exists) { error make { msg: $"Workspace not found: ($workspace_path)" } } # Analyze current extensions let extensions = (analyze-extensions $workspace_path) log info $"Found ($extensions | length) extensions to migrate" # Publish extensions to OCI if requested mut published = [] if $publish and not $dry_run { $published = (publish-extensions $extensions $registry $namespace) } # Update workspace configuration let new_config = (generate-oci-config $extensions $registry $namespace) if not $dry_run { # Backup current config let config_path = ($workspace_path | path join "config" "provisioning.yaml") let backup_path = ($config_path + ".backup") if ($config_path | path exists) { cp $config_path $backup_path log info $"Backed up config to ($backup_path)" } # Write new config $new_config | to yaml | save -f $config_path log info $"Updated workspace configuration" } # Generate migration report { workspace: $workspace_name extensions_found: ($extensions | length) extensions_published: ($published | length) new_config: $new_config dry_run: $dry_run } } # Analyze extensions in workspace def analyze-extensions [ workspace_path: string ] -> list { mut extensions = [] # Check for providers let providers_path = ($workspace_path | path join "extensions" "providers") if ($providers_path | path exists) { let provider_dirs = (ls $providers_path | where type == "dir") for provider in $provider_dirs { let manifest = (load-manifest ($provider.name | path join "manifest.yaml")) if not ($manifest | is-empty) { $extensions = ($extensions | append { type: "provider" name: ($provider.name | path basename) path: $provider.name version: ($manifest.version? | default "1.0.0") manifest: $manifest }) } } } # Check for taskservs let taskservs_path = ($workspace_path | path join "extensions" "taskservs") if ($taskservs_path | path exists) { let taskserv_dirs = (ls $taskservs_path | where type == "dir") for taskserv in $taskserv_dirs { let manifest = (load-manifest ($taskserv.name | path join "manifest.yaml")) if not ($manifest | is-empty) { $extensions = ($extensions | append { type: "taskserv" name: ($taskserv.name | path basename) path: $taskserv.name version: ($manifest.version? | default "1.0.0") manifest: $manifest }) } } } # Check for clusters let clusters_path = ($workspace_path | path join "extensions" "clusters") if ($clusters_path | path exists) { let cluster_dirs = (ls $clusters_path | where type == "dir") for cluster in $cluster_dirs { let manifest = (load-manifest ($cluster.name | path join "manifest.yaml")) if not ($manifest | is-empty) { $extensions = ($extensions | append { type: "cluster" name: ($cluster.name | path basename) path: $cluster.name version: ($manifest.version? | default "1.0.0") manifest: $manifest }) } } } $extensions } # Load manifest if exists def load-manifest [ manifest_path: string ] -> record { if ($manifest_path | path exists) { try { open $manifest_path | from yaml } catch { {} } } else { {} } } # Publish extensions to OCI registry def publish-extensions [ extensions: list registry: string namespace: string ] -> list { mut published = [] for ext in $extensions { log info $"Publishing ($ext.type)/($ext.name):($ext.version)" try { let result = (push-artifact $ext.path $registry $namespace $ext.name $ext.version) if $result { $published = ($published | append { name: $ext.name type: $ext.type version: $ext.version reference: $"($registry)/($namespace)/($ext.name):($ext.version)" }) log info $" ✓ Published ($ext.name):($ext.version)" } else { log error $" ✗ Failed to publish ($ext.name):($ext.version)" } } catch { |err| log error $" ✗ Error publishing ($ext.name): ($err.msg)" } } $published } # Generate OCI-based configuration def generate-oci-config [ extensions: list registry: string namespace: string ] -> record { # Group extensions by type let providers = ($extensions | where type == "provider" | each { |ext| $"oci://($registry)/($namespace)/($ext.name):($ext.version)" }) let taskservs = ($extensions | where type == "taskserv" | each { |ext| $"oci://($registry)/($namespace)/($ext.name):($ext.version)" }) let clusters = ($extensions | where type == "cluster" | each { |ext| $"oci://($registry)/($namespace)/($ext.name):($ext.version)" }) { dependencies: { extensions: { source_type: "oci" oci: { registry: $registry namespace: $namespace tls_enabled: false auth_token_path: "~/.provisioning/tokens/oci" } modules: { providers: $providers taskservs: $taskservs clusters: $clusters } } } } } # Validate migration export def validate-migration [ workspace_name: string ] -> record { log info $"Validating migration for workspace: ($workspace_name)" let config = (get-config) # Check if workspace uses OCI let uses_oci = ($config.dependencies?.extensions?.source_type? == "oci") if not $uses_oci { return { valid: false error: "Workspace not configured for OCI" } } # Check if all extensions are accessible let modules = ($config.dependencies.extensions.modules) mut accessible = [] mut inaccessible = [] # Check providers if ($modules.providers? | is-not-empty) { for provider in $modules.providers { let parts = ($provider | parse "oci://{registry}/{namespace}/{name}:{version}") if not ($parts | is-empty) { let reg = ($parts | first | get registry) let ns = ($parts | first | get namespace) let name = ($parts | first | get name) let version = ($parts | first | get version) if (artifact-exists $reg $ns $name $version) { $accessible = ($accessible | append $name) } else { $inaccessible = ($inaccessible | append $name) } } } } # Check taskservs if ($modules.taskservs? | is-not-empty) { for taskserv in $modules.taskservs { let parts = ($taskserv | parse "oci://{registry}/{namespace}/{name}:{version}") if not ($parts | is-empty) { let reg = ($parts | first | get registry) let ns = ($parts | first | get namespace) let name = ($parts | first | get name) let version = ($parts | first | get version) if (artifact-exists $reg $ns $name $version) { $accessible = ($accessible | append $name) } else { $inaccessible = ($inaccessible | append $name) } } } } { valid: ($inaccessible | is-empty) accessible: $accessible inaccessible: $inaccessible total: (($accessible | length) + ($inaccessible | length)) } } # Rollback migration export def rollback-migration [ workspace_name: string ] -> nothing { log warning $"Rolling back migration for workspace: ($workspace_name)" let config = (get-config) let workspace_path = ($config.paths.base | path join ".." $"workspace_($workspace_name)") let config_path = ($workspace_path | path join "config" "provisioning.yaml") let backup_path = ($config_path + ".backup") if not ($backup_path | path exists) { error make { msg: "No backup found. Cannot rollback." } } # Restore backup cp $backup_path $config_path log info $"✓ Configuration restored from backup" log info $" Backup preserved at: ($backup_path)" } # Generate migration report export def migration-report [ workspace_name: string ] -> nothing { log info $"Generating migration report for: ($workspace_name)" let validation = (validate-migration $workspace_name) print "\n=== Migration Report ===" print $"Workspace: ($workspace_name)" print $"Valid: ($validation.valid)" print $"Total Extensions: ($validation.total)" print $"Accessible: ($validation.accessible | length)" print $"Inaccessible: ($validation.inaccessible | length)" if not ($validation.inaccessible | is-empty) { print "\nInaccessible Extensions:" for ext in $validation.inaccessible { print $" - ($ext)" } } print "\n" }