Skip to content

Commit 8e6139b

Browse files
Copilothakalb
andcommitted
feat: add workflow integration for multi-tenant deployment from Infisical
Co-authored-by: hakalb <16068945+hakalb@users.noreply.github.com>
1 parent 3eeffa3 commit 8e6139b

3 files changed

Lines changed: 104 additions & 4 deletions

File tree

.github/workflows/fly-deployment.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,20 @@ jobs:
9898
main-branch-name: main
9999
set-environment-variables-for-job: true
100100

101-
# TODO: Fetch all tenants and make a deployment for each with their `TENANT_ID`
102-
# TODO: Alternatively, create a new input with multi-tenant values
101+
- name: Fetch tenants from Infisical
102+
id: fetch-tenants
103+
run: |
104+
echo "Fetching tenants from Infisical..."
105+
TENANTS=$(tsx scripts/fetch-tenants.ts)
106+
echo "tenants<<EOF" >> $GITHUB_OUTPUT
107+
echo "$TENANTS" >> $GITHUB_OUTPUT
108+
echo "EOF" >> $GITHUB_OUTPUT
109+
env:
110+
INFISICAL_CLIENT_ID: ${{ secrets.INFISICAL_READ_CLIENT_ID }}
111+
INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_READ_CLIENT_SECRET }}
112+
INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }}
113+
DEPLOY_ENV: ${{ needs.deploy-env.outputs.environment }}
114+
103115
- name: Run Deployment to Fly
104116
id: deployment
105117
uses: ./packages/nx-fly-deployment-action
@@ -108,8 +120,7 @@ jobs:
108120
fly-org: ${{ vars.FLY_ORG }}
109121
fly-region: ${{ vars.FLY_REGION }}
110122
token: ${{ steps.generate-token.outputs.token }}
111-
env: |
112-
TENANT_ID=demo
123+
tenants: ${{ steps.fetch-tenants.outputs.tenants }}
113124
secrets: |
114125
INFISICAL_CLIENT_ID=${{ secrets.INFISICAL_READ_CLIENT_ID }}
115126
INFISICAL_CLIENT_SECRET=${{ secrets.INFISICAL_READ_CLIENT_SECRET }}

packages/nx-fly-deployment-action/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,25 @@ See [action.yaml](action.yml) for descriptions of the inputs.
9292

9393
### Additional input details
9494

95+
`tenants`
96+
97+
When deploying multi-tenant applications, you can provide a list of tenant IDs. Each affected project will be deployed once per tenant with:
98+
- A unique app name: `<base-app-name>-<tenant-id>` (e.g., `cdwr-web-demo`, `cdwr-web-customer1`)
99+
- The `TENANT_ID` environment variable set to the tenant ID
100+
101+
Provide the tenant IDs as multiline values.
102+
103+
```yaml
104+
- uses: ./packages/nx-fly-deployment-action
105+
with:
106+
tenants: |
107+
demo
108+
customer1
109+
customer2
110+
```
111+
112+
If no tenants are provided, the action behaves as before (single deployment per project).
113+
95114
`postgres-preview`
96115

97116
When a Fly Postgres cluser has been created, you can attach the application to a postgres database automatically on deployment to the `preview` environment.

scripts/fetch-tenants.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env tsx
2+
3+
/**
4+
* Fetch tenant IDs from Infisical
5+
*
6+
* This script connects to Infisical and retrieves all tenant IDs
7+
* from the /web/tenants path structure.
8+
*
9+
* Outputs tenant IDs as newline-separated values to stdout.
10+
* Logs and errors are written to stderr.
11+
*/
12+
13+
// @ts-ignore - Module resolution handled by tsx at runtime
14+
import { withInfisical } from '@codeware/shared/feature/infisical';
15+
16+
async function main() {
17+
try {
18+
console.error('[fetch-tenants] Connecting to Infisical...');
19+
20+
// Fetch all secrets from /web/tenants/* paths recursively
21+
const secrets = await withInfisical({
22+
environment: process.env.DEPLOY_ENV || 'production',
23+
filter: { path: '/web/tenants', recurse: true },
24+
silent: false,
25+
site: 'eu'
26+
});
27+
28+
if (!secrets || secrets.length === 0) {
29+
console.error('[fetch-tenants] No tenants found in Infisical');
30+
process.exit(0);
31+
}
32+
33+
// Extract unique tenant IDs from secret paths
34+
// Path format: /web/tenants/<tenant-id>
35+
const tenantIds = new Set<string>();
36+
37+
for (const secret of secrets) {
38+
const { secretPath } = secret;
39+
if (!secretPath) continue;
40+
41+
const match = secretPath.match(/^\/web\/tenants\/([^/]+)$/);
42+
if (match && match[1]) {
43+
tenantIds.add(match[1]);
44+
}
45+
}
46+
47+
const tenants = Array.from(tenantIds).sort();
48+
49+
console.error(`[fetch-tenants] Found ${tenants.length} tenant(s): ${tenants.join(', ')}`);
50+
51+
// Output tenant IDs to stdout (one per line)
52+
for (const tenantId of tenants) {
53+
console.log(tenantId);
54+
}
55+
} catch (error) {
56+
if (error instanceof Error) {
57+
console.error('[fetch-tenants] Error:', error.message);
58+
// @ts-ignore - Error.cause is available in Node 16+
59+
if (error.cause) {
60+
// @ts-ignore
61+
console.error('[fetch-tenants] Cause:', error.cause);
62+
}
63+
} else {
64+
console.error('[fetch-tenants] Unknown error:', error);
65+
}
66+
process.exit(1);
67+
}
68+
}
69+
70+
main();

0 commit comments

Comments
 (0)