prvng_core/nulib/lib_provisioning/oci/commands.nu
Jesús Pérez 844f6f9297
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

452 lines
13 KiB
Text

# OCI CLI Commands
# User-facing commands for OCI artifact management
# Version: 1.0.0
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
]
use std log
# Former duplicate `use ./client.nu *` removed — replaced by selective above.
# 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]..."
echo $password | docker login $registry --username $username --password-stdin
print $"✓ Successfully logged in to [$registry]"
print $" Credentials stored in docker config"
"success"
} | complete)
if $result.exit_code != 0 {
error make {
msg: $"Failed to login to [$registry]: ($result.stderr)"
}
}
}
# Logout from OCI registry
export def "oci logout" [
registry: string # Registry endpoint
] -> nothing {
let result = (do {
docker logout $registry
print $"✓ Logged out from [$registry]"
"success"
} | complete)
if $result.exit_code != 0 {
error make {
msg: $"Failed to logout from [$registry]: ($result.stderr)"
}
}
}
# 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)
}
}