Skip to content

Commit 3b205f1

Browse files
committed
feat(nx-fly-deployment-action): add support for env and secrets
closed COD-212
1 parent 8af48bc commit 3b205f1

File tree

14 files changed

+256
-11
lines changed

14 files changed

+256
-11
lines changed

.github/workflows/fly-deployment.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ jobs:
8989
main-branch-name: main
9090
set-environment-variables-for-job: true
9191

92+
# TODO: Fetch all tenants and make a deployment for each with their `TENANT_ID`
93+
# TODO: Alternatively, create a new input with multi-tenant values
9294
- name: Run Deployment to Fly
9395
id: deployment
9496
uses: ./packages/nx-fly-deployment-action
@@ -97,3 +99,8 @@ jobs:
9799
fly-org: ${{ vars.FLY_ORG }}
98100
fly-region: ${{ vars.FLY_REGION }}
99101
token: ${{ steps.generate-token.outputs.token }}
102+
env: |
103+
TENANT_ID=default
104+
secrets: |
105+
INFISICAL_CLIENT_ID=${{ secrets.INFISICAL_READ_CLIENT_ID }}
106+
INFISICAL_CLIENT_SECRET=${{ secrets.INFISICAL_READ_CLIENT_SECRET }}

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

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,40 @@ This action will manage deployments to [Fly.io](https://fly.io) of your [Nx](htt
5656
token: ${{ secrets.GITHUB_TOKEN }}
5757
```
5858
59-
### Outputs
59+
### Determine environment
60+
61+
The environment to deploy to is determined by the event type and branch name.
62+
63+
**Pull requests** are deployed to a **preview** environment.
64+
65+
**Push events** on the `main` branch are deployed to the **production** environment.
66+
67+
> [!TIP]
68+
> The environment is also set in the `DEPLOY_ENV` environment variable for the deployed applications
69+
70+
## Inputs
71+
72+
See [action.yaml](action.yml) for descriptions of the inputs.
73+
74+
### Additional input details
75+
76+
`secrets`
6077

61-
The action will output the following variables:
78+
The secrets are passed to the deployed applications as Fly secrets. All secrets are passed to all applications.
79+
80+
Provide the secrets as multiline key/value strings.
81+
82+
```yaml
83+
- uses: ./packages/nx-fly-deployment-action
84+
with:
85+
secrets: |
86+
SECRET_KEY1=secret-value1
87+
SECRET_KEY2=secret-value2
88+
```
89+
90+
> [!NOTE]
91+
> The same pattern also applies to `env` input.
92+
93+
### Outputs
6294

63-
- `environment`: The environment used for deployment.
64-
- `destroyed`: A list of project names that were destroyed.
65-
- `skipped`: A list of project names that were skipped for some reason.
66-
- `deployed`: JSON object containing the deployed project names and their urls.
95+
See [action.yaml](action.yml) for descriptions of the outputs.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ inputs:
2020
description: >
2121
The main branch name.
2222
Defaults to the default branch of the repository.
23+
env:
24+
description: >
25+
A multiline string of environment variables to set for the application.
26+
Each variable should be on a new line in the format of `KEY=VALUE`.
27+
Defaults to an empty string.
28+
secrets:
29+
description: >
30+
A multiline string of secrets to pass to the deployed applications.
31+
Each secret should be on a new line in the format of `KEY=VALUE`.
32+
Defaults to an empty string.
2333
token:
2434
description: >
2535
The token to use for repository authentication.

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,18 @@
22

33
exports[`Fly Deployment Action schema validation > validates all schemas against fixtures > ActionInputsSchema transformed shape 1`] = `
44
{
5+
"env": [
6+
"ENV_KEY1=env-value1",
7+
"ENV_KEY2=env-value2",
8+
],
59
"flyApiToken": "fly-token",
610
"flyOrg": "company",
711
"flyRegion": "sea",
812
"mainBranch": "main",
13+
"secrets": [
14+
"SECRET_KEY1=secret-value1",
15+
"SECRET_KEY2=secret-value2",
16+
],
917
"token": "github-token",
1018
}
1119
`;
@@ -46,6 +54,10 @@ exports[`Fly Deployment Action schema validation > validates all schemas against
4654
"token": "fly-token",
4755
},
4856
"mainBranch": "main",
57+
"secrets": {
58+
"SECRET_KEY1": "secret-value1",
59+
"SECRET_KEY2": "secret-value2",
60+
},
4961
"token": "github-token",
5062
}
5163
`;

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

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,12 @@ describe('flyDeployment', () => {
278278
const setupTest = (configOverride?: Partial<ActionInputs>): ActionInputs => {
279279
return {
280280
...{
281+
env: [],
281282
flyApiToken: 'fly-api-token',
282283
flyOrg: 'fly-org',
283284
flyRegion: '',
284285
mainBranch: '',
286+
secrets: [],
285287
token: 'token'
286288
},
287289
...configOverride
@@ -303,10 +305,12 @@ describe('flyDeployment', () => {
303305
const config = setupTest();
304306

305307
expect(config).toEqual({
308+
env: [],
306309
flyApiToken: 'fly-api-token',
307310
flyOrg: 'fly-org',
308311
flyRegion: '',
309312
mainBranch: '',
313+
secrets: [],
310314
token: 'token'
311315
} satisfies ActionInputs);
312316

@@ -435,6 +439,64 @@ describe('flyDeployment', () => {
435439
]
436440
} satisfies ActionOutputs);
437441
});
442+
443+
it('should deploy apps to preview with environment variables', async () => {
444+
setContext('pr-opened');
445+
setupMocks();
446+
const config = setupTest({
447+
env: ['ENV_KEY1=env-value1', 'ENV_KEY2=env-value2']
448+
});
449+
await flyDeployment(config, true);
450+
451+
expect(getMockFly().deploy).toHaveBeenCalledWith({
452+
app: 'app-one-config-pr-1',
453+
config: '/apps/app-one/fly.toml',
454+
environment: 'preview',
455+
env: {
456+
ENV_KEY1: 'env-value1',
457+
ENV_KEY2: 'env-value2'
458+
}
459+
} satisfies DeployAppOptions);
460+
461+
expect(getMockFly().deploy).toHaveBeenCalledWith({
462+
app: 'app-two-config-pr-1',
463+
config: '/apps/app-two/src/fly.toml',
464+
environment: 'preview',
465+
env: {
466+
ENV_KEY1: 'env-value1',
467+
ENV_KEY2: 'env-value2'
468+
}
469+
} satisfies DeployAppOptions);
470+
});
471+
472+
it('should deploy apps to preview with the same secrets', async () => {
473+
setContext('pr-opened');
474+
setupMocks();
475+
const config = setupTest({
476+
secrets: ['SECRET_KEY1=secret-value1', 'SECRET_KEY2=secret-value2']
477+
});
478+
await flyDeployment(config, true);
479+
480+
expect(getMockFly().deploy).toHaveBeenCalledWith({
481+
app: 'app-one-config-pr-1',
482+
config: '/apps/app-one/fly.toml',
483+
environment: 'preview',
484+
secrets: {
485+
SECRET_KEY1: 'secret-value1',
486+
SECRET_KEY2: 'secret-value2'
487+
}
488+
} satisfies DeployAppOptions);
489+
490+
expect(getMockFly().deploy).toHaveBeenCalledWith({
491+
app: 'app-two-config-pr-1',
492+
config: '/apps/app-two/src/fly.toml',
493+
environment: 'preview',
494+
secrets: {
495+
SECRET_KEY1: 'secret-value1',
496+
SECRET_KEY2: 'secret-value2'
497+
}
498+
} satisfies DeployAppOptions);
499+
});
438500
});
439501

440502
describe('destroy preview', () => {
@@ -594,6 +656,76 @@ describe('flyDeployment', () => {
594656
]
595657
} satisfies ActionOutputs);
596658
});
659+
660+
it('should deploy apps to production with environment variables', async () => {
661+
setContext('push-main-branch');
662+
setupMocks();
663+
const config = setupTest({
664+
env: [
665+
'ENV_KEY1=env"fnutt',
666+
'ENV_KEY2=env space',
667+
'ENV_KEY3=env\\backslash'
668+
]
669+
});
670+
await flyDeployment(config, true);
671+
672+
expect(getMockFly().deploy).toHaveBeenCalledWith({
673+
app: 'app-one-config',
674+
config: '/apps/app-one/fly.toml',
675+
environment: 'production',
676+
env: {
677+
ENV_KEY1: 'env"fnutt',
678+
ENV_KEY2: 'env space',
679+
ENV_KEY3: 'env\\backslash'
680+
}
681+
} satisfies DeployAppOptions);
682+
683+
expect(getMockFly().deploy).toHaveBeenCalledWith({
684+
app: 'app-two-config',
685+
config: '/apps/app-two/src/fly.toml',
686+
environment: 'production',
687+
env: {
688+
ENV_KEY1: 'env"fnutt',
689+
ENV_KEY2: 'env space',
690+
ENV_KEY3: 'env\\backslash'
691+
}
692+
} satisfies DeployAppOptions);
693+
});
694+
695+
it('should deploy apps to production with the same secrets', async () => {
696+
setContext('push-main-branch');
697+
setupMocks();
698+
const config = setupTest({
699+
secrets: [
700+
'SECRET_KEY1=secret"fnutt',
701+
'SECRET_KEY2=secret space',
702+
'SECRET_KEY3=secret\\backslash'
703+
]
704+
});
705+
await flyDeployment(config, true);
706+
707+
expect(getMockFly().deploy).toHaveBeenCalledWith({
708+
app: 'app-one-config',
709+
config: '/apps/app-one/fly.toml',
710+
environment: 'production',
711+
secrets: {
712+
SECRET_KEY1: 'secret"fnutt',
713+
SECRET_KEY2: 'secret space',
714+
SECRET_KEY3: 'secret\\backslash'
715+
}
716+
} satisfies DeployAppOptions);
717+
718+
expect(getMockFly().deploy).toHaveBeenCalledWith({
719+
app: 'app-two-config',
720+
config: '/apps/app-two/src/fly.toml',
721+
environment: 'production',
722+
secrets: {
723+
SECRET_KEY1: 'secret"fnutt',
724+
SECRET_KEY2: 'secret space',
725+
SECRET_KEY3: 'secret\\backslash'
726+
}
727+
} satisfies DeployAppOptions);
728+
});
597729
});
598730

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

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export async function flyDeployment(
5050

5151
core.startGroup('Get deployment configuration');
5252
const config = await getDeploymentConfig(inputs);
53+
// Add environment variables to context
54+
context.env = config.env;
5355
core.endGroup();
5456

5557
core.startGroup('Initialize Fly client');
@@ -73,6 +75,7 @@ export async function flyDeployment(
7375
);
7476
const deployEnv = getDeployEnv(github.context, config.mainBranch);
7577
if (deployEnv.environment) {
78+
// Add target environment to context
7679
context.environment = deployEnv.environment;
7780
} else {
7881
throw new Error(deployEnv.reason);
@@ -130,6 +133,9 @@ export async function flyDeployment(
130133
return ActionOutputsSchema.parse(results);
131134
}
132135

136+
// Add secrets to context available for deployment
137+
context.secrets = config.secrets;
138+
133139
// Create deployments based on affected projects
134140
core.startGroup('Analyze affected projects to deploy');
135141
const projectNames = await getDeployableProjects();
@@ -232,7 +238,9 @@ export async function flyDeployment(
232238
? getPreviewAppName(configAppName, Number(context.pullRequest))
233239
: configAppName,
234240
config: resolvedFlyConfig,
235-
environment: context.environment
241+
env: context.env,
242+
environment: context.environment,
243+
secrets: context.secrets
236244
};
237245
core.info(`Deploy '${options.app}' to '${options.environment}'...`);
238246
try {

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ vi.mock('./fly-deployment');
1919
describe('main', () => {
2020
const getBooleanInputMock = vi.spyOn(core, 'getBooleanInput');
2121
const getInputMock = vi.spyOn(core, 'getInput');
22+
const getMultilineInputMock = vi.spyOn(core, 'getMultilineInput');
2223
const setFailedMock = vi.spyOn(core, 'setFailed');
2324
const setOutputMock = vi.spyOn(core, 'setOutput');
2425

@@ -31,6 +32,7 @@ describe('main', () => {
3132
// Default mock values
3233
getBooleanInputMock.mockImplementation(() => true);
3334
getInputMock.mockImplementation((name: string) => name);
35+
getMultilineInputMock.mockImplementation(() => []);
3436
});
3537

3638
it('should run without exceptions', async () => {
@@ -47,10 +49,12 @@ describe('main', () => {
4749
await main.run();
4850

4951
expect(flyDeploymentMock).toHaveBeenCalledWith({
50-
mainBranch: 'main-branch',
52+
env: [],
5153
flyApiToken: 'fly-api-token',
5254
flyOrg: 'fly-org',
5355
flyRegion: 'fly-region',
56+
mainBranch: 'main-branch',
57+
secrets: [],
5458
token: 'token'
5559
} satisfies ActionInputs);
5660
expect(runMock).toHaveReturned();
@@ -68,10 +72,12 @@ describe('main', () => {
6872
await main.run();
6973

7074
expect(flyDeploymentMock).toHaveBeenCalledWith({
75+
env: [],
7176
flyApiToken: '',
7277
flyOrg: '',
7378
flyRegion: '',
7479
mainBranch: '',
80+
secrets: [],
7581
token: 'token'
7682
} satisfies ActionInputs);
7783
expect(runMock).toHaveReturned();

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import {
1212
export async function run(): Promise<void> {
1313
try {
1414
const inputs = ActionInputsSchema.parse({
15+
env: core.getMultilineInput('env'),
1516
flyApiToken: core.getInput('fly-api-token'),
1617
flyOrg: core.getInput('fly-org'),
1718
flyRegion: core.getInput('fly-region'),
1819
mainBranch: core.getInput('main-branch'),
20+
secrets: core.getMultilineInput('secrets'),
1921
token: core.getInput('token', { required: true })
2022
} satisfies ActionInputs);
2123

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
"flyOrg": "company",
44
"flyRegion": "sea",
55
"mainBranch": "main",
6+
"env": ["ENV_KEY1=env-value1", "ENV_KEY2=env-value2"],
7+
"secrets": ["SECRET_KEY1=secret-value1", "SECRET_KEY2=secret-value2"],
68
"token": "github-token"
79
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@
66
"region": "sea"
77
},
88
"mainBranch": "main",
9+
"secrets": {
10+
"SECRET_KEY1": "secret-value1",
11+
"SECRET_KEY2": "secret-value2"
12+
},
913
"token": "github-token"
1014
}

0 commit comments

Comments
 (0)