Skip to content

Commit adc348d

Browse files
committed
feat(nx-fly-deployment-action): destroy apps retroactively
closed COD-227
1 parent bf3938b commit adc348d

File tree

7 files changed

+296
-187
lines changed

7 files changed

+296
-187
lines changed

packages/core/src/actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export {
55
type Environment,
66
EnvironmentSchema
77
} from './lib/actions/get-deploy-env';
8+
export { getPullRequest } from './lib/actions/get-pull-request';
89
export { printGitHubContext } from './lib/actions/print-github-context';
910
export { withGitHub } from './lib/actions/with-github';

packages/nx-migrate-action/src/lib/utils/get-pull-request.ts renamed to packages/core/src/lib/actions/get-pull-request.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as core from '@actions/core';
22
import * as github from '@actions/github';
3-
import { withGitHub } from '@codeware/core/actions';
3+
4+
import { withGitHub } from './with-github';
45

56
/**
67
* Get a pull request.

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ vi.mock('@actions/github', () => ({
3737
vi.mock('@codeware/core/actions', async () => ({
3838
...(await vi.importActual('@codeware/core/actions')),
3939
addPullRequestComment: vi.fn(),
40+
getPullRequest: vi.fn(),
4041
getRepositoryDefaultBranch: vi.fn(),
4142
withGitHubContext: vi.fn()
4243
}));
@@ -433,12 +434,12 @@ describe('flyDeployment', () => {
433434
{
434435
action: 'skip',
435436
appOrProject: 'app-one',
436-
reason: 'Project configuration not found'
437+
reason: 'Project config not found'
437438
},
438439
{
439440
action: 'skip',
440441
appOrProject: 'app-two',
441-
reason: 'Project configuration not found'
442+
reason: 'Project config not found'
442443
}
443444
]
444445
} satisfies ActionOutputs);
@@ -458,12 +459,12 @@ describe('flyDeployment', () => {
458459
{
459460
action: 'skip',
460461
appOrProject: 'app-one',
461-
reason: 'GitHub configuration file not found for the project'
462+
reason: 'GitHub config file not found for the project'
462463
},
463464
{
464465
action: 'skip',
465466
appOrProject: 'app-two',
466-
reason: 'GitHub configuration file not found for the project'
467+
reason: 'GitHub config file not found for the project'
467468
}
468469
]
469470
} satisfies ActionOutputs);
@@ -717,12 +718,12 @@ describe('flyDeployment', () => {
717718
{
718719
action: 'skip',
719720
appOrProject: 'app-one',
720-
reason: 'Project configuration not found'
721+
reason: 'Project config not found'
721722
},
722723
{
723724
action: 'skip',
724725
appOrProject: 'app-two',
725-
reason: 'Project configuration not found'
726+
reason: 'Project config not found'
726727
}
727728
]
728729
} satisfies ActionOutputs);
@@ -742,12 +743,12 @@ describe('flyDeployment', () => {
742743
{
743744
action: 'skip',
744745
appOrProject: 'app-one',
745-
reason: 'GitHub configuration file not found for the project'
746+
reason: 'GitHub config file not found for the project'
746747
},
747748
{
748749
action: 'skip',
749750
appOrProject: 'app-two',
750-
reason: 'GitHub configuration file not found for the project'
751+
reason: 'GitHub config file not found for the project'
751752
}
752753
]
753754
} satisfies ActionOutputs);

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

Lines changed: 30 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { join } from 'path';
2-
31
import * as core from '@actions/core';
42
import * as github from '@actions/github';
53
import {
64
addPullRequestComment,
75
getDeployEnv,
86
printGitHubContext
97
} from '@codeware/core/actions';
10-
import { type DeployAppOptions, Fly } from '@codeware/fly-node';
8+
import { Fly } from '@codeware/fly-node';
119
import type {
1210
PullRequestEvent,
1311
WebhookEventName
@@ -17,12 +15,9 @@ import { type ActionInputs } from './schemas/action-inputs.schema';
1715
import { ActionOutputsSchema } from './schemas/action-outputs.schema';
1816
import { type ActionOutputs } from './schemas/action-outputs.schema';
1917
import { type BuildingContext, ContextSchema } from './schemas/context.schema';
20-
import { addOpinionatedEnv } from './utils/add-opinionated-env';
21-
import { getDeployableProjects } from './utils/get-deployable-projects';
2218
import { getDeploymentConfig } from './utils/get-deployment-config';
23-
import { getPreviewAppName } from './utils/get-preview-app-name';
24-
import { getProjectConfiguration } from './utils/get-project-configuration';
25-
import { lookupGitHubConfigFile } from './utils/lookup-github-config-file';
19+
import { runDeployApps } from './utils/run-deploy-apps';
20+
import { runDestroyApps } from './utils/run-destroy-apps';
2621

2722
/**
2823
* Run fly deployment process
@@ -72,14 +67,18 @@ export async function flyDeployment(
7267
core.info(
7368
`Get deployment environment for '${eventName}' on '${currentBranch}'`
7469
);
70+
7571
const deployEnv = getDeployEnv(github.context, config.mainBranch);
72+
7673
if (deployEnv.environment) {
7774
// Add target environment to context
7875
context.environment = deployEnv.environment;
7976
} else {
8077
throw new Error(deployEnv.reason);
8178
}
79+
8280
core.info(`Using environment '${context.environment}'`);
81+
8382
switch (eventName as WebhookEventName) {
8483
case 'pull_request':
8584
{
@@ -96,208 +95,63 @@ export async function flyDeployment(
9695
}
9796
core.endGroup();
9897

98+
// Verify context data before actions
99+
ContextSchema.parse(context);
100+
99101
// Initialize action results
100102
const results: ActionOutputs = {
101103
environment: context.environment,
102104
projects: []
103105
};
104106

105-
// Destroy deployments based on pull request number
107+
// Action: Destroy and exit
106108
if (context.action === 'destroy') {
107-
// Verify context data before destroying deployments
108-
ContextSchema.parse(context);
109-
110-
core.startGroup('Analyze fly apps to destroy');
111-
const apps = await fly.apps.list();
112-
for (const app of apps) {
113-
if (app.name.endsWith(`-pr-${context.pullRequest}`)) {
114-
core.info(`Destroy preview app '${app.name}'`);
115-
116-
try {
117-
await fly.apps.destroy(app.name);
118-
results.projects.push({ action: 'destroy', app: app.name });
119-
} catch (error) {
120-
const msg = error instanceof Error ? error.message : String(error);
121-
core.warning(msg);
122-
results.projects.push({
123-
action: 'skip',
124-
appOrProject: app.name,
125-
reason: 'Failed to destroy application'
126-
});
127-
}
128-
}
129-
}
109+
core.startGroup('Destroy deprecated applications');
110+
results.projects = await runDestroyApps(config, fly);
130111
core.endGroup();
131112

132113
return ActionOutputsSchema.parse(results);
133114
}
134-
135-
// Create deployments based on affected projects
136-
core.startGroup('Analyze affected projects to deploy');
137-
const projectNames = await getDeployableProjects();
138-
core.info(`Deployable projects: ${projectNames.join(', ')}`);
139-
core.endGroup();
140-
141-
if (projectNames.length === 0) {
142-
core.info('No projects to deploy, skipping deployment');
143-
return ActionOutputsSchema.parse(results);
144-
}
145-
146-
// Analyze each project
147-
for (const projectName of projectNames) {
148-
core.startGroup(`Analyze project '${projectName}' before deployment`);
149-
150-
// Get project configuration
151-
core.info(`Get project configuration for '${projectName}'`);
152-
const projectConfig = await getProjectConfiguration(projectName);
153-
if (!projectConfig) {
154-
const reason = 'Project configuration not found';
155-
core.warning(`${reason}, not possible to deploy`);
156-
results.projects.push({
157-
appOrProject: projectName,
158-
action: 'skip',
159-
reason
160-
});
161-
continue;
162-
}
163-
core.info(
164-
`Found project configuration with root '${projectConfig.root}'`
165-
);
166-
167-
// Find github.json
168-
core.info(`Lookup GitHub configuration file in '${projectConfig.root}'`);
169-
const gcResponse = await lookupGitHubConfigFile(projectConfig.root);
170-
if (!gcResponse) {
171-
const reason = 'GitHub configuration file not found for the project';
172-
core.info(`${reason}, skipping`);
173-
results.projects.push({
174-
appOrProject: projectName,
175-
action: 'skip',
176-
reason
177-
});
178-
continue;
179-
}
180-
const { configFile, content: githubConfig } = gcResponse;
181-
core.info(`Found GitHub configuration file '${configFile}'`);
182-
183-
if (!githubConfig.deploy) {
184-
const reason =
185-
'Deployment is disabled in GitHub configuration for the project';
186-
core.info(`${reason}, skipping`);
187-
results.projects.push({
188-
appOrProject: projectName,
189-
action: 'skip',
190-
reason
191-
});
192-
continue;
193-
}
194-
195-
// Verify and get app name from fly.toml
196-
core.info(
197-
`Deployment is enabled, lookup Fly configuration file '${githubConfig.flyConfig}'`
198-
);
199-
const resolvedFlyConfig = join(
200-
projectConfig.root,
201-
githubConfig.flyConfig
202-
);
203-
let configAppName: string;
204-
try {
205-
const flyConfig = await fly.config.show({
206-
config: resolvedFlyConfig,
207-
local: true
208-
});
209-
configAppName = flyConfig.app;
210-
core.info(
211-
`Resolved app name '${configAppName}' from Fly configuration '${resolvedFlyConfig}'`
212-
);
213-
} catch {
214-
const reason = `Fly configuration file not found '${resolvedFlyConfig}'`;
215-
core.warning(`${reason}, not possible to deploy`);
216-
results.projects.push({
217-
appOrProject: projectName,
218-
action: 'skip',
219-
reason
220-
});
221-
continue;
222-
}
223-
core.endGroup();
224-
225-
// Verify context data before deploying
226-
ContextSchema.parse(context);
227-
228-
// Requirements are met, ready to deploy
229-
230-
core.startGroup(`Project '${projectName}' ready to fly`);
231-
232-
const appName =
233-
context.environment === 'preview'
234-
? getPreviewAppName(configAppName, Number(context.pullRequest))
235-
: configAppName;
236-
const env = addOpinionatedEnv(
237-
{ appName, prNumber: context.pullRequest },
238-
config.env
239-
);
240-
const postgres =
241-
context.environment === 'preview'
242-
? githubConfig.flyPostgresPreview
243-
: githubConfig.flyPostgresProduction;
244-
245-
const options: DeployAppOptions = {
246-
app: appName,
247-
config: resolvedFlyConfig,
248-
env,
115+
// Action: Deploy
116+
else if (context.action === 'deploy') {
117+
core.startGroup('Deploy affected applications');
118+
results.projects = await runDeployApps({
119+
config,
249120
environment: context.environment,
250-
postgres: postgres || undefined, // rather undefined than empty string
251-
secrets: config.secrets
252-
};
253-
254-
core.info(`Deploy '${options.app}' to '${options.environment}'...`);
255-
256-
try {
257-
const result = await fly.deploy(options);
258-
core.info(`Deployed to '${result.url}'`);
259-
results.projects.push({
260-
action: 'deploy',
261-
app: result.app,
262-
name: projectName,
263-
url: result.url
264-
});
265-
} catch (error) {
266-
const msg = error instanceof Error ? error.message : String(error);
267-
core.warning(msg);
268-
results.projects.push({
269-
appOrProject: projectName,
270-
action: 'skip',
271-
reason: 'Failed to deploy project'
272-
});
273-
}
274-
121+
fly,
122+
pullRequest: context.pullRequest
123+
});
275124
core.endGroup();
276125
}
277126

278127
// Preview deployments should add a comment to the pull request
279-
if (results.environment === 'preview') {
280-
core.startGroup('Pull request preview comment');
281-
core.info('Analyze deployed projects');
282-
const deployed = results.projects.filter((p) => p.action === 'deploy');
128+
const deployed = results.projects.filter((p) => p.action === 'deploy');
129+
if (deployed.length && results.environment === 'preview') {
130+
core.startGroup('Add pull request preview comment');
131+
132+
// Create a table with deployed projects
283133
const comment = [
284134
`:sparkles: Your pull request project${
285135
deployed.length > 1 ? 's are' : ' is'
286136
} ready for preview`,
287137
'| Project | App name | Preview |',
288138
'| --- | --- | --- |'
289139
];
140+
290141
for (const project of deployed) {
291142
comment.push(
292143
`| ${project.name} | ${project.app} | [${project.url}](${project.url}) |`
293144
);
294145
}
146+
295147
core.info(`Add comment to pull request ${context.pullRequest}`);
148+
296149
await addPullRequestComment(
297150
config.token,
298151
Number(context.pullRequest),
299152
comment.join('\n')
300153
);
154+
core.endGroup();
301155
}
302156

303157
return ActionOutputsSchema.parse(results);

0 commit comments

Comments
 (0)