forked from cerc-io/snowballtools-base
Implement checking for deployment removal records in intervals
This commit is contained in:
parent
4fa6f418ba
commit
f290b5c0b5
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
@ -104,6 +111,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
|
||||||
|
@ -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<{
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user