133 lines
6.2 KiB
Text
133 lines
6.2 KiB
Text
#!/usr/bin/env nu
|
|
# Pluggable selection of the OCI push-registry target, independent of the active
|
|
# workspace. A command resolves the endpoint + credentials from a configurable
|
|
# source — sops | api | literal | env — and materializes them as an env setting
|
|
# (PROVISIONING_REGISTRY + PROVISIONING_PUSH_DOCKER_CONFIG) that the OCI contract
|
|
# resolver consumes. The source is declared once (provisioning config
|
|
# registry.push.*) or passed as flags; the command resolves it at call time.
|
|
#
|
|
# Authorization model: in override mode the credential SOURCE is the gate —
|
|
# decrypting the sops file needs the Age key, the api call needs its own auth.
|
|
# This replaces the workspace-scope check, which is what makes the push target
|
|
# selectable beyond any workspace.
|
|
|
|
use platform/config/accessor.nu *
|
|
use platform/oci/credentials.nu [build-docker-config-tmpdir]
|
|
|
|
# config-get wrapper tolerant of an unloaded provisioning config.
|
|
def cfg [key: string, fallback: string]: nothing -> string {
|
|
try { config-get $key $fallback | into string } catch { $fallback }
|
|
}
|
|
|
|
# Extract a dotted key path from a record (e.g. "data.registry.endpoint").
|
|
def get-path [data: record, dotted: string]: nothing -> string {
|
|
let parts = ($dotted | split row ".")
|
|
let val = ($parts | reduce --fold $data { |p, acc|
|
|
if (($acc | describe) | str starts-with "record") {
|
|
$acc | get -o $p | default ""
|
|
} else {
|
|
""
|
|
}
|
|
})
|
|
if (($val | describe) | str starts-with "record") { "" } else { $val | to text }
|
|
}
|
|
|
|
def resolve-from-sops [
|
|
path: string, ekey: string, ukey: string, pkey: string, vault_id: string, age_key: string
|
|
]: nothing -> record {
|
|
let sops_path = if ($path | path exists) {
|
|
$path
|
|
} else if ($vault_id | is-not-empty) {
|
|
$env.HOME | path join ".config" "ontoref" "vaults" $vault_id "src-vault" $path
|
|
} else {
|
|
$path
|
|
}
|
|
if not ($sops_path | path exists) {
|
|
error make --unspanned { msg: $"[push-registry-sops-missing] ($sops_path)" }
|
|
}
|
|
let kage = if ($age_key | is-not-empty) { $age_key } else { ($env.SOPS_AGE_KEY_FILE? | default "") }
|
|
let r = if ($kage | is-not-empty) {
|
|
with-env { SOPS_AGE_KEY_FILE: $kage } { do { ^sops --decrypt --output-type json $sops_path } | complete }
|
|
} else {
|
|
do { ^sops --decrypt --output-type json $sops_path } | complete
|
|
}
|
|
if $r.exit_code != 0 {
|
|
error make --unspanned { msg: $"[push-registry-sops-decrypt-failed] ($r.stderr)" }
|
|
}
|
|
let data = ($r.stdout | from json)
|
|
{ endpoint: (get-path $data $ekey), username: (get-path $data $ukey), password: (get-path $data $pkey) }
|
|
}
|
|
|
|
def resolve-from-api [
|
|
url: string, ekey: string, ukey: string, pkey: string, auth_header: string
|
|
]: nothing -> record {
|
|
let body = if ($auth_header | is-not-empty) {
|
|
http get --headers ["Authorization" $auth_header] $url
|
|
} else {
|
|
http get $url
|
|
}
|
|
{ endpoint: (get-path $body $ekey), username: (get-path $body $ukey), password: (get-path $body $pkey) }
|
|
}
|
|
|
|
# Resolve { endpoint, username, password } for the push registry from a pluggable
|
|
# source. Precedence: explicit --kind flag → provisioning config registry.push.kind.
|
|
export def resolve-push-registry [
|
|
--kind: string = "" # sops | api | literal | env
|
|
--value: string = "" # literal endpoint | env var name | sops path | api url
|
|
--endpoint-key: string = "endpoint" # sops key / api json path for endpoint
|
|
--user-key: string = "username"
|
|
--pass-key: string = "password"
|
|
--vault-id: string = "" # sops vault id for decryption-key resolution
|
|
--age-key: string = "" # explicit Age key file for sops
|
|
--auth-header: string = "" # api Authorization header value
|
|
]: nothing -> record {
|
|
let k = if ($kind | is-not-empty) { $kind } else { (cfg "registry.push.kind" "") }
|
|
if ($k | is-empty) {
|
|
error make --unspanned { msg: "push-registry source kind not set — pass --kind or set registry.push.kind in config" }
|
|
}
|
|
let v = if ($value | is-not-empty) { $value } else { (cfg "registry.push.value" "") }
|
|
|
|
match $k {
|
|
"literal" => { endpoint: $v, username: "", password: "" },
|
|
"env" => {
|
|
let ep = ($env | get -o $v | default "")
|
|
if ($ep | is-empty) {
|
|
error make --unspanned { msg: $"[push-registry-env-unset] env var ($v) not set" }
|
|
}
|
|
{
|
|
endpoint: $ep,
|
|
username: ($env | get -o (cfg "registry.push.user_env" "PROVISIONING_REGISTRY_USER") | default ""),
|
|
password: ($env | get -o (cfg "registry.push.pass_env" "PROVISIONING_REGISTRY_PASS") | default ""),
|
|
}
|
|
},
|
|
"sops" => (resolve-from-sops $v $endpoint_key $user_key $pass_key $vault_id $age_key),
|
|
"api" => (resolve-from-api $v $endpoint_key $user_key $pass_key $auth_header),
|
|
_ => { error make --unspanned { msg: $"[push-registry-kind-unknown] ($k)" } },
|
|
}
|
|
}
|
|
|
|
# Resolve the push registry and emit shell `export` lines materializing it as an
|
|
# env setting. Capture with: eval "$(... select-push-registry --kind sops ...)"
|
|
# Sets PROVISIONING_REGISTRY (endpoint) and, when credentials are present,
|
|
# PROVISIONING_PUSH_DOCKER_CONFIG (an isolated docker config dir the OCI resolver
|
|
# uses instead of the workspace-derived credential).
|
|
export def select-push-registry [
|
|
--kind: string = ""
|
|
--value: string = ""
|
|
--endpoint-key: string = "endpoint"
|
|
--user-key: string = "username"
|
|
--pass-key: string = "password"
|
|
--vault-id: string = ""
|
|
--age-key: string = ""
|
|
--auth-header: string = ""
|
|
]: nothing -> nothing {
|
|
let r = (resolve-push-registry --kind $kind --value $value --endpoint-key $endpoint_key --user-key $user_key --pass-key $pass_key --vault-id $vault_id --age-key $age_key --auth-header $auth_header)
|
|
if ($r.endpoint | is-empty) {
|
|
error make --unspanned { msg: "push-registry resolved to an empty endpoint" }
|
|
}
|
|
print $"export PROVISIONING_REGISTRY=($r.endpoint)"
|
|
if ($r.username | is-not-empty) {
|
|
let dir = (build-docker-config-tmpdir $r.endpoint $r.username $r.password)
|
|
print $"export PROVISIONING_PUSH_DOCKER_CONFIG=($dir)"
|
|
}
|
|
}
|