Implement checking for deployment removal records in intervals

This commit is contained in:
Nabarun 2024-04-15 19:36:04 +05:30
parent 4fa6f418ba
commit f290b5c0b5
5 changed files with 155 additions and 5 deletions

View File

@ -436,6 +436,19 @@ export class Database {
return Boolean(updateResult.affected); return Boolean(updateResult.affected);
} }
async deleteDeploymentById (deploymentId: string): Promise<boolean> {
const deploymentRepository = this.dataSource.getRepository(Deployment);
const deployment = await deploymentRepository.findOneOrFail({
where: {
id: deploymentId
}
});
const deleteResult = await deploymentRepository.softRemove(deployment);
return Boolean(deleteResult);
}
async addProject (user: User, organizationId: string, data: DeepPartial<Project>): Promise<Project> { async addProject (user: User, organizationId: string, data: DeepPartial<Project>): Promise<Project> {
const projectRepository = this.dataSource.getRepository(Project); const projectRepository = this.dataSource.getRepository(Project);

View File

@ -12,7 +12,7 @@ import {
import { Project } from './Project'; import { Project } from './Project';
import { Domain } from './Domain'; import { Domain } from './Domain';
import { User } from './User'; import { User } from './User';
import { AppDeploymentRecordAttributes } from '../types'; import { AppDeploymentRecordAttributes, AppDeploymentRemovalRecordAttributes } from '../types';
export enum Environment { export enum Environment {
Production = 'Production', Production = 'Production',
@ -24,6 +24,7 @@ export enum DeploymentStatus {
Building = 'Building', Building = 'Building',
Ready = 'Ready', Ready = 'Ready',
Error = 'Error', Error = 'Error',
Deleting = 'Deleting',
} }
export interface ApplicationDeploymentRequest { export interface ApplicationDeploymentRequest {
@ -41,6 +42,12 @@ export interface ApplicationDeploymentRemovalRequest {
deployment: string; deployment: string;
} }
export interface ApplicationDeploymentRemovalRequest {
type: string;
version: string;
deployment: string;
}
export interface ApplicationRecord { export interface ApplicationRecord {
type: string; type: string;
version: string; version: string;
@ -105,6 +112,18 @@ export class Deployment {
@Column('simple-json', { nullable: true }) @Column('simple-json', { nullable: true })
applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null; applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null;
@Column('varchar', { nullable: true })
applicationDeploymentRemovalRequestId!: string | null;
@Column('simple-json', { nullable: true })
applicationDeploymentRemovalRequestData!: ApplicationDeploymentRemovalRequest | null;
@Column('varchar', { nullable: true })
applicationDeploymentRemovalRecordId!: string | null;
@Column('simple-json', { nullable: true })
applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null;
@Column({ @Column({
enum: Environment enum: Environment
}) })

View File

@ -12,7 +12,7 @@ import {
ApplicationDeploymentRequest, ApplicationDeploymentRequest,
ApplicationDeploymentRemovalRequest ApplicationDeploymentRemovalRequest
} from './entity/Deployment'; } from './entity/Deployment';
import { AppDeploymentRecord, PackageJSON } from './types'; import { AppDeploymentRecord, AppDeploymentRemovalRecord, PackageJSON } from './types';
import { sleep } from './utils'; import { sleep } from './utils';
const log = debug('snowball:registry'); const log = debug('snowball:registry');
@ -21,6 +21,7 @@ const APP_RECORD_TYPE = 'ApplicationRecord';
const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest'; const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest';
const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest'; const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest';
const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord'; const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord';
const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord';
const SLEEP_DURATION = 1000; const SLEEP_DURATION = 1000;
// TODO: Move registry code to laconic-sdk/watcher-ts // TODO: Move registry code to laconic-sdk/watcher-ts
@ -231,6 +232,30 @@ export class Registry {
); );
} }
/**
* Fetch ApplicationDeploymentRemovalRecords for deployments
*/
async getDeploymentRemovalRecords (
deployments: Deployment[]
): Promise<AppDeploymentRemovalRecord[]> {
// Fetch ApplicationDeploymentRemovalRecords for corresponding ApplicationDeploymentRecord set in deployments
const records = await this.registry.queryRecords(
{
type: APP_DEPLOYMENT_REMOVAL_RECORD_TYPE
},
true
);
// Filter records with ApplicationDeploymentRecord and ApplicationDeploymentRemovalRequest IDs
return records.filter((record: AppDeploymentRemovalRecord) =>
deployments.some(
(deployment) =>
deployment.applicationDeploymentRemovalRequestId === record.attributes.request &&
deployment.applicationDeploymentRecordId === record.attributes.deployment
)
);
}
async createApplicationDeploymentRemovalRequest (data: { async createApplicationDeploymentRemovalRequest (data: {
deploymentId: string; deploymentId: string;
}): Promise<{ }): Promise<{

View File

@ -15,7 +15,7 @@ import { Permission, ProjectMember } from './entity/ProjectMember';
import { User } from './entity/User'; import { User } from './entity/User';
import { Registry } from './registry'; import { Registry } from './registry';
import { GitHubConfig, RegistryConfig } from './config'; import { GitHubConfig, RegistryConfig } from './config';
import { AppDeploymentRecord, GitPushEventPayload, PackageJSON } from './types'; import { AppDeploymentRecord, AppDeploymentRemovalRecord, GitPushEventPayload, PackageJSON } from './types';
import { Role } from './entity/UserOrganization'; import { Role } from './entity/UserOrganization';
const log = debug('snowball:service'); const log = debug('snowball:service');
@ -63,14 +63,13 @@ export class Service {
/** /**
* Checks for ApplicationDeploymentRecord and update corresponding deployments * Checks for ApplicationDeploymentRecord and update corresponding deployments
* Continues check in loop after a delay of DEPLOY_RECORD_CHECK_DELAY_MS * Continues check in loop after a delay of registryConfig.fetchDeploymentRecordDelay
*/ */
async checkDeployRecordsAndUpdate (): Promise<void> { async checkDeployRecordsAndUpdate (): Promise<void> {
// Fetch deployments in building state // Fetch deployments in building state
const deployments = await this.db.getDeployments({ const deployments = await this.db.getDeployments({
where: { where: {
status: DeploymentStatus.Building status: DeploymentStatus.Building
// TODO: Fetch and check records for recent deployments
} }
}); });
@ -116,6 +115,38 @@ export class Service {
}, this.config.registryConfig.fetchDeploymentRecordDelay); }, this.config.registryConfig.fetchDeploymentRecordDelay);
} }
/**
* Checks for ApplicationDeploymentRemovalRecord and remove corresponding deployments
* Continues check in loop after a delay of registryConfig.fetchDeploymentRecordDelay
*/
async checkDeploymentRemovalRecordsAndUpdate (): Promise<void> {
// Fetch deployments in deleting state
const deployments = await this.db.getDeployments({
where: {
status: DeploymentStatus.Deleting
}
});
if (deployments.length) {
log(
`Found ${deployments.length} deployments in ${DeploymentStatus.Deleting} state`
);
// Fetch ApplicationDeploymentRemovalRecords for deployments
const records = await this.registry.getDeploymentRemovalRecords(deployments);
log(`Found ${records.length} ApplicationDeploymentRemovalRecords`);
// Update deployments for which ApplicationDeploymentRemovalRecords were returned
if (records.length) {
await this.deleteDeploymentsWithRecordData(records, deployments);
}
}
this.deployRecordCheckTimeout = setTimeout(() => {
this.checkDeploymentRemovalRecordsAndUpdate();
}, this.config.registryConfig.fetchDeploymentRecordDelay);
}
/** /**
* Update deployments with ApplicationDeploymentRecord data * Update deployments with ApplicationDeploymentRecord data
*/ */
@ -178,6 +209,45 @@ export class Service {
await Promise.all(deploymentUpdatePromises); await Promise.all(deploymentUpdatePromises);
} }
/**
* Delete deployments with ApplicationDeploymentRemovalRecord data
*/
async deleteDeploymentsWithRecordData (
records: AppDeploymentRemovalRecord[],
deployments: Deployment[],
): Promise<void> {
const removedApplicationDeploymentRecordIds = records.map(record => record.attributes.deployment);
// Get removed deployments for ApplicationDeploymentRecords
const removedDeployments = deployments.filter(deployment => removedApplicationDeploymentRecordIds.includes(deployment.applicationDeploymentRecordId!))
const recordToDeploymentsMap = removedDeployments.reduce(
(acc: { [key: string]: Deployment }, deployment) => {
acc[deployment.applicationDeploymentRecordId!] = deployment;
return acc;
},
{}
);
// Update deployment data for ApplicationDeploymentRecords and delete
const deploymentUpdatePromises = records.map(async (record) => {
const deployment = recordToDeploymentsMap[record.attributes.deployment];
await this.db.updateDeploymentById(deployment.id, {
applicationDeploymentRemovalRecordId: record.id,
applicationDeploymentRemovalRecordData: record.attributes,
});
log(
`Updated deployment ${deployment.id} with ApplicationDeploymentRemovalRecord ${record.id}`
);
await this.db.deleteDeploymentById(deployment.id)
});
await Promise.all(deploymentUpdatePromises);
}
async getUser (userId: string): Promise<User | null> { async getUser (userId: string): Promise<User | null> {
return this.db.getUser({ return this.db.getUser({
where: { where: {
@ -725,10 +795,22 @@ export class Service {
id: deploymentId id: deploymentId
} }
}); });
if (deployment && deployment.applicationDeploymentRecordId) { if (deployment && deployment.applicationDeploymentRecordId) {
const result = await this.registry.createApplicationDeploymentRemovalRequest({ deploymentId: deployment.applicationDeploymentRecordId }); const result = await this.registry.createApplicationDeploymentRemovalRequest({ deploymentId: deployment.applicationDeploymentRecordId });
await this.db.updateDeploymentById(
deployment.id,
{
status: DeploymentStatus.Deleting,
applicationDeploymentRemovalRequestId: result.applicationDeploymentRemovalRequestId,
applicationDeploymentRemovalRequestData: result.applicationDeploymentRemovalRequestData
}
);
return (result !== undefined || result !== null); return (result !== undefined || result !== null);
} }
return false; return false;
} }

View File

@ -38,6 +38,13 @@ export interface AppDeploymentRecordAttributes {
version: string; version: string;
} }
export interface AppDeploymentRemovalRecordAttributes {
deployment: string;
request: string;
type: "ApplicationDeploymentRemovalRecord";
version: string;
}
interface RegistryRecord { interface RegistryRecord {
id: string; id: string;
names: string[] | null; names: string[] | null;
@ -50,3 +57,7 @@ interface RegistryRecord {
export interface AppDeploymentRecord extends RegistryRecord { export interface AppDeploymentRecord extends RegistryRecord {
attributes: AppDeploymentRecordAttributes; attributes: AppDeploymentRecordAttributes;
} }
export interface AppDeploymentRemovalRecord extends RegistryRecord {
attributes: AppDeploymentRemovalRecordAttributes;
}