From 9fb48cdb6e3023b510a04c750fb9098f5d8b9cce Mon Sep 17 00:00:00 2001 From: Zaid A Date: Thu, 2 Jul 2026 17:26:31 -0500 Subject: [PATCH] fix(createCollectionsForAllMembers): skip existing collections, fix N+1 calls, fix unlock branch - Skip instead of update: when a collection already exists inside the parent collection for a member, the script now logs a message and moves on to the next member rather than updating it - Eliminate N+1 API calls: fetch all org-collections once before the loop and filter in-memory with jq, instead of calling bw list org-collections once per member (~4s x N wasted API calls per run) - Fix broken unlock branch: remove the if/else unlock block where the 'already unlocked' else branch called bw unlock without providing credentials (would hang on stdin). Always supply password via printf; bw unlock is idempotent - Switch loop to process substitution: change 'echo | while read' to 'while read; done < <(...)' so the loop runs in the current shell, fixing counter variable scoping and set -e reliability - Add bw sync before fetching to ensure the CLI cache is fresh - Remove unused curl from dependency check - Add empty member name fallback to member ID - Add created/skipped summary count at end of run --- .../createCollectionsForAllMembers.sh | 77 ++++++++++--------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/Bash Scripts/createCollectionsForAllMembers.sh b/Bash Scripts/createCollectionsForAllMembers.sh index 1ff1fe8..9413837 100644 --- a/Bash Scripts/createCollectionsForAllMembers.sh +++ b/Bash Scripts/createCollectionsForAllMembers.sh @@ -56,8 +56,8 @@ decrypt_secret() { } # Check dependencies -for cmd in jq curl bw openssl; do - command -v "$cmd" >/dev/null 2>&1 || handle_error "$cmd is required but not installed." +for cmd in jq bw openssl; do + command -v "$cmd" > /dev/null 2>&1 || handle_error "$cmd is required but not installed." done # Ensure BITWARDEN_PASS is set @@ -68,14 +68,13 @@ fi # Decrypt Credentials password=$(decrypt_secret "$secure_password_file") -# Unlock Bitwarden CLI session -if ! bw status | grep -q "unlocked"; then - log "Unlocking Bitwarden CLI..." - session_key=$(printf "%s" "$password" | bw unlock --raw 2>/dev/null) || handle_error "Failed to unlock Bitwarden CLI" -else - session_key=$(bw unlock --raw) - log "Bitwarden CLI already unlocked." -fi +# Unlock Bitwarden CLI session (always supply password; bw unlock is idempotent) +log "Unlocking Bitwarden CLI..." +session_key=$(printf "%s" "$password" | bw unlock --raw 2>/dev/null) || handle_error "Failed to unlock Bitwarden CLI" + +# Sync to ensure CLI cache is up to date before fetching members/collections +log "Syncing Bitwarden vault..." +bw --session "$session_key" sync > /dev/null 2>&1 || handle_error "Failed to sync Bitwarden vault" # Check if Parent Collection Exists & Get ID log "Checking if parent collection '$parent_collection_name' exists..." @@ -101,45 +100,53 @@ if [[ -z "$org_members" ]]; then handle_error "No active organization members found." fi -# Process each member **sequentially** -echo "$org_members" | jq -c '.' | while read -r member; do +# Fetch ALL existing collections once before the loop +log "Fetching existing collections (one-time)..." +all_collections=$(bw --session "$session_key" list org-collections --organizationid "$organization_id") \ + || handle_error "Failed to fetch collections list" + +created=0 +skipped=0 + +# Process each member sequentially using process substitution so the loop runs +# in the current shell +while read -r member; do member_id=$(echo "$member" | jq -r '.id') member_name=$(echo "$member" | jq -r '.name // .email') - # If name is empty, use email prefix + # If name contains guest suffixes, strip them; fall back to email prefix member_name=$(echo "$member_name" | sed 's/#EXT#.*//; s/@.*//') # Trim long names (50 char limit) member_name=$(echo "$member_name" | cut -c1-50) + # Guard against a completely empty name after transformations + [[ -z "$member_name" ]] && member_name="$member_id" + log "Processing member: $member_name" collection_name="$parent_collection_name/$member_name" - # Check if collection already exists - existing_collection_id=$(bw --session "$session_key" list org-collections --organizationid "$organization_id" | jq -r ".[] | select(.externalId == \"$member_id\") | .id") + # In-memory check against the pre-fetched list + existing_collection_id=$(echo "$all_collections" | jq -r ".[] | select(.name == \"$collection_name\") | .id") if [[ -n "$existing_collection_id" ]]; then - log "Updating collection '$existing_collection_id' name to '$collection_name' while preserving all attributes." - existing_collection=$(bw --session "$session_key" get org-collection "$existing_collection_id" --organizationid "$organization_id") || handle_error "Failed to retrieve collection data for $collection_name" - - updated_collection=$(echo "$existing_collection" | jq -c --arg name "$collection_name" '.name = $name') - - echo "$updated_collection" | bw encode \ - | bw --session "$session_key" edit org-collection "$existing_collection_id" --organizationid "$organization_id" > /dev/null \ - || handle_error "Failed to update collection $collection_name" - log "Collection '$collection_name' updated." - else - log "Creating collection: $collection_name" - collection_id=$(bw --session "$session_key" get template org-collection \ - | jq -c --arg n "$collection_name" --arg c "$organization_id" --arg uid "$member_id" \ - '.name=$n | .organizationId=$c | .externalId=$uid | .users=[{"id":$uid, "readOnly":false, "hidePasswords":false, "manage":true}]' \ - | bw encode \ - | bw --session "$session_key" create org-collection --organizationid "$organization_id" \ - | jq -r '.id') || handle_error "Failed to create collection for $member_name" - log "Created Collection '$collection_name' for member '$member_name'." + log "Collection '$collection_name' already exists (ID: $existing_collection_id). Skipping member '$member_name'." + ((skipped++)) + continue fi -done + log "Creating collection: $collection_name" + collection_id=$(bw --session "$session_key" get template org-collection \ + | jq -c --arg n "$collection_name" --arg c "$organization_id" --arg uid "$member_id" \ + '.name=$n | .organizationId=$c | .externalId=$uid | .users=[{"id":$uid, "readOnly":false, "hidePasswords":false, "manage":true}]' \ + | bw encode \ + | bw --session "$session_key" create org-collection --organizationid "$organization_id" \ + | jq -r '.id') || handle_error "Failed to create collection for $member_name" + + log "Created Collection '$collection_name' for member '$member_name'." + ((created++)) + +done < <(echo "$org_members" | jq -c '.') -log "Collection creation and user assignment completed!" \ No newline at end of file +log "Collection processing complete. Created: $created | Skipped (already existed): $skipped." \ No newline at end of file