#!/usr/bin/env nu # provisioning dns — DNS record management via the configured provider middleware. use ../../../catalog/providers/prov_lib/middleware.nu [ mw_dns_zone_get mw_dns_record_list mw_dns_record_create mw_dns_record_update mw_dns_record_delete mw_dns_record_upsert ] def load_capabilities [ws_path: string]: nothing -> record { let cap_file = ($ws_path | path join "infra/libre-wuji/capabilities.ncl") if not ($cap_file | path exists) { return {} } let prov_root = ($env.PROVISIONING? | default "/usr/local/provisioning") let r = (do { ^nickel export --format json --import-path $prov_root $cap_file } | complete) if $r.exit_code != 0 { return {} } ($r.stdout | from json).provides?.dns? | default {} } def load_zones [workspace?: string]: nothing -> record { let ws_path = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default ".")) let zones_file = ($ws_path | path join "infra/lib/dns_zones.ncl") if not ($zones_file | path exists) { error make --unspanned { msg: $"dns_zones.ncl not found at ($zones_file)" } } let r = (do { ^nickel export --format json $zones_file } | complete) if $r.exit_code != 0 { error make --unspanned { msg: $"Failed to evaluate dns_zones.ncl: ($r.stderr)" } } ($r.stdout | from json).zones } def select_zone [zones: record, zone?: string]: nothing -> string { if ($zone | is-not-empty) { return $zone } let names = ($zones | columns) if ($names | length) == 1 { return ($names | first) } let sel = ($names | input list "Select zone:") if ($sel | is-empty) { error make --unspanned { msg: "No zone selected" } } $sel } def resolve_provider [zones: record, zone: string]: nothing -> string { let override = ($env | get -o DNS_PROVIDER_OVERRIDE | default "") if ($override | is-not-empty) { return $override } let z = ($zones | get -o $zone) if ($z | is-empty) { error make --unspanned { msg: $"Zone '($zone)' not found in dns_zones.ncl" } } $z.provider | into string } def --env set_cf_token [zones: record, zone: string, ws_path: string]: nothing -> nothing { let override = ($env | get -o DNS_PROVIDER_OVERRIDE | default "") if ($override | is-not-empty) and $override != "cloudflare" { return } let z = ($zones | get -o $zone | default {}) let secret_ref = ($z.secret_ref? | default "") if ($secret_ref | is-empty) { return } # env var override — CI / pre-exported token let env_key = ($"CF_TOKEN_($secret_ref | str upcase | str replace --all '-' '_')") let from_env = ($env | get -o $env_key | default "") if ($from_env | is-not-empty) { load-env { CF_API_TOKEN: $from_env } return } # ensure SOPS_AGE_KEY_FILE is set — look for .kage in the infra tree if ($env | get -o SOPS_AGE_KEY_FILE | default "" | is-empty) { let kage_hits = (glob ($"($ws_path)/infra/*/.kage") | append (glob ($"($ws_path)/.p/.kage"))) if ($kage_hits | is-not-empty) { load-env { SOPS_AGE_KEY_FILE: ($kage_hits | first) } } } # auto-decrypt SOPS file: infra/*/secrets/.sops.yaml let matches = (glob ($"($ws_path)/infra/*/secrets/($secret_ref).sops.yaml")) let sops_file = if ($matches | is-empty) { "" } else { $matches | first } if ($sops_file | is-empty) { error make --unspanned { msg: $"No SOPS file found for ($secret_ref) in ($ws_path)/infra/*/secrets/" } } let r = (do { ^sops --decrypt $sops_file } | complete) if $r.exit_code != 0 { error make --unspanned { msg: $"sops --decrypt failed for ($sops_file): ($r.stderr)" } } let token = ($r.stdout | from yaml | get -o token | default "") if ($token | is-empty) { error make --unspanned { msg: $"Key 'token' not found in ($sops_file)" } } load-env { CF_API_TOKEN: $token } } # List DNS records for a zone. Filter by --type or --source. Omit zone for interactive selector. # With --all: always shows the full zone selector regardless of how many zones are configured. export def "dns show" [ zone?: string --all (-a) --type (-t): string --source: string --raw (-r) --workspace (-w): string ]: nothing -> table { let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default ".")) let zones = (load_zones $ws) let zname = if $all { let sel = ($zones | columns | input list "Select zone:") if ($sel | is-empty) { error make --unspanned { msg: "No zone selected" } } $sel } else { select_zone $zones $zone } set_cf_token $zones $zname $ws let provider = (resolve_provider $zones $zname) mut records = (mw_dns_record_list $provider $zname) if ($type | is-not-empty) { $records = ($records | where type == ($type | str upcase)) } if ($source | is-not-empty) { $records = ($records | where source == $source) } if $raw { return $records } $records | sort-by type name | each {|r| let val = if $r.type == "MX" and ($r.priority? | is-not-empty) { $"($r.priority) ($r.value)" } else if $r.type == "TXT" and ($r.value | str length) > 80 { $"($r.value | str substring 0..79)…" } else { $r.value } { type: $r.type, name: $r.name, value: $val, ttl: $r.ttl, px: (if ($r.proxied? | default false) { "✓" } else { "-" }), } } } # Add a DNS record. Omit args for interactive prompts. # Example: dns add mail.example.com A 1.2.3.4 --zone example.com export def "dns add" [ name?: string type?: string value?: string --ttl (-l): int = 300 --zone (-z): string --priority: int --workspace (-w): string --proxied (-p) ]: nothing -> record { let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default ".")) let zones = (load_zones $ws) let resolved_zone = (select_zone $zones $zone) set_cf_token $zones $resolved_zone $ws let provider = (resolve_provider $zones $resolved_zone) let rec_name = if ($name | is-not-empty) { $name } else { let v = (input $" name (e.g. mail.($resolved_zone)): ") if ($v | is-empty) { error make --unspanned { msg: "name is required" } } $v } let rec_type = if ($type | is-not-empty) { $type | str upcase } else { let sel = (["A" "AAAA" "CNAME" "MX" "TXT" "CAA" "NS" "SRV"] | input list "Type:") if ($sel | is-empty) { error make --unspanned { msg: "type is required" } } $sel } let rec_value = if ($value | is-not-empty) { $value } else { let v = (input " value: ") if ($v | is-empty) { error make --unspanned { msg: "value is required" } } $v } let rec_ttl = if ($name | is-not-empty) { $ttl } else { let v = (input $" ttl [($ttl)]: ") if ($v | is-empty) { $ttl } else { $v | into int } } let rec_proxied = if $proxied { true } else if ($name | is-not-empty) { false } else { (input " proxied y/n [n]: ") == "y" } let rec_priority = if ($priority | is-not-empty) { $priority } else if $rec_type == "MX" { let v = (input " priority [10]: ") if ($v | is-empty) { 10 } else { $v | into int } } else { null } mut rec = { zone: $resolved_zone, name: $rec_name, type: $rec_type, value: $rec_value, ttl: $rec_ttl, proxied: $rec_proxied, source: "manual", } if ($rec_priority | is-not-empty) { $rec = ($rec | upsert priority $rec_priority) } mw_dns_record_create $provider $rec } # Delete a DNS record by name and type. Requires --confirm. export def "dns delete" [ name: string type: string --zone (-z): string --workspace (-w): string --confirm ]: nothing -> nothing { if not $confirm { error make --unspanned { msg: "Pass --confirm to delete a DNS record" } } let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default ".")) let zones = (load_zones $ws) let resolved_zone = (select_zone $zones $zone) set_cf_token $zones $resolved_zone $ws let provider = (resolve_provider $zones $resolved_zone) let rtype = ($type | str upcase) let live = (mw_dns_record_list $provider $resolved_zone | where { |r| $r.name == $name and $r.type == $rtype }) if ($live | is-empty) { print $"No record found: ($name) ($rtype) in ($resolved_zone)" return } mw_dns_record_delete $provider ($live | first).id $resolved_zone print $"Deleted ($name) ($rtype) from ($resolved_zone)" } # Edit a DNS record interactively — select from live records, change value/ttl/proxied. export def "dns edit" [ zone?: string --workspace (-w): string ]: nothing -> nothing { let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default ".")) let zones = (load_zones $ws) let zname = (select_zone $zones $zone) set_cf_token $zones $zname $ws let provider = (resolve_provider $zones $zname) let records = (mw_dns_record_list $provider $zname | sort-by type name) if ($records | is-empty) { print $"No records found for ($zname)" return } let labels = ($records | each {|r| let px = if ($r.proxied? | default false) { "⚡" } else { " " } let pri = if $r.type == "MX" and ($r.priority? | is-not-empty) { $"($r.priority) " } else { "" } let val = if ($r.value | str length) > 55 { $"($r.value | str substring 0..54)…" } else { $r.value } $"($r.type | fill -w 6)($px) ($r.name) → ($pri)($val)" }) let idx = ($labels | input list --index "Select record:") if ($idx | is-empty) { return } let rec = ($records | get $idx) print $" (ansi cyan)($rec.type)(ansi reset) ($rec.name)" let action = (["Edit" "Delete"] | input list "Action:") if ($action | is-empty) { return } if $action == "Delete" { let confirm = (input " Delete this record? y/N: ") if $confirm != "y" { print "Cancelled."; return } mw_dns_record_delete $provider $rec.id $zname print $" deleted: ($rec.type) ($rec.name)" return } let raw_value = (input $" value [($rec.value)]: ") let new_value = if ($raw_value | is-empty) { $rec.value } else { $raw_value } let raw_ttl = (input $" ttl [($rec.ttl)]: ") let new_ttl = if ($raw_ttl | is-empty) { $rec.ttl } else { $raw_ttl | into int } let cur_px = if ($rec.proxied? | default false) { "y" } else { "n" } let raw_px = (input $" proxied [($cur_px)] y/n: ") let new_proxied = if ($raw_px | is-empty) { $rec.proxied? | default false } else { $raw_px == "y" } if $new_value == $rec.value and $new_ttl == $rec.ttl and $new_proxied == ($rec.proxied? | default false) { print "No changes." return } let updated = { zone: $zname, name: $rec.name, type: $rec.type, value: $new_value, ttl: $new_ttl, proxied: $new_proxied, } mw_dns_record_update $provider $rec.id $updated | ignore print $" updated: ($rec.type) ($rec.name)" } # Reconcile static_records in dns_zones.ncl against live provider state. # Skips records owned by external-dns (txt ownership) and docker-mailserver (label). # With --apply: creates/updates; orphans always require manual confirmation. export def "dns sync" [ --zone (-z): string --workspace (-w): string --dry-run --apply ]: nothing -> table { let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default ".")) let zones = (load_zones $ws) let target_zones = if ($zone | is-not-empty) { [$zone] } else { $zones | columns } $target_zones | each {|zname| let z = ($zones | get $zname) set_cf_token $zones $zname $ws let provider = (resolve_provider $zones $zname) let declared = ($z.static_records? | default []) let live = (mw_dns_record_list $provider $zname) let live_manual = ($live | where { |r| ($r.source? | default "manual") == "manual" }) let declared_keys = ($declared | each {|d| { zone: $zname, name: $d.name, type: ($d.type | into string) } }) let missing = ($declared | where {|d| let key = { zone: $zname, name: $d.name, type: ($d.type | into string) } not ($live_manual | any { |r| $r.name == $d.name and $r.type == ($d.type | into string) }) } | each {|d| { action: "create", zone: $zname, name: $d.name, type: ($d.type | into string), live_value: "", declared_value: $d.value }}) let updates = ($declared | where {|d| $live_manual | any { |r| $r.name == $d.name and $r.type == ($d.type | into string) and $r.value != $d.value } } | each {|d| let live_r = ($live_manual | where { |r| $r.name == $d.name and $r.type == ($d.type | into string) } | first) { action: "update", zone: $zname, name: $d.name, type: ($d.type | into string), live_value: $live_r.value, declared_value: $d.value } }) let orphans = ($live_manual | where {|r| not ($declared | any {|d| $d.name == $r.name and ($d.type | into string) == $r.type }) } | each {|r| { action: "orphan", zone: $zname, name: $r.name, type: $r.type, live_value: $r.value, declared_value: "" } }) let diff_table = ($missing ++ $updates ++ $orphans) if $apply and not $dry_run { for row in ($missing ++ $updates) { let rec = ($declared | where { |d| $d.name == $row.name and ($d.type | into string) == $row.type } | first) mw_dns_record_upsert $provider ({ zone: $zname } | merge $rec) | ignore print $" applied ($row.action): ($row.name) ($row.type)" } } $diff_table } | flatten } # Show zones with active infra component usage. Use --all to include unused zones. export def "dns list" [ --all (-a) --workspace (-w): string ]: nothing -> table { let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default ".")) let zones = (load_zones $ws) let zone_names = ($zones | columns) let comp_files = (glob ($"($ws)/infra/*/components/*.ncl")) let usage = ($comp_files | each {|f| let component = ($f | path basename | str replace --regex '\.ncl$' '') let lines = (open --raw $f | lines) $zone_names | each {|zone| let fqdns = ($lines | where { $in | str contains $zone } | each {|line| $line | parse --regex '"([^"]+)"' | get capture0 } | flatten | each { $in | str replace --regex '^https?://' '' } | uniq | where { ($in | str ends-with $zone) and not ($in | str contains "@") } ) $fqdns | each {|fqdn| { zone: $zone, component: $component, fqdn: $fqdn }} } | flatten } | flatten | uniq | sort-by zone component) if not $all { return ($usage | sort-by zone component) } let used_zones = ($usage | get zone | uniq) let unused = ($zone_names | where {|z| not ($used_zones | any {|u| $u == $z }) } | each {|z| { zone: $z, component: "-", fqdn: "-" }} ) $usage ++ $unused | sort-by zone component } # Query the live DKIM TXT record for a domain. Omit domain for interactive selector. export def "dns dkim show" [ domain?: string --selector (-s): string = "mail" --workspace (-w): string ]: nothing -> record { let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default ".")) let zones = (load_zones $ws) let zname = (select_zone $zones $domain) let provider = (resolve_provider $zones $zname) set_cf_token $zones $zname $ws let dkim_name = $"($selector)._domainkey.($zname)" let records = (mw_dns_record_list $provider $zname | where { |r| $r.name == $dkim_name and $r.type == "TXT" }) if ($records | is-empty) { error make --unspanned { msg: $"No DKIM TXT record found for ($dkim_name)" } } $records | first } def "main list" [ --all (-a) --workspace (-w): string ]: nothing -> table { dns list --all=$all --workspace $workspace } def "main l" [--workspace (-w): string]: nothing -> table { dns list --workspace $workspace } def "main la" [--workspace (-w): string]: nothing -> table { dns list --all --workspace $workspace } def "main s" [ zone?: string --all (-a) --type (-t): string --source: string --raw (-r) --workspace (-w): string ]: nothing -> table { dns show $zone --all=$all --type $type --source $source --raw=$raw --workspace $workspace } def "main sa" [ --type (-t): string --source: string --raw (-r) --workspace (-w): string ]: nothing -> table { dns show --all --type $type --source $source --raw=$raw --workspace $workspace } def "main e" [zone?: string --workspace (-w): string]: nothing -> nothing { dns edit $zone --workspace $workspace } def "main a" [ name?: string type?: string value?: string --ttl (-l): int = 300 --zone (-z): string --priority: int --workspace (-w): string --proxied (-p) ]: nothing -> record { dns add $name $type $value --ttl $ttl --zone $zone --priority $priority --workspace $workspace --proxied=$proxied } def "main d" [ name: string type: string --zone (-z): string --workspace (-w): string --confirm ]: nothing -> nothing { dns delete $name $type --zone $zone --workspace $workspace --confirm=$confirm } def "main sy" [ --zone (-z): string --workspace (-w): string --dry-run --apply ]: nothing -> table { dns sync --zone $zone --workspace $workspace --dry-run=$dry_run --apply=$apply } def "main show" [ zone?: string --all (-a) --type (-t): string --source: string --raw (-r) --workspace (-w): string ]: nothing -> table { dns show $zone --all=$all --type $type --source $source --raw=$raw --workspace $workspace } def "main add" [ name?: string type?: string value?: string --ttl (-l): int = 300 --zone (-z): string --priority: int --workspace (-w): string --proxied (-p) ]: nothing -> record { dns add $name $type $value --ttl $ttl --zone $zone --priority $priority --workspace $workspace --proxied=$proxied } def "main edit" [ zone?: string --workspace (-w): string ]: nothing -> nothing { dns edit $zone --workspace $workspace } def "main delete" [ name: string type: string --zone (-z): string --workspace (-w): string --confirm ]: nothing -> nothing { dns delete $name $type --zone $zone --workspace $workspace --confirm=$confirm } def "main sync" [ --zone (-z): string --workspace (-w): string --dry-run --apply ]: nothing -> table { dns sync --zone $zone --workspace $workspace --dry-run=$dry_run --apply=$apply } def "main dkim show" [ domain?: string --selector (-s): string = "mail" --workspace (-w): string ]: nothing -> record { dns dkim show $domain --selector $selector --workspace $workspace } # Collect dns_internal entries from all component NCL files in the workspace. # Returns list of { host, zone, target, ttl, component } records. def collect-dns-internal [ws: string, prov_root: string]: nothing -> list { let comp_glob = ($ws | path join "infra" "*" "components" "*.ncl") let split_file = ($ws | path join "infra" "libre-wuji" "components" "coredns_vpn_split.ncl") (glob $comp_glob) | where { $in != $split_file } | each {|f| let r = (do { ^nickel export --format json --import-path $prov_root $f } | complete) if $r.exit_code != 0 { return [] } let data = ($r.stdout | from json) let comp = ($f | path basename | str replace --regex '\.ncl$' '') $data | columns | each {|key| let val = ($data | get $key) let entries = if ($val | describe | str starts-with "record") { $val.dns_internal? | default [] } else { [] } $entries | each {|e| { host: $e.host, zone: $e.zone, target: $e.target, ttl: ($e.ttl? | default 300 | into int), component: $comp } } } | flatten } | flatten } # Generate the Nickel content for coredns_vpn_split.ncl from grouped zone data. # zones_map: record where keys are zone names, values are lists of { host, target, ttl }. def gen-split-ncl [zones_map: record]: nothing -> string { let entries = ($zones_map | transpose zone records | each {|row| let recs = ($row.records | sort-by host | each {|r| $" \{ name = \"($r.host)\", value = \"($r.target)\", ttl = ($r.ttl), rectype = \"A\", etcd_dns_ttl = 300, etcd_peer_port = 2380, etcd_cli_port = 2379 }," } | str join "\n") $" \{ kind = 'private_zone, domain = \"($row.zone)\", port = 53, bind = bind_ip, records = [ ($recs) ], forward = \{ source = \".\", forward_ip = null }, use_log = true, use_errors = true, use_cache = false, etcd_cluster_name = \"\", }," } | str join "\n") $"# Generated by `prvng dns build-private` — do not edit manually. # Source of truth: dns_internal fields in infra/*/components/*.ncl # Re-run `prvng dns build-private --apply` to regenerate from current declarations. fun bind_ip => \{ entries = [ ($entries) ], }" } # List all private DNS entries declared via dns_internal across all workspace components. # Reads every component NCL file and aggregates dns_internal entries into a table. export def "dns priv-list" [ --workspace (-w): string --zone (-z): string --raw (-r) ]: nothing -> table { let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default ".")) let prov_root = ($env.PROVISIONING? | default "/usr/local/provisioning") let entries = (collect-dns-internal $ws $prov_root) if ($entries | is-empty) { print "No dns_internal entries found." return [] } let filtered = if ($zone | is-not-empty) { $entries | where zone == $zone } else { $entries } if $raw { return $filtered } $filtered | sort-by zone host | each {|r| { zone: $r.zone host: $r.host target: $r.target ttl: $r.ttl component: $r.component } } } # Reconcile dns_internal declarations across all components with coredns_vpn_split.ncl. # Reads every component NCL file, collects dns_internal entries, groups by zone, # and generates the canonical split file. With --apply: writes the file to disk. # After --apply: run `just install-component coredns_vpn libre-wuji-cp-0` to deploy. export def "dns build-private" [ --workspace (-w): string --dry-run --apply ]: nothing -> table { let ws = ($workspace | default ($env.PROVISIONING_WORKSPACE? | default ".")) let prov_root = ($env.PROVISIONING? | default "/usr/local/provisioning") let split_out = ($ws | path join "infra" "libre-wuji" "components" "coredns_vpn_split.ncl" | path expand) let all_entries = (collect-dns-internal $ws $prov_root) if ($all_entries | is-empty) { print "No dns_internal entries found in any component." return [] } let zones_map = ($all_entries | group-by zone | transpose zone records | each {|row| let recs = ($row.records | each {|e| { host: $e.host, target: $e.target, ttl: $e.ttl } }) { $row.zone: $recs } } | reduce --fold {} {|it, acc| $acc | merge $it }) let new_content = (gen-split-ncl $zones_map) # Read existing split file by exporting the function applied to "10.0.8.20". # We write a wrapper NCL expression to a temp file and nickel-export it. let existing_entries = if ($split_out | path exists) { let tmpfile = ($nu.temp-dir | path join "dns_build_private_eval.ncl") let import_expr = $"(char lparen)import \"($split_out)\"(char rparen) \"10.0.8.20\"" $import_expr | save --force $tmpfile let r = (do { ^nickel export --format json $tmpfile } | complete) if ($tmpfile | path exists) { rm $tmpfile } if $r.exit_code == 0 { ($r.stdout | from json).entries? | default [] | each {|e| let zone = ($e.domain? | default "") $e.records? | default [] | each {|rec| { host: ($rec.name? | default ""), zone: $zone, target: ($rec.value? | default "") } } } | flatten } else { [] } } else { [] } let desired_flat = ($all_entries | each {|e| { host: $e.host, zone: $e.zone, target: $e.target }}) let to_add = ($desired_flat | where {|d| not ($existing_entries | any {|ex| $ex.host == $d.host and $ex.zone == $d.zone and $ex.target == $d.target }) } | each {|r| { action: "add", zone: $r.zone, host: $r.host, target: $r.target }}) let to_remove = ($existing_entries | where {|ex| not ($desired_flat | any {|d| $d.host == $ex.host and $d.zone == $ex.zone }) } | each {|r| { action: "remove", zone: $r.zone, host: $r.host, target: $r.target }}) let diff = ($to_add ++ $to_remove | sort-by zone host) if ($diff | is-empty) { print $"(ansi green)✓(ansi reset) coredns_vpn_split.ncl is up to date — ($desired_flat | length) records across ($zones_map | columns | length) zones." return $diff } if not $dry_run and $apply { $new_content | save --force $split_out print $"(ansi green)✓(ansi reset) Written ($split_out)" print $" Run: just install-component coredns_vpn libre-wuji-cp-0" } else { let n_changes = ($diff | length) print $"Diff ($n_changes) changes - pass --apply to write ($split_out | path basename):" } $diff } def print_help []: nothing -> nothing { let title = "DNS Record Management" let bar = (1..($title | str length) | each {|_| "=" } | str join "") print $title print $bar print "" print " Usage: prvng dns [options]" print "" print $" (ansi green)Read:(ansi reset)" print $" (ansi cyan)list \(l\)(ansi reset) [--all/-a] zones with active component usage" print $" (ansi cyan)la(ansi reset) alias: list --all" print $" (ansi cyan)show \(s\)(ansi reset) [zone] [--all] [--type T] live records; --all forces zone selector" print $" (ansi cyan)dkim show(ansi reset) [domain] [--selector S] live DKIM TXT record" print "" print $" (ansi green)Write:(ansi reset)" print $" (ansi cyan)add \(a\)(ansi reset) [name type value] create record; prompts if args omitted" print $" (ansi cyan)edit \(e\)(ansi reset) [zone] interactive editor and delete" print $" (ansi cyan)delete \(d\)(ansi reset) --confirm remove record [--zone Z]" print $" (ansi cyan)sync \(sy\)(ansi reset) [--zone Z] [--apply] reconcile static_records vs live" print "" print $" (ansi green)Flags for show:(ansi reset)" print " --type / -t T filter by type (A, AAAA, CNAME, MX, TXT...)" print " --source S filter by source" print " --raw / -r return raw table, skip formatting" print " --workspace / -w P override workspace path" print "" print $" (ansi green)Private DNS:(ansi reset)" print $" (ansi cyan)priv-list (char lparen)pl(char rparen)(ansi reset) [--zone Z] [--raw] list all dns_internal records from components" print $" (ansi cyan)build-private (char lparen)bp(char rparen)(ansi reset) [--apply] [--dry-run] sync coredns_vpn_split.ncl from dns_internal declarations" print "" print $" (ansi green)Aliases:(ansi reset)" print " l -> list la -> list --all s -> show sa -> show --all" print " e -> edit a -> add d -> delete sy -> sync" print " pl -> priv-list bp -> build-private" } def "main help" []: nothing -> nothing { print_help } def "main build-private" [--workspace (-w): string --dry-run --apply]: nothing -> table { dns build-private --workspace $workspace --dry-run=$dry_run --apply=$apply } def "main bp" [--workspace (-w): string --dry-run --apply]: nothing -> table { dns build-private --workspace $workspace --dry-run=$dry_run --apply=$apply } def "main priv-list" [--workspace (-w): string --zone (-z): string --raw (-r)]: nothing -> table { dns priv-list --workspace $workspace --zone $zone --raw=$raw } def "main pl" [--workspace (-w): string --zone (-z): string --raw (-r)]: nothing -> table { dns priv-list --workspace $workspace --zone $zone --raw=$raw } def main [...args: string]: nothing -> nothing { let ws = ($env.PROVISIONING_WORKSPACE? | default ".") let first = ($args | get -o 0 | default "") let dns_cap = (load_capabilities $ws) let aliases = ($dns_cap.provider_aliases? | default {}) if ($first | is-not-empty) and ($first in ($aliases | columns)) { let provider = ($aliases | get $first | into string) let rest = ($args | skip 1) let sub = ($rest | get -o 0 | default "") let sub_rest = ($rest | skip 1) with-env { DNS_PROVIDER_OVERRIDE: $provider } { match $sub { "show" | "s" => { let z = ($sub_rest | get -o 0) if ($z | is-not-empty) { dns show $z --workspace $ws } else { dns show --workspace $ws } } "add" | "a" => { dns add ($sub_rest | get -o 0) ($sub_rest | get -o 1) ($sub_rest | get -o 2) --workspace $ws } "edit" | "e" => { let z = ($sub_rest | get -o 0) if ($z | is-not-empty) { dns edit $z --workspace $ws } else { dns edit --workspace $ws } } "delete" | "d" => { if ($sub_rest | length) < 2 { error make --unspanned { msg: $"delete requires --confirm" } } dns delete ($sub_rest | get 0) ($sub_rest | get 1) --workspace $ws --confirm } "list" | "l" => { dns list --workspace $ws } "sync" | "sy" => { dns sync --workspace $ws } "dkim show" => { let z = ($sub_rest | get -o 0) if ($z | is-not-empty) { dns dkim show $z --workspace $ws } else { dns dkim show --workspace $ws } } _ => { print $"Unknown subcommand: ($sub)" print "" print_help } } } } else { match $first { "" | "help" | "h" | "-h" | "--help" => { print_help } _ => { print $"Unknown subcommand: ($first)" print "" print_help } } } }