Add Google Workspace user account provisioning (#66)
* Add Google Workspace user account provisioning * Fix validation: remove stale skipGoogleUserProvisioning from a-akimov, add it for BobDickinson - a-akimov: had skipGoogleUserProvisioning but is not in a provisionUser role (DOCS_MAINTAINERS) - BobDickinson: is in REGISTRY_MAINTAINERS (provisionUser role) but missing Google user fields
This commit is contained in:
parent
d365a398cf
commit
2de77b8684
10 changed files with 502 additions and 23 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,6 +1,9 @@
|
|||
node_modules/
|
||||
bin/
|
||||
*.log
|
||||
.devenv/
|
||||
.devenv.flake.nix
|
||||
devenv.lock
|
||||
|
||||
# Pulumi
|
||||
.pulumi/
|
||||
|
|
|
|||
8
devenv.nix
Normal file
8
devenv.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
packages = [
|
||||
pkgs.nodejs_22
|
||||
pkgs.pulumi-bin
|
||||
pkgs.pulumiPackages.pulumi-nodejs
|
||||
];
|
||||
}
|
||||
246
package-lock.json
generated
246
package-lock.json
generated
|
|
@ -11,14 +11,29 @@
|
|||
"dependencies": {
|
||||
"@pulumi/github": "^6.7.3",
|
||||
"@pulumi/googleworkspace": "file:sdks/googleworkspace",
|
||||
"@pulumi/pulumi": "^3.197.0"
|
||||
"@pulumi/pulumi": "^3.218.0",
|
||||
"@pulumi/random": "^4.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.18.6",
|
||||
"prettier": "^3.7.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/grpc-js": {
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
|
||||
|
|
@ -89,6 +104,34 @@
|
|||
"integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@js-sdsl/ordered-map": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
|
||||
|
|
@ -349,7 +392,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
|
|
@ -676,6 +718,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@pulumi/random": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@pulumi/random/-/random-4.19.1.tgz",
|
||||
"integrity": "sha512-gDotQyxtl+pcJb4oaGfbi6PsK8OtzjBmF1D8PYLvr13kCup98KTIIv/8wF2xbgbFf7ljJn3EblADxZ0u8AC/Dg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@pulumi/pulumi": "^3.142.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sigstore/bundle": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz",
|
||||
|
|
@ -774,6 +825,34 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node14": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node16": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tufjs/canonical-json": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz",
|
||||
|
|
@ -879,7 +958,6 @@
|
|||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -896,6 +974,19 @@
|
|||
"acorn": "^8"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.5",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
|
||||
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
|
|
@ -905,6 +996,13 @@
|
|||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
|
|
@ -914,6 +1012,15 @@
|
|||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/async-mutex": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz",
|
||||
"integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bin-links": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bin-links/-/bin-links-6.0.0.tgz",
|
||||
|
|
@ -1150,6 +1257,13 @@
|
|||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
|
|
@ -1250,6 +1364,16 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"devOptional": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
|
|
@ -1744,6 +1868,13 @@
|
|||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"devOptional": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/make-fetch-happen": {
|
||||
"version": "15.0.3",
|
||||
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.3.tgz",
|
||||
|
|
@ -2288,7 +2419,6 @@
|
|||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
|
||||
"integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
|
|
@ -2767,6 +2897,56 @@
|
|||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@tsconfig/node16": "^1.0.2",
|
||||
"acorn": "^8.4.1",
|
||||
"acorn-walk": "^8.1.1",
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"v8-compile-cache-lib": "^3.0.1",
|
||||
"yn": "3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-node": "dist/bin.js",
|
||||
"ts-node-cwd": "dist/bin-cwd.js",
|
||||
"ts-node-esm": "dist/bin-esm.js",
|
||||
"ts-node-script": "dist/bin-script.js",
|
||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||
"ts-script": "dist/bin-script-deprecated.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/core": ">=1.2.50",
|
||||
"@swc/wasm": ">=1.2.50",
|
||||
"@types/node": "*",
|
||||
"typescript": ">=2.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@swc/wasm": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tuf-js": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz",
|
||||
|
|
@ -2787,7 +2967,6 @@
|
|||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
@ -2842,6 +3021,13 @@
|
|||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/validate-npm-package-license": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||
|
|
@ -3002,6 +3188,54 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"sdks/googleworkspace": {}
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"sdks/googleworkspace": {
|
||||
"name": "@pulumi/googleworkspace",
|
||||
"version": "0.7.0",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@pulumi/pulumi": "^3.142.0",
|
||||
"@types/node": "^18",
|
||||
"async-mutex": "^0.5.0",
|
||||
"typescript": "^4.3.5"
|
||||
}
|
||||
},
|
||||
"sdks/googleworkspace/node_modules/@types/node": {
|
||||
"version": "18.19.130",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
|
||||
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"sdks/googleworkspace/node_modules/typescript": {
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"sdks/googleworkspace/node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,13 @@
|
|||
"dependencies": {
|
||||
"@pulumi/github": "^6.11.0",
|
||||
"@pulumi/googleworkspace": "file:sdks/googleworkspace",
|
||||
"@pulumi/pulumi": "^3.218.0"
|
||||
"@pulumi/pulumi": "^3.218.0",
|
||||
"@pulumi/random": "^4.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.18.6",
|
||||
"prettier": "^3.7.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,51 @@ test('TYPESCRIPT_SDK_AUTH role exists (GitHub-only)', () => {
|
|||
return role !== undefined && role.github !== undefined && role.discord === undefined;
|
||||
});
|
||||
|
||||
// Test Google Workspace user provisioning
|
||||
test('Roles with provisionUser exist', () => {
|
||||
const provisionRoles = ROLES.filter((r) => r.google?.provisionUser);
|
||||
return provisionRoles.length > 0;
|
||||
});
|
||||
|
||||
test('Members with googleEmailPrefix have firstName and lastName', () =>
|
||||
MEMBERS.every((m) => {
|
||||
if (!m.googleEmailPrefix) return true;
|
||||
return !!m.firstName && !!m.lastName;
|
||||
}));
|
||||
|
||||
test('googleEmailPrefix values are unique', () => {
|
||||
const prefixes = MEMBERS.filter((m) => m.googleEmailPrefix).map((m) => m.googleEmailPrefix);
|
||||
return prefixes.length === new Set(prefixes).size;
|
||||
});
|
||||
|
||||
test('ProvisionUser members are explicit: fields present or skip flag set', () => {
|
||||
const provisionRoleIds = new Set(ROLES.filter((r) => r.google?.provisionUser).map((r) => r.id));
|
||||
return MEMBERS.every((member) => {
|
||||
const inProvisionRole = member.memberOf.some((id) => provisionRoleIds.has(id));
|
||||
if (!inProvisionRole) return !member.skipGoogleUserProvisioning;
|
||||
|
||||
const hasProvisioningFields = !!(
|
||||
member.firstName &&
|
||||
member.lastName &&
|
||||
member.googleEmailPrefix
|
||||
);
|
||||
return hasProvisioningFields !== !!member.skipGoogleUserProvisioning;
|
||||
});
|
||||
});
|
||||
|
||||
test('Some members in provisionUser roles have Google user fields', () => {
|
||||
const membersInProvisionRoles = MEMBERS.filter((m) =>
|
||||
m.memberOf.some((id) => {
|
||||
const role = roleLookup.get(id);
|
||||
return role?.google?.provisionUser === true;
|
||||
})
|
||||
);
|
||||
const provisioned = membersInProvisionRoles.filter(
|
||||
(m) => m.firstName && m.lastName && m.googleEmailPrefix
|
||||
);
|
||||
return membersInProvisionRoles.length > 0 && provisioned.length > 0;
|
||||
});
|
||||
|
||||
// Summary
|
||||
console.log(`\n${passed} passed, ${failed} failed`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
|
|
|
|||
|
|
@ -98,6 +98,72 @@ console.log('Validating member ordering in users.ts...');
|
|||
}
|
||||
}
|
||||
|
||||
// Validate Google Workspace user provisioning fields
|
||||
console.log('Validating Google Workspace user provisioning fields...');
|
||||
{
|
||||
const googleEmailPrefixes = new Map<string, string>();
|
||||
|
||||
for (const member of MEMBERS) {
|
||||
const memberId = member.github || member.email || 'unknown';
|
||||
|
||||
// Members with googleEmailPrefix must also have firstName and lastName
|
||||
if (member.googleEmailPrefix) {
|
||||
if (!member.firstName) {
|
||||
console.error(`ERROR: Member "${memberId}" has googleEmailPrefix but is missing firstName`);
|
||||
hasErrors = true;
|
||||
}
|
||||
if (!member.lastName) {
|
||||
console.error(`ERROR: Member "${memberId}" has googleEmailPrefix but is missing lastName`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
// Check uniqueness of googleEmailPrefix
|
||||
const existing = googleEmailPrefixes.get(member.googleEmailPrefix);
|
||||
if (existing) {
|
||||
console.error(
|
||||
`ERROR: googleEmailPrefix "${member.googleEmailPrefix}" is used by both "${existing}" and "${memberId}"`
|
||||
);
|
||||
hasErrors = true;
|
||||
} else {
|
||||
googleEmailPrefixes.set(member.googleEmailPrefix, memberId);
|
||||
}
|
||||
}
|
||||
|
||||
// Members in provisionUser roles without all three fields won't get a GWS account
|
||||
const inProvisionUserRole = member.memberOf.some((roleId: RoleId) => {
|
||||
const role = roleLookup.get(roleId);
|
||||
return role?.google?.provisionUser === true;
|
||||
});
|
||||
|
||||
const hasProvisioningFields = !!(
|
||||
member.googleEmailPrefix &&
|
||||
member.firstName &&
|
||||
member.lastName
|
||||
);
|
||||
|
||||
if (member.skipGoogleUserProvisioning && !inProvisionUserRole) {
|
||||
console.error(
|
||||
`ERROR: Member "${memberId}" has skipGoogleUserProvisioning=true but is not in a provisionUser role`
|
||||
);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (inProvisionUserRole && hasProvisioningFields && member.skipGoogleUserProvisioning) {
|
||||
console.error(
|
||||
`ERROR: Member "${memberId}" has provisioning fields and skipGoogleUserProvisioning=true; pick one`
|
||||
);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (inProvisionUserRole && !hasProvisioningFields && !member.skipGoogleUserProvisioning) {
|
||||
console.error(
|
||||
`ERROR: Member "${memberId}" is in a provisionUser role but is missing Google user fields. Add fields or set skipGoogleUserProvisioning: true`
|
||||
);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate parent role references in roles.ts
|
||||
console.log('Validating parent role references in roles.ts...');
|
||||
for (const role of ROLES) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ export interface GoogleConfig {
|
|||
group: string;
|
||||
/** If true, accepts emails from anyone including external users */
|
||||
isEmailGroup?: boolean;
|
||||
/** If true, members of this role get a Google Workspace user account */
|
||||
provisionUser?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -77,13 +79,14 @@ export const ROLES: readonly Role[] = [
|
|||
description: 'Lead core maintainers',
|
||||
github: { team: 'lead-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE },
|
||||
discord: { role: 'lead maintainers (synced)' },
|
||||
// Discord only for now - could add GitHub if needed
|
||||
google: { group: 'lead-maintainers', provisionUser: true },
|
||||
},
|
||||
{
|
||||
id: ROLE_IDS.CORE_MAINTAINERS,
|
||||
description: 'Core maintainers',
|
||||
github: { team: 'core-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE },
|
||||
discord: { role: 'core maintainers (synced)' },
|
||||
google: { group: 'core-maintainers', provisionUser: true },
|
||||
},
|
||||
{
|
||||
id: ROLE_IDS.MODERATORS,
|
||||
|
|
@ -130,7 +133,7 @@ export const ROLES: readonly Role[] = [
|
|||
description: 'Official registry builders and maintainers',
|
||||
github: { team: 'registry-wg', parent: ROLE_IDS.WORKING_GROUPS },
|
||||
discord: { role: 'registry maintainers (synced)' },
|
||||
google: { group: 'registry-wg' },
|
||||
google: { group: 'registry-wg', provisionUser: true },
|
||||
},
|
||||
{
|
||||
id: ROLE_IDS.USE_MCP_MAINTAINERS,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export const MEMBERS: readonly Member[] = [
|
|||
{
|
||||
github: '000-000-000-000-000',
|
||||
discord: '1360717264051241071',
|
||||
skipGoogleUserProvisioning: true,
|
||||
memberOf: [ROLE_IDS.CORE_MAINTAINERS],
|
||||
},
|
||||
{
|
||||
|
|
@ -60,6 +61,7 @@ export const MEMBERS: readonly Member[] = [
|
|||
github: 'BobDickinson',
|
||||
email: 'bob.dickinson@gmail.com',
|
||||
discord: '1175893001202045139',
|
||||
skipGoogleUserProvisioning: true,
|
||||
memberOf: [
|
||||
ROLE_IDS.MAINTAINERS,
|
||||
ROLE_IDS.INSPECTOR_MAINTAINERS,
|
||||
|
|
@ -75,6 +77,7 @@ export const MEMBERS: readonly Member[] = [
|
|||
github: 'caitiem20',
|
||||
email: 'caitie.mccaffrey@microsoft.com',
|
||||
discord: '1425586366288494722',
|
||||
skipGoogleUserProvisioning: true,
|
||||
memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.TRANSPORT_WG],
|
||||
},
|
||||
{
|
||||
|
|
@ -147,10 +150,15 @@ export const MEMBERS: readonly Member[] = [
|
|||
github: 'domdomegg',
|
||||
email: 'adam@modelcontextprotocol.io',
|
||||
discord: '102128241715716096',
|
||||
firstName: 'Adam',
|
||||
lastName: 'Jones',
|
||||
googleEmailPrefix: 'adam',
|
||||
existingGWSUser: true,
|
||||
memberOf: [ROLE_IDS.MCPB_MAINTAINERS, ROLE_IDS.REGISTRY_MAINTAINERS],
|
||||
},
|
||||
{
|
||||
github: 'dsp',
|
||||
skipGoogleUserProvisioning: true,
|
||||
memberOf: [
|
||||
ROLE_IDS.AUTH_MAINTAINERS,
|
||||
ROLE_IDS.LEAD_MAINTAINERS,
|
||||
|
|
@ -170,19 +178,16 @@ export const MEMBERS: readonly Member[] = [
|
|||
github: 'dsp-ant',
|
||||
email: 'david@modelcontextprotocol.io',
|
||||
discord: '166107790262272000',
|
||||
firstName: 'David',
|
||||
lastName: 'Soria Parra',
|
||||
googleEmailPrefix: 'david',
|
||||
existingGWSUser: true,
|
||||
memberOf: [
|
||||
ROLE_IDS.AUTH_MAINTAINERS,
|
||||
ROLE_IDS.LEAD_MAINTAINERS,
|
||||
ROLE_IDS.CORE_MAINTAINERS,
|
||||
ROLE_IDS.DOCS_MAINTAINERS,
|
||||
ROLE_IDS.GO_SDK,
|
||||
ROLE_IDS.FINANCIAL_SERVICES_IG,
|
||||
ROLE_IDS.MODERATORS,
|
||||
ROLE_IDS.PHP_SDK,
|
||||
ROLE_IDS.PYTHON_SDK,
|
||||
ROLE_IDS.SECURITY_WG,
|
||||
ROLE_IDS.TRANSPORT_WG,
|
||||
ROLE_IDS.TYPESCRIPT_SDK,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -298,6 +303,10 @@ export const MEMBERS: readonly Member[] = [
|
|||
{
|
||||
github: 'jspahrsummers',
|
||||
email: 'justin@modelcontextprotocol.io',
|
||||
firstName: 'Justin',
|
||||
lastName: 'Spahr-Summers',
|
||||
googleEmailPrefix: 'justin',
|
||||
existingGWSUser: true,
|
||||
memberOf: [ROLE_IDS.LEAD_MAINTAINERS, ROLE_IDS.CORE_MAINTAINERS],
|
||||
},
|
||||
{
|
||||
|
|
@ -335,6 +344,7 @@ export const MEMBERS: readonly Member[] = [
|
|||
{
|
||||
github: 'kurtisvg',
|
||||
discord: '1158458388917780590',
|
||||
skipGoogleUserProvisioning: true,
|
||||
memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.TRANSPORT_WG],
|
||||
},
|
||||
{
|
||||
|
|
@ -345,6 +355,9 @@ export const MEMBERS: readonly Member[] = [
|
|||
{
|
||||
github: 'localden',
|
||||
discord: '1351224014143754260',
|
||||
firstName: 'Den',
|
||||
lastName: 'Delimarsky',
|
||||
googleEmailPrefix: 'den',
|
||||
memberOf: [
|
||||
ROLE_IDS.AUTH_MAINTAINERS,
|
||||
ROLE_IDS.CORE_MAINTAINERS,
|
||||
|
|
@ -422,6 +435,7 @@ export const MEMBERS: readonly Member[] = [
|
|||
{
|
||||
github: 'nickcoai',
|
||||
discord: '1153783469860732968',
|
||||
skipGoogleUserProvisioning: true,
|
||||
memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.SERVER_IDENTITY_WG],
|
||||
},
|
||||
{
|
||||
|
|
@ -469,6 +483,10 @@ export const MEMBERS: readonly Member[] = [
|
|||
{
|
||||
github: 'pcarleton',
|
||||
discord: '1354465170969067852',
|
||||
firstName: 'Paul',
|
||||
lastName: 'Carleton',
|
||||
googleEmailPrefix: 'paul',
|
||||
existingGWSUser: true,
|
||||
memberOf: [
|
||||
ROLE_IDS.CORE_MAINTAINERS,
|
||||
ROLE_IDS.PYTHON_SDK,
|
||||
|
|
@ -496,6 +514,9 @@ export const MEMBERS: readonly Member[] = [
|
|||
{
|
||||
github: 'pja-ant',
|
||||
discord: '328628782497923072',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Alexander',
|
||||
googleEmailPrefix: 'pja',
|
||||
memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.MAINTAINERS, ROLE_IDS.TRANSPORT_WG],
|
||||
},
|
||||
{
|
||||
|
|
@ -505,12 +526,17 @@ export const MEMBERS: readonly Member[] = [
|
|||
{
|
||||
github: 'pwwpche',
|
||||
discord: '1226238847013228604',
|
||||
skipGoogleUserProvisioning: true,
|
||||
memberOf: [ROLE_IDS.CORE_MAINTAINERS],
|
||||
},
|
||||
{
|
||||
github: 'rdimitrov',
|
||||
email: 'radoslav@modelcontextprotocol.io',
|
||||
discord: '1088231882979815424',
|
||||
firstName: 'Radoslav',
|
||||
lastName: 'Dimitrov',
|
||||
googleEmailPrefix: 'radoslav',
|
||||
existingGWSUser: true,
|
||||
memberOf: [ROLE_IDS.MAINTAINERS, ROLE_IDS.REGISTRY_MAINTAINERS, ROLE_IDS.SKILLS_OVER_MCP_IG],
|
||||
},
|
||||
{
|
||||
|
|
@ -546,6 +572,10 @@ export const MEMBERS: readonly Member[] = [
|
|||
github: 'tadasant',
|
||||
email: 'tadas@modelcontextprotocol.io',
|
||||
discord: '400092503677599754',
|
||||
firstName: 'Tadas',
|
||||
lastName: 'Antanavicius',
|
||||
googleEmailPrefix: 'tadas',
|
||||
existingGWSUser: true,
|
||||
memberOf: [
|
||||
ROLE_IDS.COMMUNITY_MANAGERS,
|
||||
ROLE_IDS.MODERATORS,
|
||||
|
|
@ -564,7 +594,11 @@ export const MEMBERS: readonly Member[] = [
|
|||
github: 'toby',
|
||||
email: 'toby@modelcontextprotocol.io',
|
||||
discord: '560155411777323048',
|
||||
memberOf: [ROLE_IDS.REGISTRY_MAINTAINERS],
|
||||
firstName: 'Toby',
|
||||
lastName: 'Padilla',
|
||||
googleEmailPrefix: 'toby',
|
||||
existingGWSUser: true,
|
||||
memberOf: [ROLE_IDS.MAINTAINERS, ROLE_IDS.REGISTRY_MAINTAINERS],
|
||||
},
|
||||
{
|
||||
github: 'topherbullock',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,16 @@ export interface Member {
|
|||
discord?: string;
|
||||
/** Roles this member belongs to */
|
||||
memberOf: readonly RoleId[];
|
||||
/** First name (required for Google Workspace user provisioning) */
|
||||
firstName?: string;
|
||||
/** Last name (required for Google Workspace user provisioning) */
|
||||
lastName?: string;
|
||||
/** Google Workspace email prefix (e.g., 'david' -> david@modelcontextprotocol.io) */
|
||||
googleEmailPrefix?: string;
|
||||
/** If true, this user already exists in Google Workspace and should be imported into Pulumi state */
|
||||
existingGWSUser?: boolean;
|
||||
/** Explicitly skip automatic GWS user provisioning for provisionUser roles */
|
||||
skipGoogleUserProvisioning?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import * as crypto from 'crypto';
|
||||
import * as pulumi from '@pulumi/pulumi';
|
||||
import * as gworkspace from '@pulumi/googleworkspace';
|
||||
import * as random from '@pulumi/random';
|
||||
import { ROLES, type Role, buildRoleLookup } from './config/roles';
|
||||
import { MEMBERS } from './config/users';
|
||||
import type { RoleId } from './config/roleIds';
|
||||
|
|
@ -45,20 +48,91 @@ ROLES.forEach((role: Role) => {
|
|||
});
|
||||
});
|
||||
|
||||
// Provision Google Workspace user accounts for members in roles with provisionUser
|
||||
const provisionedUsersByEmail: Record<string, gworkspace.User> = {};
|
||||
const newUserPasswords: Record<string, pulumi.Output<string>> = {};
|
||||
|
||||
MEMBERS.forEach((member) => {
|
||||
if (
|
||||
!member.firstName ||
|
||||
!member.lastName ||
|
||||
!member.googleEmailPrefix ||
|
||||
member.skipGoogleUserProvisioning
|
||||
)
|
||||
return;
|
||||
|
||||
const needsUser = member.memberOf.some((roleId: RoleId) => {
|
||||
const role = roleLookup.get(roleId);
|
||||
return role?.google?.provisionUser === true;
|
||||
});
|
||||
if (!needsUser) return;
|
||||
|
||||
const primaryEmail = `${member.googleEmailPrefix}@modelcontextprotocol.io`;
|
||||
|
||||
if (member.existingGWSUser) {
|
||||
// Import existing user into Pulumi state without recreating
|
||||
const user = new gworkspace.User(
|
||||
`gws-user-${member.googleEmailPrefix}`,
|
||||
{
|
||||
primaryEmail,
|
||||
name: { familyName: member.lastName!, givenName: member.firstName! },
|
||||
orgUnitPath: '/Model Context Protocol',
|
||||
},
|
||||
{ import: primaryEmail }
|
||||
);
|
||||
provisionedUsersByEmail[primaryEmail] = user;
|
||||
} else {
|
||||
// Create new user with random password
|
||||
const password = new random.RandomPassword(`gws-pwd-${member.googleEmailPrefix}`, {
|
||||
length: 24,
|
||||
special: true,
|
||||
});
|
||||
const hashedPassword = password.result.apply((plaintext: string) =>
|
||||
crypto.createHash('sha1').update(plaintext).digest('hex')
|
||||
);
|
||||
|
||||
const user = new gworkspace.User(`gws-user-${member.googleEmailPrefix}`, {
|
||||
primaryEmail,
|
||||
name: { familyName: member.lastName!, givenName: member.firstName! },
|
||||
password: hashedPassword,
|
||||
hashFunction: 'SHA-1',
|
||||
changePasswordAtNextLogin: true,
|
||||
orgUnitPath: '/Model Context Protocol',
|
||||
});
|
||||
provisionedUsersByEmail[primaryEmail] = user;
|
||||
|
||||
// Track password for export so an admin can retrieve it
|
||||
newUserPasswords[primaryEmail] = password.result;
|
||||
}
|
||||
});
|
||||
|
||||
// Create group memberships for users
|
||||
MEMBERS.forEach((member) => {
|
||||
if (!member.email) return;
|
||||
// Prefer the provisioned GWS email over the personal email for group memberships
|
||||
const gwsEmail = member.googleEmailPrefix
|
||||
? `${member.googleEmailPrefix}@modelcontextprotocol.io`
|
||||
: undefined;
|
||||
const memberEmail = gwsEmail || member.email;
|
||||
if (!memberEmail) return;
|
||||
const provisionedUser = gwsEmail ? provisionedUsersByEmail[gwsEmail] : undefined;
|
||||
|
||||
member.memberOf.forEach((roleId: RoleId) => {
|
||||
const role = roleLookup.get(roleId);
|
||||
if (!role?.google) return; // Role doesn't have Google config
|
||||
|
||||
new gworkspace.GroupMember(`${member.email}-${role.google.group}`, {
|
||||
groupId: groups[role.google.group].id,
|
||||
email: member.email!,
|
||||
role: 'MEMBER',
|
||||
});
|
||||
new gworkspace.GroupMember(
|
||||
`${memberEmail}-${role.google.group}`,
|
||||
{
|
||||
groupId: groups[role.google.group].id,
|
||||
email: memberEmail,
|
||||
role: 'MEMBER',
|
||||
},
|
||||
provisionedUser ? { dependsOn: [provisionedUser] } : undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
export { groups as googleGroups };
|
||||
// Export initial passwords as secrets so an admin can retrieve them with:
|
||||
// pulumi stack output --show-secrets newGWSUserPasswords
|
||||
export const newGWSUserPasswords = pulumi.secret(newUserPasswords);
|
||||
|
|
|
|||
Loading…
Reference in a new issue