Final production deployment: activated live sync and updated documentation

This commit is contained in:
Moritz Graf
2026-04-25 14:31:37 +02:00
parent d4b4e19223
commit 82acc4e7d5
3 changed files with 65 additions and 54 deletions
+62 -52
View File
@@ -13,9 +13,9 @@ const CONFIG = {
SHEET_NAME: "Form Responses 1",
// Column indices (1-indexed).
// Example: If Source is Column B, index is 2. If Dest is Column C, index is 3.
COL_SOURCE_ADDRESS: 2,
COL_DESTINATION_ADDRESS: 6,
// Example: If Forward Address is Column D, index is 4. If Forward To is Column F, index is 6.
COL_FORWARD_ADDRESS: 6,
COL_FORWARD_TO_ADDRESS: 4,
// The admin email that should receive the execution reports
ADMIN_EMAIL: "moritz@haumdaucher.de",
@@ -89,13 +89,13 @@ function syncForwardings() {
const changelog = [];
// 1. Process Creations and Updates
for (const sourceEmail in desiredState) {
const desiredDestinations = desiredState[sourceEmail];
for (const forwardAddress in desiredState) {
const forwardToAddresses = desiredState[forwardAddress];
if (!currentState[sourceEmail]) {
createForwardingGroup(sourceEmail, desiredDestinations, changelog);
if (!currentState[forwardAddress]) {
createForwardingGroup(forwardAddress, forwardToAddresses, changelog);
} else {
updateForwardingGroup(sourceEmail, currentState[sourceEmail], desiredDestinations, changelog);
updateForwardingGroup(forwardAddress, currentState[forwardAddress], forwardToAddresses, changelog);
}
}
@@ -144,48 +144,54 @@ function readDesiredStateFromSheet() {
// Start at i=1 to skip the header row
for (let i = 1; i < data.length; i++) {
const row = data[i];
let source = (row[CONFIG.COL_SOURCE_ADDRESS - 1] || "").toString().trim().toLowerCase();
let dest = (row[CONFIG.COL_DESTINATION_ADDRESS - 1] || "").toString().trim().toLowerCase();
let forwardAddress = (row[CONFIG.COL_FORWARD_ADDRESS - 1] || "").toString().trim().toLowerCase();
let forwardToAddress = (row[CONFIG.COL_FORWARD_TO_ADDRESS - 1] || "").toString().trim().toLowerCase();
// Ignore completely empty rows
if (!source && !dest) {
if (!forwardAddress && !forwardToAddress) {
continue;
}
// 1. Auto-append domain to source if they just typed a name (e.g. "frederic")
if (source && !source.includes("@")) {
source += `@${CONFIG.WORKSPACE_DOMAIN}`;
// 1. Auto-append domain to forwardAddress if they just typed a name (e.g. "frederic")
if (forwardAddress && !forwardAddress.includes("@")) {
forwardAddress += `@${CONFIG.WORKSPACE_DOMAIN}`;
}
// 2. Validate source domain
if (source && !source.endsWith(`@${CONFIG.WORKSPACE_DOMAIN}`)) {
console.warn(`Row ${i + 1}: Skipped. Source address must belong to @${CONFIG.WORKSPACE_DOMAIN} domain. Found: '${source}'`);
// 2. Validate forwardAddress domain (MUST be internal)
if (forwardAddress && !forwardAddress.endsWith(`@${CONFIG.WORKSPACE_DOMAIN}`)) {
console.warn(`Row ${i + 1}: Skipped. Forward address must belong to @${CONFIG.WORKSPACE_DOMAIN} domain. Found: '${forwardAddress}'`);
continue;
}
// 3. Validate source is a valid email
if (source && !emailRegex.test(source)) {
console.warn(`Row ${i + 1}: Skipped. Invalid source email format: '${source}'`);
// 3. Validate forwardAddress is a valid email
if (forwardAddress && !emailRegex.test(forwardAddress)) {
console.warn(`Row ${i + 1}: Skipped. Invalid forward address format: '${forwardAddress}'`);
continue;
}
// 4. Validate destination is a valid email
if (dest && !emailRegex.test(dest)) {
console.warn(`Row ${i + 1}: Skipped. Invalid destination email format: '${dest}'`);
// 4. Validate forwardToAddress domain (MUST NOT be internal)
if (forwardToAddress && forwardToAddress.endsWith(`@${CONFIG.WORKSPACE_DOMAIN}`)) {
console.warn(`Row ${i + 1}: Skipped. Destination address cannot be an internal @${CONFIG.WORKSPACE_DOMAIN} address. Found: '${forwardToAddress}'`);
continue;
}
if (source && dest) {
console.log(`Parsed Row ${i + 1}: Valid mapping [${source} -> ${dest}]`);
if (!desiredState[source]) {
desiredState[source] = [];
// 5. Validate forwardToAddress is a valid email
if (forwardToAddress && !emailRegex.test(forwardToAddress)) {
console.warn(`Row ${i + 1}: Skipped. Invalid destination email format: '${forwardToAddress}'`);
continue;
}
if (forwardAddress && forwardToAddress) {
console.log(`Parsed Row ${i + 1}: Valid mapping [${forwardAddress} -> ${forwardToAddress}]`);
if (!desiredState[forwardAddress]) {
desiredState[forwardAddress] = [];
}
// Ensure we don't add duplicate destinations for the same source
if (desiredState[source].indexOf(dest) === -1) {
desiredState[source].push(dest);
if (desiredState[forwardAddress].indexOf(forwardToAddress) === -1) {
desiredState[forwardAddress].push(forwardToAddress);
}
} else {
console.warn(`Row ${i + 1}: Skipped. Missing either source or destination (Source: '${source}', Dest: '${dest}')`);
console.warn(`Row ${i + 1}: Skipped. Missing either forward address or destination (Forward: '${forwardAddress}', To: '${forwardToAddress}')`);
}
}
@@ -254,49 +260,53 @@ function logAction(changelog, message) {
/**
* Creates a new Workspace Group to act as a forwarder
*/
function createForwardingGroup(sourceEmail, destinations, changelog) {
function createForwardingGroup(forwardAddress, forwardToAddresses, changelog) {
if (CONFIG.DRY_RUN) {
logAction(changelog, `[DRY RUN - CREATED] Group: ${sourceEmail}`);
for (const dest of destinations) {
logAction(changelog, ` + [DRY RUN] Added member: ${dest}`);
logAction(changelog, `[DRY RUN - CREATED] Group: ${forwardAddress}`);
for (const forwardTo of forwardToAddresses) {
logAction(changelog, ` + [DRY RUN] Added member: ${forwardTo}`);
}
return;
}
try {
const newGroup = {
email: sourceEmail,
name: `Auto-Forwarder: ${sourceEmail}`,
email: forwardAddress,
name: `Auto-Forwarder: ${forwardAddress}`,
description: CONFIG.GROUP_DESCRIPTION_TAG
};
AdminDirectory.Groups.insert(newGroup);
logAction(changelog, `[CREATED] Group: ${sourceEmail}`);
logAction(changelog, `[CREATED] Group: ${forwardAddress}`);
for (const dest of destinations) {
AdminDirectory.Members.insert({ email: dest, role: 'MEMBER' }, sourceEmail);
logAction(changelog, ` + Added member: ${dest}`);
// Propagation delay: Google Workspace APIs are eventually consistent.
// We must wait a few seconds after creation before we can add members to the new group.
Utilities.sleep(3000);
for (const forwardTo of forwardToAddresses) {
AdminDirectory.Members.insert({ email: forwardTo, role: 'MEMBER' }, forwardAddress);
logAction(changelog, ` + Added member: ${forwardTo}`);
}
} catch (e) {
logAction(changelog, `[ERROR] Failed to create group ${sourceEmail}: ${e.message}`);
logAction(changelog, `[ERROR] Failed to create group ${forwardAddress}: ${e.message}`);
}
}
/**
* Reconciles members of an existing forwarding group
*/
function updateForwardingGroup(sourceEmail, currentMembers, desiredMembers, changelog) {
function updateForwardingGroup(forwardAddress, currentMembers, desiredMembers, changelog) {
// 1. Add missing members
for (const dest of desiredMembers) {
if (currentMembers.indexOf(dest) === -1) {
for (const forwardTo of desiredMembers) {
if (currentMembers.indexOf(forwardTo) === -1) {
if (CONFIG.DRY_RUN) {
logAction(changelog, `[DRY RUN - UPDATED] Group ${sourceEmail}: Added member ${dest}`);
logAction(changelog, `[DRY RUN - UPDATED] Group ${forwardAddress}: Added member ${forwardTo}`);
} else {
try {
AdminDirectory.Members.insert({ email: dest, role: 'MEMBER' }, sourceEmail);
logAction(changelog, `[UPDATED] Group ${sourceEmail}: Added member ${dest}`);
AdminDirectory.Members.insert({ email: forwardTo, role: 'MEMBER' }, forwardAddress);
logAction(changelog, `[UPDATED] Group ${forwardAddress}: Added member ${forwardTo}`);
} catch (e) {
logAction(changelog, `[ERROR] Failed to add member ${dest} to ${sourceEmail}: ${e.message}`);
logAction(changelog, `[ERROR] Failed to add member ${forwardTo} to ${forwardAddress}: ${e.message}`);
}
}
}
@@ -306,13 +316,13 @@ function updateForwardingGroup(sourceEmail, currentMembers, desiredMembers, chan
for (const current of currentMembers) {
if (desiredMembers.indexOf(current) === -1) {
if (CONFIG.DRY_RUN) {
logAction(changelog, `[DRY RUN - UPDATED] Group ${sourceEmail}: Removed member ${current}`);
logAction(changelog, `[DRY RUN - UPDATED] Group ${forwardAddress}: Removed member ${current}`);
} else {
try {
AdminDirectory.Members.remove(sourceEmail, current);
logAction(changelog, `[UPDATED] Group ${sourceEmail}: Removed member ${current}`);
AdminDirectory.Members.remove(forwardAddress, current);
logAction(changelog, `[UPDATED] Group ${forwardAddress}: Removed member ${current}`);
} catch (e) {
logAction(changelog, `[ERROR] Failed to remove member ${current} from ${sourceEmail}: ${e.message}`);
logAction(changelog, `[ERROR] Failed to remove member ${current} from ${forwardAddress}: ${e.message}`);
}
}
}