- display.module.sh: style toggle per view (compact/table) - display.json: default config with all views set to compact - ctx::display: points to .wgctl/config/display.json - list: _render_table with dynamic widths, colors, shared row_color/status_color - group/identity/net/hosts/activity: _render_table added - rule/subnet/policy: table UI functions + _render_table - ui::peer::status_color: \033[2m for offline (dimmer, more readable) - note: individual table layout refinements pending cleanup pass - note: configurable colors per field deferred to display config v2
630 lines
No EOL
19 KiB
Bash
630 lines
No EOL
19 KiB
Bash
#!/usr/bin/env bash
|
|
# identity.command.sh — manage peer identities
|
|
#
|
|
# Subcommands:
|
|
# wgctl identity list
|
|
# wgctl identity show --name <name>
|
|
# wgctl identity add --name <name> --peer <peer>
|
|
# wgctl identity remove --name <name>
|
|
# wgctl identity migrate [--dry-run]
|
|
|
|
# ============================================
|
|
# Lifecycle
|
|
# ============================================
|
|
|
|
function cmd::identity::on_load() {
|
|
load_module identity
|
|
load_module policy
|
|
|
|
flag::register --name
|
|
flag::register --peer
|
|
flag::register --dry-run
|
|
flag::register --force
|
|
flag::register --rule
|
|
flag::register --policy
|
|
flag::register --set-strict-rule
|
|
flag::register --unset-strict-rule
|
|
flag::register --set-auto-apply
|
|
flag::register --unset-auto-apply
|
|
flag::register --field
|
|
flag::register --value
|
|
flag::register --migrate
|
|
|
|
command::mixin json_output
|
|
}
|
|
|
|
# ============================================
|
|
# Help
|
|
# ============================================
|
|
|
|
function cmd::identity::help() {
|
|
cat <<EOF
|
|
Usage: wgctl identity <subcommand> [options]
|
|
|
|
Manage peer identities — group peers by person/device owner.
|
|
|
|
Subcommands:
|
|
list List all identities
|
|
show --name <n> Show identity details with peers and rule tree
|
|
add --name <n> Create a new identity
|
|
remove --name <n> Remove an identity
|
|
migrate Migrate peers to identities
|
|
|
|
rule assign --name <n> --rule <r> Assign rule to identity
|
|
Blocked if peer already has rule directly
|
|
[--migrate] Remove conflicting direct peer rules first
|
|
rule unassign --name <n> --rule <r> Remove rule from identity
|
|
rule unassign --name <n> --all Remove all rules from identity
|
|
|
|
options --name <n> --strict-rule <bool> Set strict rule mode
|
|
options --name <n> --auto-apply <bool> Set auto apply
|
|
|
|
Examples:
|
|
wgctl identity list
|
|
wgctl identity show --name nuno
|
|
wgctl identity add --name alice
|
|
wgctl identity rule assign --name nuno --rule admin
|
|
wgctl identity rule assign --name nuno --rule user --migrate
|
|
wgctl identity rule unassign --name nuno --rule admin
|
|
wgctl identity options --name nuno --strict-rule true
|
|
EOF
|
|
}
|
|
|
|
# ============================================
|
|
# Run
|
|
# ============================================
|
|
|
|
function cmd::identity::run() {
|
|
local subcmd="${1:-list}"
|
|
shift || true
|
|
|
|
if command::json && [[ "$subcmd" == "list" ]]; then
|
|
cmd::identity::_output_json
|
|
return 0
|
|
fi
|
|
|
|
case "$subcmd" in
|
|
list) cmd::identity::_list "$@" ;;
|
|
show) cmd::identity::_show "$@" ;;
|
|
add) cmd::identity::_add "$@" ;;
|
|
remove) cmd::identity::_remove "$@" ;;
|
|
migrate) cmd::identity::_migrate "$@" ;;
|
|
rule) cmd::identity::_rule "$@" ;;
|
|
options) cmd::identity::_options "$@" ;;
|
|
--help) cmd::identity::help ;;
|
|
*)
|
|
log::error "Unknown subcommand '${subcmd}'. Available: list, show, add, remove, migrate, rule, options"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ============================================
|
|
# Subcommands
|
|
# ============================================
|
|
|
|
function cmd::identity::_list() {
|
|
local data
|
|
data=$(identity::list_data | ui::sort_rows 1)
|
|
|
|
if [[ -z "$data" ]]; then
|
|
log::info "No identities found. Run 'wgctl identity migrate' to create from existing peers."
|
|
return 0
|
|
fi
|
|
|
|
if display::is_table "identity_list"; then
|
|
cmd::identity::_render_table "$data"
|
|
return 0
|
|
fi
|
|
|
|
echo ""
|
|
while IFS='|' read -r name peer_count types rules policy; do
|
|
local rules_display
|
|
rules_display=$(echo "$rules" | sed 's/,/, /g')
|
|
ui::identity::list_row_compact "$name" "$peer_count" "$rules_display" "$policy"
|
|
done <<< "$data"
|
|
echo ""
|
|
}
|
|
|
|
function cmd::identity::_show() {
|
|
local name=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--help) cmd::identity::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
identity::require_exists "$name" || return 1
|
|
|
|
# Gather identity-level metadata
|
|
local policy strict auto rules_list peer_count
|
|
policy=$(identity::policy "$name")
|
|
strict=$(identity::rule_flags "$name" "strict_rule")
|
|
auto=$(identity::rule_flags "$name" "auto_apply")
|
|
rules_list=$(identity::rules "$name" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')
|
|
|
|
local data
|
|
data=$(identity::show_data "$name")
|
|
peer_count=$(echo "$data" | grep '^peer_count|' | cut -d'|' -f2)
|
|
|
|
# Precompute handshakes once for all peers
|
|
declare -A _id_handshakes=()
|
|
while IFS=$'\t' read -r pk ts; do
|
|
[[ -n "$pk" ]] && _id_handshakes["$pk"]="$ts"
|
|
done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null)
|
|
|
|
# Header
|
|
echo ""
|
|
ui::row "Identity" "$name"
|
|
ui::row "Policy" "$policy"
|
|
ui::row "Rules" "${rules_list:-—}"
|
|
ui::row "Strict rule" "$(ui::bool "$strict")"
|
|
ui::row "Auto apply" "$(ui::bool "$auto")"
|
|
ui::row "Peers" "$peer_count"
|
|
echo ""
|
|
|
|
# Device list
|
|
while IFS='|' read -r key val type_val index_val; do
|
|
case "$key" in
|
|
name|peer_count) ;;
|
|
device)
|
|
local status=""
|
|
status=$(cmd::identity::_device_status "$val" _id_handshakes)
|
|
ui::identity::device_row "$val" "$type_val" "$index_val" "$status"
|
|
;;
|
|
esac
|
|
done <<< "$data"
|
|
|
|
# Rules tree
|
|
local identity_rules
|
|
identity_rules=$(identity::rules "$name")
|
|
if [[ -n "$identity_rules" ]]; then
|
|
printf "\n \033[2m── Rules \033[0m%s\n\n" \
|
|
"$(printf '\033[2m─%.0s' {1..38})"
|
|
ui::rule::identity_block "$name" "$strict" --no-header
|
|
fi
|
|
|
|
echo ""
|
|
}
|
|
|
|
function cmd::identity::_render_table() {
|
|
local data="${1:-}"
|
|
[[ -z "$data" ]] && return 0
|
|
|
|
printf "\n %-20s %-8s %-20s %s\n" "NAME" "PEERS" "RULES" "POLICY"
|
|
printf " %s\n" "$(printf '─%.0s' {1..65})"
|
|
while IFS='|' read -r name peer_count types rules policy; do
|
|
[[ -z "$name" ]] && continue
|
|
local rules_display
|
|
rules_display=$(echo "$rules" | sed 's/,/, /g')
|
|
ui::identity::list_row_table "$name" "$peer_count" "$rules_display" "$policy"
|
|
done <<< "$data"
|
|
printf " %s\n\n" "$(printf '─%.0s' {1..65})"
|
|
}
|
|
|
|
function cmd::identity::_device_status() {
|
|
local peer_name="${1:-}"
|
|
local -n _handshakes="${2:-__empty_map}"
|
|
|
|
local peer_ip
|
|
peer_ip=$(peers::get_ip "$peer_name" 2>/dev/null) || return 0
|
|
[[ -z "$peer_ip" ]] && return 0
|
|
|
|
local is_blocked is_restricted pubkey handshake_ts
|
|
peers::is_blocked "$peer_name" && is_blocked="true" || is_blocked="false"
|
|
peers::is_restricted "$peer_name" && is_restricted="true" || is_restricted="false"
|
|
|
|
pubkey="$(keys::public "$peer_name")"
|
|
handshake_ts="${_handshakes[$pubkey]:-0}"
|
|
|
|
local last_ts
|
|
last_ts=$(peers::get_meta "$peer_name" "last_ts" 2>/dev/null) || last_ts=""
|
|
|
|
local status
|
|
status=$(peers::format_status_verbose \
|
|
"$peer_name" "$pubkey" "$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts")
|
|
echo " — ${status}"
|
|
}
|
|
|
|
function cmd::identity::_add() {
|
|
local name="" peer=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--peer) peer="$2"; shift 2 ;;
|
|
--help) cmd::identity::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
[[ -z "$peer" ]] && { log::error "Missing required flag: --peer"; return 1; }
|
|
|
|
cmd::identity::_require_peer_exists "$peer" || return 1
|
|
|
|
local peer_type index
|
|
peer_type=$(cmd::identity::_resolve_peer_type "$peer" "$name")
|
|
index=$(identity::next_index "$name" "$peer_type")
|
|
|
|
local id_file
|
|
id_file=$(ctx::identity::path "${name}.identity")
|
|
json::identity_add_peer "$id_file" "$name" "$peer" "$peer_type" "$index" </dev/null
|
|
log::ok "Added '${peer}' to identity '${name}' (${peer_type} #${index})"
|
|
}
|
|
|
|
function cmd::identity::_require_peer_exists() {
|
|
local peer="${1:-}"
|
|
if [[ ! -f "$(ctx::clients)/${peer}.conf" ]]; then
|
|
log::error "Peer '${peer}' not found"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function cmd::identity::_resolve_peer_type() {
|
|
local peer="${1:-}" identity_name="${2:-}"
|
|
local inferred
|
|
inferred=$(identity::infer "$peer")
|
|
if [[ -n "$inferred" ]]; then
|
|
echo "$inferred" | cut -d'|' -f2
|
|
else
|
|
peers::get_meta "$peer" "type" 2>/dev/null || echo "none"
|
|
fi
|
|
}
|
|
|
|
function cmd::identity::_remove() {
|
|
local name="" force=false
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--force) force=true; shift ;;
|
|
--help) cmd::identity::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
identity::require_exists "$name" || return 1
|
|
|
|
local peers
|
|
peers=$(identity::peers "$name")
|
|
|
|
if [[ -n "$peers" ]]; then
|
|
local peer_list="${peers//$'\n'/, }"
|
|
log::warn "This will permanently remove identity '${name}' and ALL associated peers:"
|
|
log::warn " ${peer_list}"
|
|
|
|
if ! $force; then
|
|
ui::confirm "Continue?" || { log::info "Aborted"; return 0; }
|
|
fi
|
|
|
|
cmd::identity::_remove_all_peers "$peers"
|
|
peers::reload || return 1
|
|
fi
|
|
|
|
local id_file
|
|
id_file=$(ctx::identity::path "${name}.identity")
|
|
json::identity_remove "$id_file" </dev/null
|
|
log::ok "Identity '${name}' removed"
|
|
}
|
|
|
|
function cmd::identity::_remove_all_peers() {
|
|
local peers="${1:-}"
|
|
while IFS= read -r peer_name; do
|
|
[[ -z "$peer_name" ]] && continue
|
|
cmd::identity::_remove_peer "$peer_name"
|
|
done <<< "$peers"
|
|
}
|
|
|
|
function cmd::identity::_remove_peer() {
|
|
local peer_name="${1:-}"
|
|
local client_ip was_blocked=false
|
|
|
|
client_ip=$(peers::get_ip "$peer_name" 2>/dev/null) || client_ip=""
|
|
peers::is_blocked "$peer_name" && was_blocked=true
|
|
|
|
peers::purge "$peer_name" "$client_ip" "$was_blocked" || return 1
|
|
log::ok "Removed peer '${peer_name}'"
|
|
}
|
|
|
|
function cmd::identity::_migrate() {
|
|
local dry_run="false"
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--dry-run) dry_run="true"; shift ;;
|
|
--help) cmd::identity::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ "$dry_run" == "true" ]] && log::info "Dry run — no files will be written"
|
|
echo ""
|
|
|
|
[[ "$dry_run" == "false" ]] && mkdir -p "$(ctx::identities)"
|
|
|
|
local created=0 skipped=0 output
|
|
output=$(json::identity_migrate \
|
|
"$(ctx::identities)" \
|
|
"$(ctx::clients)" \
|
|
"$(ctx::meta)" \
|
|
"$dry_run")
|
|
|
|
while IFS='|' read -r action identity_name peer_name peer_type index; do
|
|
case "$action" in
|
|
create)
|
|
ui::identity::migrate_create "$peer_name" "$identity_name" "$peer_type" "$index"
|
|
(( created++ )) || true
|
|
;;
|
|
skip)
|
|
ui::identity::migrate_skip "$peer_name"
|
|
(( skipped++ )) || true
|
|
;;
|
|
esac
|
|
done <<< "$output"
|
|
|
|
ui::identity::migrate_summary "$created" "$skipped" "$dry_run"
|
|
}
|
|
|
|
function cmd::identity::_rule() {
|
|
local subcmd="${1:-show}"
|
|
shift || true
|
|
|
|
case "$subcmd" in
|
|
assign) cmd::identity::_rule_assign "$@" ;;
|
|
unassign) cmd::identity::_rule_unassign "$@" ;;
|
|
show) cmd::identity::_rule_show "$@" ;;
|
|
*)
|
|
log::error "Unknown rule subcommand '${subcmd}'. Available: assign, unassign, show"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function cmd::identity::_rule_assign() {
|
|
local name="" rule="" migrate=false
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--rule) rule="$2"; shift 2 ;;
|
|
--migrate) migrate=true; shift ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
[[ -z "$rule" ]] && { log::error "Missing required flag: --rule"; return 1; }
|
|
identity::require_exists "$name" || return 1
|
|
rule::exists "$rule" || { log::error "Rule not found: ${rule}"; return 1; }
|
|
|
|
local conflicts=()
|
|
while IFS= read -r peer_name; do
|
|
[[ -z "$peer_name" ]] && continue
|
|
local peer_rule
|
|
peer_rule=$(peers::get_meta "$peer_name" "rule" 2>/dev/null)
|
|
[[ "$peer_rule" == "$rule" ]] && conflicts+=("$peer_name")
|
|
done < <(identity::peers "$name")
|
|
|
|
if [[ ${#conflicts[@]} -gt 0 ]]; then
|
|
if ! $migrate; then
|
|
log::error "The following peers have '${rule}' as a direct rule: ${conflicts[*]}"
|
|
log::error "Use --migrate to remove direct rules and let the identity rule take over."
|
|
return 1
|
|
fi
|
|
# Migrate — remove direct rules from conflicting peers
|
|
for peer_name in "${conflicts[@]}"; do
|
|
local ip
|
|
ip=$(peers::get_ip "$peer_name")
|
|
rule::unapply "$rule" "$ip"
|
|
log::wg "Migrated '${rule}' from peer '${peer_name}' to identity '${name}'"
|
|
done
|
|
fi
|
|
|
|
local exit_code=0
|
|
identity::add_rule "$name" "$rule" || exit_code=$?
|
|
|
|
if [[ $exit_code -eq 2 ]]; then
|
|
log::warn "Rule '${rule}' is already assigned to identity '${name}'"
|
|
return 0
|
|
fi
|
|
|
|
log::ok "Rule '${rule}' assigned to identity '${name}'"
|
|
|
|
# Reapply rules if auto_apply
|
|
local auto
|
|
auto=$(identity::rule_flags "$name" "auto_apply")
|
|
if [[ "$auto" != "false" ]]; then
|
|
log::info "Reapplying rules for all peers in identity '${name}'..."
|
|
identity::reapply_rules "$name"
|
|
log::ok "Rules reapplied"
|
|
fi
|
|
|
|
# Warn about strict_rule
|
|
if policy::strict_rule "$(identity::policy "$name")"; then
|
|
log::warn "Strict rule is enabled for identity '${name}' — peer rules will not be additive"
|
|
fi
|
|
}
|
|
|
|
function cmd::identity::_rule_unassign() {
|
|
local name="" rule="" all=false
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--rule) rule="$2"; shift 2 ;;
|
|
--all) all=true; shift ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
identity::require_exists "$name" || return 1
|
|
|
|
if $all; then
|
|
local rules
|
|
rules=$(identity::rules "$name")
|
|
if [[ -z "$rules" ]]; then
|
|
log::warn "Identity '${name}' has no rules assigned"
|
|
return 0
|
|
fi
|
|
identity::clear_rules "$name"
|
|
log::ok "All rules removed from identity '${name}'"
|
|
cmd::identity::_reapply_after_unassign "$name"
|
|
return 0
|
|
fi
|
|
|
|
[[ -z "$rule" ]] && {
|
|
log::error "Missing required flag: --rule (or use --all to remove all)"
|
|
return 1
|
|
}
|
|
|
|
identity::remove_rule "$name" "$rule"
|
|
local exit_code=$?
|
|
if [[ $exit_code -ne 0 ]]; then
|
|
log::error "Rule '${rule}' is not assigned to identity '${name}'"
|
|
return 1
|
|
fi
|
|
|
|
log::ok "Rule '${rule}' removed from identity '${name}'"
|
|
cmd::identity::_reapply_after_unassign "$name"
|
|
}
|
|
|
|
function cmd::identity::_reapply_after_unassign() {
|
|
local name="${1:-}"
|
|
local auto
|
|
auto=$(identity::rule_flags "$name" "auto_apply")
|
|
if [[ "$auto" != "false" ]]; then
|
|
log::info "Reapplying rules for all peers in identity '${name}'..."
|
|
identity::reapply_rules "$name"
|
|
log::ok "Rules reapplied"
|
|
else
|
|
log::info "Note: auto_apply is disabled — run 'wgctl audit --fix' to update fw rules"
|
|
fi
|
|
}
|
|
|
|
function cmd::identity::_rule_show() {
|
|
local name=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
identity::require_exists "$name" || return 1
|
|
|
|
local rules policy strict auto
|
|
rules=$(identity::rules "$name")
|
|
policy=$(identity::policy "$name")
|
|
strict=$(identity::rule_flags "$name" "strict_rule")
|
|
auto=$(identity::rule_flags "$name" "auto_apply")
|
|
|
|
echo ""
|
|
ui::row "Identity" "$name"
|
|
ui::row "Policy" "$policy"
|
|
ui::row "Strict rule" "$(ui::bool "$strict")"
|
|
ui::row "Auto apply" "$(ui::bool "$auto")"
|
|
echo ""
|
|
|
|
if [[ -z "$rules" ]]; then
|
|
ui::row "Rules" "— none assigned"
|
|
else
|
|
printf " %-20s\n" "Rules:"
|
|
while IFS= read -r rule_name; do
|
|
[[ -z "$rule_name" ]] && continue
|
|
printf " · %s\n" "$rule_name"
|
|
done <<< "$rules"
|
|
fi
|
|
echo ""
|
|
}
|
|
|
|
function cmd::identity::_options() {
|
|
local name="" new_policy=""
|
|
local set_strict="" set_auto=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--policy) new_policy="$2"; shift 2 ;;
|
|
--set-strict-rule) set_strict="true"; shift ;;
|
|
--unset-strict-rule) set_strict="false"; shift ;;
|
|
--set-auto-apply) set_auto="true"; shift ;;
|
|
--unset-auto-apply) set_auto="false"; shift ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
identity::require_exists "$name" || return 1
|
|
|
|
local changed=false
|
|
|
|
if [[ -n "$new_policy" ]]; then
|
|
policy::require_exists "$new_policy" || return 1
|
|
identity::set_policy "$name" "$new_policy"
|
|
log::ok "Policy set to '${new_policy}' for identity '${name}'"
|
|
changed=true
|
|
fi
|
|
|
|
if [[ -n "$set_strict" ]]; then
|
|
identity::set_rule_flag "$name" "strict_rule" "$set_strict"
|
|
if [[ "$set_strict" == "true" ]]; then
|
|
log::ok "Strict rule enabled for identity '${name}' — peer rules will not be additive"
|
|
else
|
|
log::ok "Strict rule disabled for identity '${name}' — peer rules will be additive"
|
|
fi
|
|
changed=true
|
|
fi
|
|
|
|
if [[ -n "$set_auto" ]]; then
|
|
identity::set_rule_flag "$name" "auto_apply" "$set_auto"
|
|
if [[ "$set_auto" == "true" ]]; then
|
|
log::ok "Auto apply enabled for identity '${name}'"
|
|
else
|
|
log::ok "Auto apply disabled for identity '${name}'"
|
|
fi
|
|
changed=true
|
|
fi
|
|
|
|
if ! $changed; then
|
|
cmd::identity::_rule_show --name "$name"
|
|
fi
|
|
}
|
|
|
|
function cmd::identity::_output_json() {
|
|
local data
|
|
data=$(identity::list_data 2>/dev/null)
|
|
|
|
local -a identities=()
|
|
while IFS='|' read -r name peer_count types rules policy; do
|
|
[[ -z "$name" ]] && continue
|
|
|
|
# Build rules array
|
|
local rules_json="[]"
|
|
if [[ -n "$rules" ]]; then
|
|
local rules_array
|
|
rules_array=$(echo "$rules" | tr ',' '\n' | \
|
|
while IFS= read -r r; do [[ -n "$r" ]] && printf '"%s",' "$r"; done | sed 's/,$//')
|
|
rules_json="[${rules_array}]"
|
|
fi
|
|
|
|
# Build types array (was comma-separated string)
|
|
local types_json="[]"
|
|
if [[ -n "$types" ]]; then
|
|
local types_array
|
|
types_array=$(echo "$types" | tr ',' '\n' | \
|
|
while IFS= read -r t; do [[ -n "$t" ]] && printf '"%s",' "$t"; done | sed 's/,$//')
|
|
types_json="[${types_array}]"
|
|
fi
|
|
|
|
identities+=("$(printf '{"name":"%s","peer_count":%s,"types":%s,"rules":%s,"policy":"%s"}' \
|
|
"$name" "$peer_count" "$types_json" "$rules_json" "$policy")")
|
|
done <<< "$data"
|
|
|
|
local count=${#identities[@]}
|
|
local array
|
|
array=$(printf '%s\n' "${identities[@]:-}" | paste -sd ',' -)
|
|
printf '{"identities":[%s]}' "${array:-}" | json::envelope "identity list" "$count"
|
|
}
|
|
|