Skip to content

Commit 3eeffa3

Browse files
Copilothakalb
andcommitted
feat: add multi-tenant deployment support to nx-fly-deployment-action
Co-authored-by: hakalb <16068945+hakalb@users.noreply.github.com>
1 parent 1d6fc0a commit 3eeffa3

12 files changed

Lines changed: 198 additions & 48 deletions

File tree

packages/nx-fly-deployment-action/action.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ inputs:
3535
The token to use for repository authentication.
3636
For example `GITHUB_TOKEN`.
3737
required: true
38+
tenants:
39+
description: >
40+
A multiline string of tenant IDs to deploy for multi-tenant applications.
41+
Each tenant ID should be on a new line.
42+
If provided, each affected project will be deployed once per tenant with TENANT_ID env var set.
43+
Defaults to an empty string (no multi-tenant deployment).
3844
opt-out-depot-builder:
3945
description: >
4046
Whether to opt out of the default depot builder.

packages/nx-fly-deployment-action/src/lib/__snapshots__/schemas.spec.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ exports[`Fly Deployment Action schema validation > validates all schemas against
1515
"SECRET_KEY1=secret-value1",
1616
"SECRET_KEY2=secret-value2",
1717
],
18+
"tenants": [],
1819
"token": "github-token",
1920
}
2021
`;
@@ -60,6 +61,7 @@ exports[`Fly Deployment Action schema validation > validates all schemas against
6061
"SECRET_KEY1": "secret-value1",
6162
"SECRET_KEY2": "secret-value2",
6263
},
64+
"tenants": [],
6365
"token": "github-token",
6466
}
6567
`;

packages/nx-fly-deployment-action/src/lib/fly-deployment.spec.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ describe('flyDeployment', () => {
336336
mainBranch: '',
337337
optOutDepotBuilder: false,
338338
secrets: [],
339+
tenants: [],
339340
token: 'token'
340341
},
341342
...configOverride
@@ -384,6 +385,7 @@ describe('flyDeployment', () => {
384385
mainBranch: '',
385386
optOutDepotBuilder: false,
386387
secrets: [],
388+
tenants: [],
387389
token: 'token'
388390
} satisfies ActionInputs);
389391

@@ -1049,6 +1051,102 @@ describe('flyDeployment', () => {
10491051
optOutDepotBuilder: true
10501052
});
10511053
});
1054+
1055+
it('should deploy apps to production with multiple tenants', async () => {
1056+
setContext('push-main-branch');
1057+
setupMocks();
1058+
const config = setupTest({
1059+
tenants: ['demo', 'customer1']
1060+
});
1061+
const result = await flyDeployment(config, true);
1062+
1063+
// Each project should be deployed once for each tenant
1064+
expect(getMockFly().deploy).toHaveBeenCalledTimes(4); // 2 projects * 2 tenants
1065+
1066+
// app-one for demo tenant
1067+
expect(getMockFly().deploy).toHaveBeenCalledWith({
1068+
app: 'app-one-config-demo',
1069+
config: '/apps/app-one/fly.toml',
1070+
env: {
1071+
APP_NAME: 'app-one-config-demo',
1072+
PR_NUMBER: '',
1073+
TENANT_ID: 'demo'
1074+
},
1075+
environment: 'production',
1076+
optOutDepotBuilder: false,
1077+
postgres: expect.any(String)
1078+
});
1079+
1080+
// app-one for customer1 tenant
1081+
expect(getMockFly().deploy).toHaveBeenCalledWith({
1082+
app: 'app-one-config-customer1',
1083+
config: '/apps/app-one/fly.toml',
1084+
env: {
1085+
APP_NAME: 'app-one-config-customer1',
1086+
PR_NUMBER: '',
1087+
TENANT_ID: 'customer1'
1088+
},
1089+
environment: 'production',
1090+
optOutDepotBuilder: false,
1091+
postgres: expect.any(String)
1092+
});
1093+
1094+
// app-two for demo tenant
1095+
expect(getMockFly().deploy).toHaveBeenCalledWith({
1096+
app: 'app-two-config-demo',
1097+
config: '/apps/app-two/src/fly.toml',
1098+
env: {
1099+
APP_NAME: 'app-two-config-demo',
1100+
PR_NUMBER: '',
1101+
TENANT_ID: 'demo'
1102+
},
1103+
environment: 'production',
1104+
optOutDepotBuilder: false
1105+
});
1106+
1107+
// app-two for customer1 tenant
1108+
expect(getMockFly().deploy).toHaveBeenCalledWith({
1109+
app: 'app-two-config-customer1',
1110+
config: '/apps/app-two/src/fly.toml',
1111+
env: {
1112+
APP_NAME: 'app-two-config-customer1',
1113+
PR_NUMBER: '',
1114+
TENANT_ID: 'customer1'
1115+
},
1116+
environment: 'production',
1117+
optOutDepotBuilder: false
1118+
});
1119+
1120+
expect(result).toEqual({
1121+
environment: 'production',
1122+
projects: [
1123+
{
1124+
action: 'deploy',
1125+
app: 'app-one-config-demo',
1126+
name: 'app-one (demo)',
1127+
url: 'https://app-one-config-demo.fly.dev'
1128+
},
1129+
{
1130+
action: 'deploy',
1131+
app: 'app-one-config-customer1',
1132+
name: 'app-one (customer1)',
1133+
url: 'https://app-one-config-customer1.fly.dev'
1134+
},
1135+
{
1136+
action: 'deploy',
1137+
app: 'app-two-config-demo',
1138+
name: 'app-two (demo)',
1139+
url: 'https://app-two-config-demo.fly.dev'
1140+
},
1141+
{
1142+
action: 'deploy',
1143+
app: 'app-two-config-customer1',
1144+
name: 'app-two (customer1)',
1145+
url: 'https://app-two-config-customer1.fly.dev'
1146+
}
1147+
]
1148+
} satisfies ActionOutputs);
1149+
});
10521150
});
10531151

