403 lines
12 KiB
Plaintext
403 lines
12 KiB
Plaintext
|
|
# Extension Discovery and Search
|
||
|
|
# Discovers extensions across OCI registries, Gitea, and local sources
|
||
|
|
|
||
|
|
use ../utils/logger.nu *
|
||
|
|
use ../oci/client.nu *
|
||
|
|
use versions.nu [is-semver, sort-by-semver, get-latest-version]
|
||
|
|
|
||
|
|
# Discover extensions in OCI registry
|
||
|
|
export def discover-oci-extensions [
|
||
|
|
oci_config?: record
|
||
|
|
extension_type?: string
|
||
|
|
]: nothing -> list {
|
||
|
|
try {
|
||
|
|
let config = if ($oci_config | is-empty) {
|
||
|
|
get-oci-config
|
||
|
|
} else {
|
||
|
|
$oci_config
|
||
|
|
}
|
||
|
|
|
||
|
|
let token = (load-oci-token $config.auth_token_path)
|
||
|
|
|
||
|
|
log-info $"Discovering extensions in OCI registry: ($config.registry)/($config.namespace)"
|
||
|
|
|
||
|
|
# List all artifacts
|
||
|
|
let artifacts = (oci-list-artifacts $config.registry $config.namespace --auth-token $token)
|
||
|
|
|
||
|
|
if ($artifacts | is-empty) {
|
||
|
|
log-warn "No artifacts found in OCI registry"
|
||
|
|
return []
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get metadata for each artifact
|
||
|
|
let extensions = ($artifacts | each {|artifact_name|
|
||
|
|
try {
|
||
|
|
let tags = (oci-get-artifact-tags $config.registry $config.namespace $artifact_name --auth-token $token)
|
||
|
|
|
||
|
|
if ($tags | is-empty) {
|
||
|
|
null
|
||
|
|
} else {
|
||
|
|
let semver_tags = ($tags | where ($it | is-semver))
|
||
|
|
let latest = if ($semver_tags | is-not-empty) {
|
||
|
|
$semver_tags | sort-by-semver | last
|
||
|
|
} else {
|
||
|
|
$tags | last
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get manifest for latest version
|
||
|
|
let manifest = (oci-get-artifact-manifest
|
||
|
|
$config.registry
|
||
|
|
$config.namespace
|
||
|
|
$artifact_name
|
||
|
|
$latest
|
||
|
|
--auth-token $token
|
||
|
|
)
|
||
|
|
|
||
|
|
# Extract extension type from annotations
|
||
|
|
let ext_type = (extract-extension-type $manifest)
|
||
|
|
|
||
|
|
{
|
||
|
|
name: $artifact_name
|
||
|
|
type: $ext_type
|
||
|
|
versions: $tags
|
||
|
|
latest: $latest
|
||
|
|
source: "oci"
|
||
|
|
registry: $config.registry
|
||
|
|
namespace: $config.namespace
|
||
|
|
digest: ($manifest.config?.digest? | default "")
|
||
|
|
annotations: ($manifest.config?.annotations? | default {})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch { |err|
|
||
|
|
log-warn $"Failed to get metadata for ($artifact_name): ($err.msg)"
|
||
|
|
null
|
||
|
|
}
|
||
|
|
} | compact)
|
||
|
|
|
||
|
|
# Filter by extension type if specified
|
||
|
|
if ($extension_type | is-not-empty) {
|
||
|
|
$extensions | where type == $extension_type
|
||
|
|
} else {
|
||
|
|
$extensions
|
||
|
|
}
|
||
|
|
|
||
|
|
} catch { |err|
|
||
|
|
log-error $"Failed to discover OCI extensions: ($err.msg)"
|
||
|
|
[]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Search extensions in OCI registry
|
||
|
|
export def search-oci-extensions [
|
||
|
|
query: string
|
||
|
|
oci_config?: record
|
||
|
|
]: nothing -> list {
|
||
|
|
try {
|
||
|
|
let all_extensions = (discover-oci-extensions $oci_config)
|
||
|
|
|
||
|
|
$all_extensions | where {|ext|
|
||
|
|
($ext.name | str contains $query) or
|
||
|
|
($ext.type | str contains $query)
|
||
|
|
}
|
||
|
|
|
||
|
|
} catch { |err|
|
||
|
|
log-error $"Failed to search OCI extensions: ($err.msg)"
|
||
|
|
[]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get extension metadata from OCI registry
|
||
|
|
export def get-oci-extension-metadata [
|
||
|
|
extension_name: string
|
||
|
|
version: string
|
||
|
|
oci_config?: record
|
||
|
|
]: nothing -> record {
|
||
|
|
try {
|
||
|
|
let config = if ($oci_config | is-empty) {
|
||
|
|
get-oci-config
|
||
|
|
} else {
|
||
|
|
$oci_config
|
||
|
|
}
|
||
|
|
|
||
|
|
let token = (load-oci-token $config.auth_token_path)
|
||
|
|
|
||
|
|
let manifest = (oci-get-artifact-manifest
|
||
|
|
$config.registry
|
||
|
|
$config.namespace
|
||
|
|
$extension_name
|
||
|
|
$version
|
||
|
|
--auth-token $token
|
||
|
|
)
|
||
|
|
|
||
|
|
if ($manifest | is-empty) {
|
||
|
|
return {}
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: $extension_name
|
||
|
|
version: $version
|
||
|
|
source: "oci"
|
||
|
|
registry: $config.registry
|
||
|
|
namespace: $config.namespace
|
||
|
|
oci_digest: ($manifest.config?.digest? | default "")
|
||
|
|
created: ($manifest.config?.created? | default "")
|
||
|
|
size: ($manifest.config?.size? | default 0)
|
||
|
|
annotations: ($manifest.config?.annotations? | default {})
|
||
|
|
layers: ($manifest.layers? | default [])
|
||
|
|
media_type: ($manifest.mediaType? | default "")
|
||
|
|
}
|
||
|
|
|
||
|
|
} catch { |err|
|
||
|
|
log-error $"Failed to get OCI extension metadata: ($err.msg)"
|
||
|
|
{}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Discover local extensions
|
||
|
|
export def discover-local-extensions [
|
||
|
|
extension_type?: string
|
||
|
|
]: nothing -> list {
|
||
|
|
let extension_paths = [
|
||
|
|
($env.PWD | path join ".provisioning" "extensions")
|
||
|
|
($env.HOME | path join ".provisioning-extensions")
|
||
|
|
"/opt/provisioning-extensions"
|
||
|
|
] | where ($it | path exists)
|
||
|
|
|
||
|
|
let extensions = ($extension_paths | each {|base_path|
|
||
|
|
discover-in-path $base_path $extension_type
|
||
|
|
} | flatten)
|
||
|
|
|
||
|
|
$extensions
|
||
|
|
}
|
||
|
|
|
||
|
|
# Discover extensions in specific path
|
||
|
|
def discover-in-path [
|
||
|
|
base_path: string
|
||
|
|
extension_type?: string
|
||
|
|
]: nothing -> list {
|
||
|
|
let type_dirs = if ($extension_type | is-not-empty) {
|
||
|
|
[$extension_type]
|
||
|
|
} else {
|
||
|
|
["providers", "taskservs", "clusters"]
|
||
|
|
}
|
||
|
|
|
||
|
|
$type_dirs | each {|type_dir|
|
||
|
|
let type_path = ($base_path | path join $type_dir)
|
||
|
|
|
||
|
|
if not ($type_path | path exists) {
|
||
|
|
return []
|
||
|
|
}
|
||
|
|
|
||
|
|
let extensions = (ls $type_path
|
||
|
|
| where type == dir
|
||
|
|
| get name
|
||
|
|
| each {|ext_path|
|
||
|
|
try {
|
||
|
|
let ext_name = ($ext_path | path basename)
|
||
|
|
let manifest_file = ($ext_path | path join "extension.yaml")
|
||
|
|
|
||
|
|
let manifest = if ($manifest_file | path exists) {
|
||
|
|
open $manifest_file | from yaml
|
||
|
|
} else {
|
||
|
|
{
|
||
|
|
extension: {
|
||
|
|
name: $ext_name
|
||
|
|
type: $type_dir
|
||
|
|
version: "local"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: ($manifest.extension.name? | default $ext_name)
|
||
|
|
type: ($manifest.extension.type? | default $type_dir)
|
||
|
|
version: ($manifest.extension.version? | default "local")
|
||
|
|
path: $ext_path
|
||
|
|
source: "local"
|
||
|
|
description: ($manifest.extension.description? | default "")
|
||
|
|
}
|
||
|
|
} catch { |err|
|
||
|
|
log-warn $"Failed to read extension at ($ext_path): ($err.msg)"
|
||
|
|
null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
| compact
|
||
|
|
)
|
||
|
|
|
||
|
|
$extensions
|
||
|
|
} | flatten
|
||
|
|
}
|
||
|
|
|
||
|
|
# Discover all extensions (OCI, Gitea, Local)
|
||
|
|
export def discover-all-extensions [
|
||
|
|
extension_type?: string
|
||
|
|
--include-oci
|
||
|
|
--include-gitea
|
||
|
|
--include-local
|
||
|
|
]: nothing -> list {
|
||
|
|
let mut all_extensions = []
|
||
|
|
|
||
|
|
# Discover from OCI if flag set or if no flags set (default all)
|
||
|
|
if $include_oci or (not $include_oci and not $include_gitea and not $include_local) {
|
||
|
|
if (is-oci-available) {
|
||
|
|
let oci_exts = (discover-oci-extensions null $extension_type)
|
||
|
|
$all_extensions = ($all_extensions | append $oci_exts)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Discover from Gitea if flag set or default
|
||
|
|
if $include_gitea or (not $include_oci and not $include_gitea and not $include_local) {
|
||
|
|
if (is-gitea-available) {
|
||
|
|
# TODO: Implement Gitea discovery
|
||
|
|
log-debug "Gitea discovery not yet implemented"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Discover from local if flag set or default
|
||
|
|
if $include_local or (not $include_oci and not $include_gitea and not $include_local) {
|
||
|
|
let local_exts = (discover-local-extensions $extension_type)
|
||
|
|
$all_extensions = ($all_extensions | append $local_exts)
|
||
|
|
}
|
||
|
|
|
||
|
|
$all_extensions
|
||
|
|
}
|
||
|
|
|
||
|
|
# Search all sources for extensions
|
||
|
|
export def search-extensions [
|
||
|
|
query: string
|
||
|
|
--source: string = "all" # all, oci, gitea, local
|
||
|
|
]: nothing -> list {
|
||
|
|
match $source {
|
||
|
|
"oci" => {
|
||
|
|
search-oci-extensions $query
|
||
|
|
}
|
||
|
|
"gitea" => {
|
||
|
|
# TODO: Implement Gitea search
|
||
|
|
log-warn "Gitea search not yet implemented"
|
||
|
|
[]
|
||
|
|
}
|
||
|
|
"local" => {
|
||
|
|
let local_exts = (discover-local-extensions)
|
||
|
|
$local_exts | where {|ext|
|
||
|
|
($ext.name | str contains $query) or
|
||
|
|
($ext.type | str contains $query) or
|
||
|
|
($ext.description? | default "" | str contains $query)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
"all" => {
|
||
|
|
let all = (discover-all-extensions)
|
||
|
|
$all | where {|ext|
|
||
|
|
($ext.name | str contains $query) or
|
||
|
|
($ext.type | str contains $query)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
log-error $"Unknown source: ($source)"
|
||
|
|
[]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# List extensions with detailed information
|
||
|
|
export def list-extensions [
|
||
|
|
--extension-type: string = ""
|
||
|
|
--source: string = "all"
|
||
|
|
--format: string = "table" # table, json, yaml
|
||
|
|
]: nothing -> any {
|
||
|
|
let extensions = (discover-all-extensions $extension_type)
|
||
|
|
|
||
|
|
let filtered = if $source != "all" {
|
||
|
|
$extensions | where source == $source
|
||
|
|
} else {
|
||
|
|
$extensions
|
||
|
|
}
|
||
|
|
|
||
|
|
match $format {
|
||
|
|
"json" => ($filtered | to json)
|
||
|
|
"yaml" => ($filtered | to yaml)
|
||
|
|
"table" => {
|
||
|
|
$filtered
|
||
|
|
| select name type version source
|
||
|
|
| sort-by type name
|
||
|
|
}
|
||
|
|
_ => $filtered
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get extension versions from all sources
|
||
|
|
export def get-extension-versions [
|
||
|
|
extension_name: string
|
||
|
|
--source: string = "all"
|
||
|
|
]: nothing -> list {
|
||
|
|
let mut versions = []
|
||
|
|
|
||
|
|
# Get from OCI
|
||
|
|
if $source == "all" or $source == "oci" {
|
||
|
|
if (is-oci-available) {
|
||
|
|
let config = (get-oci-config)
|
||
|
|
let token = (load-oci-token $config.auth_token_path)
|
||
|
|
|
||
|
|
let oci_tags = (oci-get-artifact-tags
|
||
|
|
$config.registry
|
||
|
|
$config.namespace
|
||
|
|
$extension_name
|
||
|
|
--auth-token $token
|
||
|
|
)
|
||
|
|
|
||
|
|
let oci_versions = ($oci_tags | each {|tag|
|
||
|
|
{version: $tag, source: "oci"}
|
||
|
|
})
|
||
|
|
|
||
|
|
$versions = ($versions | append $oci_versions)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get from Gitea
|
||
|
|
if $source == "all" or $source == "gitea" {
|
||
|
|
# TODO: Implement Gitea versions
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get from local
|
||
|
|
if $source == "all" or $source == "local" {
|
||
|
|
let local_exts = (discover-local-extensions)
|
||
|
|
let local_matches = ($local_exts | where name == $extension_name)
|
||
|
|
|
||
|
|
let local_versions = ($local_matches | each {|ext|
|
||
|
|
{version: ($ext.version? | default "local"), source: "local"}
|
||
|
|
})
|
||
|
|
|
||
|
|
$versions = ($versions | append $local_versions)
|
||
|
|
}
|
||
|
|
|
||
|
|
$versions
|
||
|
|
}
|
||
|
|
|
||
|
|
# Extract extension type from OCI manifest annotations
|
||
|
|
def extract-extension-type [manifest: record]: nothing -> string {
|
||
|
|
let annotations = ($manifest.config?.annotations? | default {})
|
||
|
|
|
||
|
|
# Try standard annotation
|
||
|
|
let ext_type = ($annotations | get -o "provisioning.extension.type")
|
||
|
|
|
||
|
|
if ($ext_type | is-not-empty) {
|
||
|
|
return $ext_type
|
||
|
|
}
|
||
|
|
|
||
|
|
# Try OCI image labels
|
||
|
|
let labels = ($manifest.config?.config?.Labels? | default {})
|
||
|
|
let label_type = ($labels | get -o "provisioning.extension.type")
|
||
|
|
|
||
|
|
if ($label_type | is-not-empty) {
|
||
|
|
return $label_type
|
||
|
|
}
|
||
|
|
|
||
|
|
# Default to unknown
|
||
|
|
"unknown"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check if Gitea is available
|
||
|
|
def is-gitea-available []: nothing -> bool {
|
||
|
|
# TODO: Implement Gitea availability check
|
||
|
|
false
|
||
|
|
}
|