prvng_core/nulib/lib_provisioning/oci/commands.nu

453 lines
13 KiB
Text
Raw Normal View History

2025-10-07 10:32:04 +01:00
# OCI CLI Commands
# User-facing commands for OCI artifact management
# Version: 1.0.0
refactor(17 files final batch): selective imports — drive to 94% elimination (ADR-025 L2/L3) Final large batch of single-star conversions. Orchestrator facades (Layer 3, expanded to explicit symbol lists): config/accessor.nu 18 symbols (bridges accessor/mod) config/accessor_generated.nu 18 symbols (consumer of accessor) utils/version.nu 35 symbols (bridges version/mod) dependencies/mod.nu 7 symbols from resolver.nu oci_registry/mod.nu 12 multi-word "oci-registry X" subcommands oci/commands.nu 12 symbols from oci/client.nu Selective imports (Layer 2): platform/discovery.nu target.nu [5 symbols] platform/health.nu target.nu [2 symbols] platform/connection.nu user/config [get-active-workspace] vm/preparer.nu vm/detector [check-vm-capability] vm/backend_libvirt.nu result.nu [7 symbols] extensions/tests/test_versions.nu versions [5 symbols] utils/version/loader.nu utils/nickel_processor [ncl-eval ncl-eval-soft] Dead imports dropped: platform/credentials.nu user/config platform/activation.nu target config/cache/core.nu cache/metadata config/interpolation/core.nu helpers/environment utils/version/loader.nu version/core (kept nickel_processor) Validation: all 17 files match pre-existing baselines (or 0 errors for clean ones). Pre-existing noise in vm/, dependencies/, oci_registry/, oci/commands is known transitive — unrelated to this work. MILESTONE: 94% of star-imports eliminated (370 → 21). Remaining 21 star-lines in 6 files are intentional exceptions: - integrations/mod.nu (2 stars, re-exports already-selective children; acceptable bounded scope) - cmd/environment.nu (3 stars, contains ~7 undefined function calls — needs Blocker-1 style cleanup in follow-up commit) - providers/loader.nu (1 dynamic `use ($entry_point) *` — runtime dispatch) - vm/cleanup_scheduler.nu (1 in string template — not a real import) - lib_provisioning/mod.nu (13 stars — root facade; empties in ADR-025 Phase 4) Refs: ADR-025
2026-04-17 17:08:10 +01:00
use lib_provisioning/config/loader.nu [get-config]
# Selective oci client imports (ADR-025 Phase 3 Layer 2).
use lib_provisioning/oci/client.nu [
build-artifact-ref get-oci-config is-oci-available load-oci-token
oci-artifact-exists oci-delete-artifact oci-get-artifact-manifest
oci-get-artifact-tags oci-list-artifacts oci-pull-artifact
oci-push-artifact test-oci-connection
]
2025-10-07 10:32:04 +01:00
use std log
refactor(17 files final batch): selective imports — drive to 94% elimination (ADR-025 L2/L3) Final large batch of single-star conversions. Orchestrator facades (Layer 3, expanded to explicit symbol lists): config/accessor.nu 18 symbols (bridges accessor/mod) config/accessor_generated.nu 18 symbols (consumer of accessor) utils/version.nu 35 symbols (bridges version/mod) dependencies/mod.nu 7 symbols from resolver.nu oci_registry/mod.nu 12 multi-word "oci-registry X" subcommands oci/commands.nu 12 symbols from oci/client.nu Selective imports (Layer 2): platform/discovery.nu target.nu [5 symbols] platform/health.nu target.nu [2 symbols] platform/connection.nu user/config [get-active-workspace] vm/preparer.nu vm/detector [check-vm-capability] vm/backend_libvirt.nu result.nu [7 symbols] extensions/tests/test_versions.nu versions [5 symbols] utils/version/loader.nu utils/nickel_processor [ncl-eval ncl-eval-soft] Dead imports dropped: platform/credentials.nu user/config platform/activation.nu target config/cache/core.nu cache/metadata config/interpolation/core.nu helpers/environment utils/version/loader.nu version/core (kept nickel_processor) Validation: all 17 files match pre-existing baselines (or 0 errors for clean ones). Pre-existing noise in vm/, dependencies/, oci_registry/, oci/commands is known transitive — unrelated to this work. MILESTONE: 94% of star-imports eliminated (370 → 21). Remaining 21 star-lines in 6 files are intentional exceptions: - integrations/mod.nu (2 stars, re-exports already-selective children; acceptable bounded scope) - cmd/environment.nu (3 stars, contains ~7 undefined function calls — needs Blocker-1 style cleanup in follow-up commit) - providers/loader.nu (1 dynamic `use ($entry_point) *` — runtime dispatch) - vm/cleanup_scheduler.nu (1 in string template — not a real import) - lib_provisioning/mod.nu (13 stars — root facade; empties in ADR-025 Phase 4) Refs: ADR-025
2026-04-17 17:08:10 +01:00
# Former duplicate `use ./client.nu *` removed — replaced by selective above.
2025-10-07 10:32:04 +01:00
# Pull OCI artifact to local cache
export def "oci pull" [
artifact: string # Format: name:version or registry/namespace/name:version
--registry: string # Override default registry
--namespace: string = "provisioning-extensions" # Override default namespace
--destination: string # Local path (default: workspace extensions)
--insecure # Skip TLS verification
] -> nothing {
let config = (get-config)
# Parse artifact reference
let parts = ($artifact | split row ":")
let artifact_name = ($parts | get 0)
let version = ($parts | get 1? | default "latest")
# Determine registry
let reg = if ($registry | is-not-empty) {
$registry
} else if ($config.registry? | is-not-empty) {
$config.registry.oci.endpoint
} else {
"localhost:5000"
}
# Determine destination
let dest = if ($destination | is-not-empty) {
$destination
} else {
let ws_extensions = ($config.paths.extensions | path join $artifact_name)
mkdir $ws_extensions
$ws_extensions
}
# Get authentication token if configured
let auth_token = if ($config.registry?.oci?.auth_token_path? | is-not-empty) {
open ($config.registry.oci.auth_token_path | path expand)
} else {
""
}
print $"Pulling ($reg)/($namespace)/($artifact_name):($version)..."
let result = (pull-artifact $reg $namespace $artifact_name $version $dest
--auth-token $auth_token
--insecure=$insecure)
if $result {
print $"✓ Successfully pulled ($artifact_name):($version) to ($dest)"
} else {
error make {
msg: $"Failed to pull ($artifact_name):($version)"
}
}
}
# Push local extension as OCI artifact
export def "oci push" [
source_path: string # Local extension directory
artifact_name: string # Artifact name
version: string # Artifact version
--registry: string # Override default registry
--namespace: string = "provisioning-extensions"
--insecure
] -> nothing {
let config = (get-config)
# Validate source exists
if not ($source_path | path exists) {
error make {
msg: $"Source path not found: ($source_path)"
}
}
# Determine registry
let reg = if ($registry | is-not-empty) {
$registry
} else if ($config.registry? | is-not-empty) {
$config.registry.oci.endpoint
} else {
"localhost:5000"
}
# Get authentication token
let auth_token = if ($config.registry?.oci?.auth_token_path? | is-not-empty) {
open ($config.registry.oci.auth_token_path | path expand)
} else {
""
}
print $"Pushing ($artifact_name):($version) to ($reg)/($namespace)..."
let result = (push-artifact $source_path $reg $namespace $artifact_name $version
--auth-token $auth_token
--insecure=$insecure)
if $result {
print $"✓ Successfully pushed ($artifact_name):($version)"
print $" Reference: ($reg)/($namespace)/($artifact_name):($version)"
} else {
error make {
msg: $"Failed to push ($artifact_name):($version)"
}
}
}
# List available artifacts in registry
export def "oci list" [
--namespace: string = "provisioning-extensions"
--registry: string
--insecure
] -> table {
let config = (get-config)
let reg = if ($registry | is-not-empty) {
$registry
} else if ($config.registry? | is-not-empty) {
$config.registry.oci.endpoint
} else {
"localhost:5000"
}
let auth_token = if ($config.registry?.oci?.auth_token_path? | is-not-empty) {
open ($config.registry.oci.auth_token_path | path expand)
} else {
""
}
print $"Listing artifacts in ($reg)/($namespace)..."
let artifacts = (list-artifacts $reg $namespace
--auth-token $auth_token
--insecure=$insecure)
if ($artifacts | is-empty) {
print "No artifacts found"
return []
}
# Format as table
$artifacts | each { |artifact|
{
name: $artifact
registry: $reg
namespace: $namespace
reference: $"($reg)/($namespace)/($artifact)"
}
}
}
# Search for artifacts matching query
export def "oci search" [
query: string # Search query (artifact name pattern)
--namespace: string = "provisioning-extensions"
--registry: string
--insecure
] -> table {
let artifacts = (oci list --namespace $namespace --registry $registry --insecure=$insecure)
$artifacts | where name =~ $query
}
# Show artifact tags (versions)
export def "oci tags" [
artifact_name: string # Artifact name
--namespace: string = "provisioning-extensions"
--registry: string
--insecure
] -> table {
let config = (get-config)
let reg = if ($registry | is-not-empty) {
$registry
} else if ($config.registry? | is-not-empty) {
$config.registry.oci.endpoint
} else {
"localhost:5000"
}
let auth_token = if ($config.registry?.oci?.auth_token_path? | is-not-empty) {
open ($config.registry.oci.auth_token_path | path expand)
} else {
""
}
print $"Fetching tags for ($artifact_name)..."
let tags = (get-artifact-tags $reg $namespace $artifact_name
--auth-token $auth_token
--insecure=$insecure)
if ($tags | is-empty) {
print $"No tags found for ($artifact_name)"
return []
}
# Format as table with semantic version sorting
$tags | each { |tag|
{
artifact: $artifact_name
version: $tag
reference: $"($reg)/($namespace)/($artifact_name):($tag)"
}
} | sort-by version --reverse
}
# Inspect artifact manifest
export def "oci inspect" [
artifact: string # Format: name:version
--namespace: string = "provisioning-extensions"
--registry: string
--insecure
--format: string = "yaml" # Output format: yaml, json
] -> nothing {
let config = (get-config)
# Parse artifact reference
let parts = ($artifact | split row ":")
let artifact_name = ($parts | get 0)
let version = ($parts | get 1? | default "latest")
let reg = if ($registry | is-not-empty) {
$registry
} else if ($config.registry? | is-not-empty) {
$config.registry.oci.endpoint
} else {
"localhost:5000"
}
let auth_token = if ($config.registry?.oci?.auth_token_path? | is-not-empty) {
open ($config.registry.oci.auth_token_path | path expand)
} else {
""
}
print $"Inspecting ($reg)/($namespace)/($artifact_name):($version)..."
let manifest = (get-artifact-manifest $reg $namespace $artifact_name $version
--auth-token $auth_token
--insecure=$insecure)
if ($manifest | is-empty) {
error make {
msg: $"Artifact not found or manifest unavailable"
}
}
# Output in requested format
match $format {
"json" => { $manifest | to json }
"yaml" => { $manifest | to yaml }
_ => { $manifest }
} | print
}
# Login to OCI registry
export def "oci login" [
registry: string # Registry endpoint
--username: string = "_token" # Username (default: _token)
--password-stdin # Read password from stdin
--token-file: string # Path to token file
] -> nothing {
let password = if $password_stdin {
input --suppress-output "Password: "
} else if ($token_file | is-not-empty) {
open ($token_file | path expand)
} else {
input --suppress-output "Password: "
}
# Use docker login for credential storage
let result = (do {
print $"Logging in to [$registry]..."
2025-10-07 10:32:04 +01:00
echo $password | docker login $registry --username $username --password-stdin
print $"✓ Successfully logged in to [$registry]"
2025-10-07 10:32:04 +01:00
print $" Credentials stored in docker config"
"success"
} | complete)
if $result.exit_code != 0 {
2025-10-07 10:32:04 +01:00
error make {
msg: $"Failed to login to [$registry]: ($result.stderr)"
2025-10-07 10:32:04 +01:00
}
}
}
# Logout from OCI registry
export def "oci logout" [
registry: string # Registry endpoint
] -> nothing {
let result = (do {
2025-10-07 10:32:04 +01:00
docker logout $registry
print $"✓ Logged out from [$registry]"
"success"
} | complete)
if $result.exit_code != 0 {
2025-10-07 10:32:04 +01:00
error make {
msg: $"Failed to logout from [$registry]: ($result.stderr)"
2025-10-07 10:32:04 +01:00
}
}
}
# Delete artifact from registry
export def "oci delete" [
artifact: string # Format: name:version
--namespace: string = "provisioning-extensions"
--registry: string
--insecure
--force # Skip confirmation
] -> nothing {
let config = (get-config)
# Parse artifact reference
let parts = ($artifact | split row ":")
let artifact_name = ($parts | get 0)
let version = ($parts | get 1? | default "latest")
let reg = if ($registry | is-not-empty) {
$registry
} else if ($config.registry? | is-not-empty) {
$config.registry.oci.endpoint
} else {
"localhost:5000"
}
let full_ref = $"($reg)/($namespace)/($artifact_name):($version)"
# Confirm deletion unless --force
if not $force {
let confirm = (input $"Delete ($full_ref)? (y/N): ")
if ($confirm | str downcase) != "y" {
print "Deletion cancelled"
return
}
}
let auth_token = if ($config.registry?.oci?.auth_token_path? | is-not-empty) {
open ($config.registry.oci.auth_token_path | path expand)
} else {
""
}
print $"Deleting ($full_ref)..."
let result = (delete-artifact $reg $namespace $artifact_name $version
--auth-token $auth_token
--insecure=$insecure)
if $result {
print $"✓ Successfully deleted ($full_ref)"
} else {
error make {
msg: $"Failed to delete ($full_ref)"
}
}
}
# Copy artifact between registries
export def "oci copy" [
source: string # Format: [registry/namespace/]name:version
destination: string # Format: [registry/namespace/]name:version
--insecure
] -> nothing {
# Parse source
let src_parts = ($source | split row "/")
let src_registry = if ($src_parts | length) > 2 {
$src_parts | get 0
} else {
"localhost:5000"
}
let src_namespace = if ($src_parts | length) > 2 {
$src_parts | get 1
} else {
"provisioning-extensions"
}
let src_artifact = if ($src_parts | length) > 2 {
$src_parts | get 2
} else {
$src_parts | get 0
}
let src_name_ver = ($src_artifact | split row ":")
let src_name = ($src_name_ver | get 0)
let src_version = ($src_name_ver | get 1? | default "latest")
# Parse destination
let dest_parts = ($destination | split row "/")
let dest_registry = if ($dest_parts | length) > 2 {
$dest_parts | get 0
} else {
"localhost:5000"
}
let dest_namespace = if ($dest_parts | length) > 2 {
$dest_parts | get 1
} else {
"provisioning-extensions"
}
let dest_artifact = if ($dest_parts | length) > 2 {
$dest_parts | get 2
} else {
$dest_parts | get 0
}
let dest_name_ver = ($dest_artifact | split row ":")
let dest_name = ($dest_name_ver | get 0)
let dest_version = ($dest_name_ver | get 1? | default $src_version)
print $"Copying ($src_registry)/($src_namespace)/($src_name):($src_version)"
print $" to ($dest_registry)/($dest_namespace)/($dest_name):($dest_version)"
let result = (copy-artifact $src_registry $src_namespace
$dest_registry $dest_namespace
$src_name $src_version
--insecure=$insecure)
if $result {
print $"✓ Successfully copied artifact"
} else {
error make {
msg: "Failed to copy artifact"
}
}
}
# Show OCI configuration
export def "oci config" [] -> record {
let config = (get-config)
{
tool: (detect-oci-tool)
registry: ($config.registry?.oci?.endpoint? | default "localhost:5000")
namespace: ($config.registry?.oci?.namespaces? | default {})
cache_dir: ($env.HOME | path join ".provisioning" "oci-cache")
tls_enabled: ($config.registry?.oci?.tls_enabled? | default false)
}
}