10541152
describe('error handling', () => {

packages/nx-fly-deployment-action/src/lib/main.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ describe('main', () => {
5959
mainBranch: 'main-branch',
6060
optOutDepotBuilder: true,
6161
secrets: [],
62+
tenants: [],
6263
token: 'token'
6364
} satisfies ActionInputs);
6465
expect(runMock).toHaveReturned();
@@ -83,6 +84,7 @@ describe('main', () => {
8384
mainBranch: '',
8485
optOutDepotBuilder: true,
8586
secrets: [],
87+
tenants: [],
8688
token: 'token'
8789
} satisfies ActionInputs);
8890
expect(runMock).toHaveReturned();

packages/nx-fly-deployment-action/src/lib/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export async function run(): Promise<void> {
1919
mainBranch: core.getInput('main-branch'),
2020
optOutDepotBuilder: core.getBooleanInput('opt-out-depot-builder'),
2121
secrets: core.getMultilineInput('secrets'),
22+
tenants: core.getMultilineInput('tenants'),
2223
token: core.getInput('token', { required: true })
2324
} satisfies ActionInputs);
2425

packages/nx-fly-deployment-action/src/lib/schemas/__fixtures__/action-inputs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
"postgresPreview": "db-app",
88
"env": ["ENV_KEY1=env-value1", "ENV_KEY2=env-value2"],
99
"secrets": ["SECRET_KEY1=secret-value1", "SECRET_KEY2=secret-value2"],
10+
"tenants": [],
1011
"token": "github-token"
1112
}

packages/nx-fly-deployment-action/src/lib/schemas/__fixtures__/deployment-config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"SECRET_KEY1": "secret-value1",
1212
"SECRET_KEY2": "secret-value2"
1313
},
14+
"tenants": [],
1415
"token": "github-token"
1516
}

packages/nx-fly-deployment-action/src/lib/schemas/action-inputs.schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const ActionInputsSchema = z.object({
88
mainBranch: z.string(),
99
optOutDepotBuilder: z.boolean(),
1010
secrets: z.array(z.string()),
11+
tenants: z.array(z.string()),
1112
token: z.string().min(1, 'A GitHub token is required')
1213
});
1314

packages/nx-fly-deployment-action/src/lib/schemas/deployment-config.schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const DeploymentConfigSchema = z.object({
1212
}),
1313
mainBranch: z.string().min(1, 'main branch is required'),
1414
secrets: z.record(z.string(), z.string()).optional(),
15+
tenants: z.array(z.string()),
1516
token: ActionInputsSchema.shape.token
1617
});
1718

packages/nx-fly-deployment-action/src/lib/utils/get-deployment-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const getDeploymentConfig = async (
4343
mainBranch: mainBranchInput,
4444
optOutDepotBuilder,
4545
secrets: secretsInput,
46+
tenants,
4647
token
4748
} = inputs;
4849

@@ -66,6 +67,7 @@ export const getDeploymentConfig = async (
6667
},
6768
mainBranch,
6869
secrets,
70+
tenants,
6971
token
7072
};
7173
core.info(JSON.stringify(config, null, 2));

0 commit comments

Comments
 (0)