provisioning/tools/migrate-to-oci.nu

346 lines
10 KiB
Plaintext
Raw Permalink Normal View History

2025-10-07 11:12:02 +01:00
# 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<record> {
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<record>
registry: string
namespace: string
] -> list<record> {
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<record>
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"
}