346 lines
10 KiB
Plaintext
346 lines
10 KiB
Plaintext
|
|
# 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"
|
||
|
|
}
|