Add back-end function to create deployment with auction

This commit is contained in:
IshaVenikar 2024-10-03 16:50:47 +05:30 committed by Nabarun
parent 0466632426
commit da3ecde4a1
4 changed files with 254 additions and 11 deletions

View File

@ -28,6 +28,12 @@ export enum DeploymentStatus {
Deleting = 'Deleting', Deleting = 'Deleting',
} }
export interface ApplicationDeploymentAuction {
application: string;
auction: string;
type: string;
}
export interface ApplicationDeploymentRequest { export interface ApplicationDeploymentRequest {
type: string; type: string;
version: string; version: string;
@ -112,13 +118,13 @@ export class Deployment {
@Column('simple-json', { nullable: true }) @Column('simple-json', { nullable: true })
applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null; applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null;
@Column('varchar', { nullable: true }) @Column('varchar', { nullable: true })
applicationDeploymentRemovalRequestId!: string | null; applicationDeploymentRemovalRequestId!: string | null;
@Column('simple-json', { nullable: true }) @Column('simple-json', { nullable: true })
applicationDeploymentRemovalRequestData!: ApplicationDeploymentRemovalRequest | null; applicationDeploymentRemovalRequestData!: ApplicationDeploymentRemovalRequest | null;
@Column('varchar', { nullable: true }) @Column('varchar', { nullable: true })
applicationDeploymentRemovalRecordId!: string | null; applicationDeploymentRemovalRecordId!: string | null;
@ -147,7 +153,7 @@ export class Deployment {
@UpdateDateColumn() @UpdateDateColumn()
updatedAt!: Date; updatedAt!: Date;
@DeleteDateColumn() @DeleteDateColumn()
deletedAt!: Date | null; deletedAt!: Date | null;
} }

View File

@ -10,14 +10,16 @@ import {
ApplicationRecord, ApplicationRecord,
Deployment, Deployment,
ApplicationDeploymentRequest, ApplicationDeploymentRequest,
ApplicationDeploymentRemovalRequest ApplicationDeploymentRemovalRequest,
ApplicationDeploymentAuction
} from './entity/Deployment'; } from './entity/Deployment';
import { AppDeploymentRecord, AppDeploymentRemovalRecord, PackageJSON } from './types'; import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionData, PackageJSON } from './types';
import { sleep } from './utils'; import { sleep } from './utils';
const log = debug('snowball:registry'); const log = debug('snowball:registry');
const APP_RECORD_TYPE = 'ApplicationRecord'; const APP_RECORD_TYPE = 'ApplicationRecord';
const APP_DEPLOYMENT_AUCTION_RECORD_TYPE = 'ApplicationDeploymentAuction';
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';
@ -148,10 +150,94 @@ export class Registry {
}; };
} }
async createApplicationDeploymentAuction (data: {
deployment: Deployment,
appName: string,
},
auctionData: AuctionData,
): Promise<{
applicationDeploymentAuctionId: string;
applicationDeploymentAuctionData: ApplicationDeploymentAuction;
deployerLrns: string[];
}> {
const lrn = this.getLrn(data.appName);
const records = await this.registry.resolveNames([lrn]);
const applicationRecord = records[0];
if (!applicationRecord) {
throw new Error(`No record found for ${lrn}`);
}
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
// TODO: Take auction params from user
const auctionResult = await this.registry.createProviderAuction(
{
commitFee: auctionData.commitFee,
commitsDuration: auctionData.commitsDuration,
revealFee: auctionData.revealFee,
revealsDuration: auctionData.revealsDuration,
denom: auctionData.denom,
maxPrice: auctionData.maxPrice,
numProviders: auctionData.numProviders,
},
this.registryConfig.privateKey,
fee
)
if (!auctionResult.auction) {
throw new Error('Error creating auction');
}
// Create record of type applicationDeploymentAuction and publish
const applicationDeploymentAuction = {
application: `${lrn}@${applicationRecord.attributes.app_version}`,
auction: auctionResult.auction.id,
type: APP_DEPLOYMENT_AUCTION_RECORD_TYPE,
};
await sleep(SLEEP_DURATION);
const result = await this.registry.setRecord(
{
privateKey: this.registryConfig.privateKey,
record: applicationDeploymentAuction,
bondId: this.registryConfig.bondId
},
this.registryConfig.privateKey,
fee
);
log(`Application deployment auction record published: ${result.id}`);
log('Application deployment auction data:', applicationDeploymentAuction);
let deployerLrns = [];
const { winnerAddresses } = auctionResult.auction;
for (const auctionWinner of winnerAddresses) {
const deployerRecord = await this.registry.queryRecords(
{
paymentAddress: auctionWinner,
},
true
);
const lrn = deployerRecord.names.length > 0 ? deployerRecord.names[0] : null;
deployerLrns.push(lrn);
}
return {
applicationDeploymentAuctionId: auctionResult.auction.id,
applicationDeploymentAuctionData: applicationDeploymentAuction,
deployerLrns
};
}
async createApplicationDeploymentRequest (data: { async createApplicationDeploymentRequest (data: {
deployment: Deployment, deployment: Deployment,
appName: string, appName: string,
repository: string, repository: string,
auctionId?: string,
lrn?: string,
environmentVariables: { [key: string]: string }, environmentVariables: { [key: string]: string },
dns: string, dns: string,
}): Promise<{ }): Promise<{
@ -175,8 +261,6 @@ export class Registry {
dns: data.dns, dns: data.dns,
// TODO: Not set in test-progressive-web-app CI // TODO: Not set in test-progressive-web-app CI
// deployment: '$CERC_REGISTRY_DEPLOYMENT_LRN',
// https://git.vdb.to/cerc-io/laconic-registry-cli/commit/129019105dfb93bebcea02fde0ed64d0f8e5983b // https://git.vdb.to/cerc-io/laconic-registry-cli/commit/129019105dfb93bebcea02fde0ed64d0f8e5983b
config: JSON.stringify({ config: JSON.stringify({
env: data.environmentVariables env: data.environmentVariables
@ -187,7 +271,9 @@ export class Registry {
)}`, )}`,
repository: data.repository, repository: data.repository,
repository_ref: data.deployment.commitHash repository_ref: data.deployment.commitHash
}) }),
...(data.lrn && { deployer: data.lrn }),
...(data.auctionId && { auction: data.auctionId }),
}; };
await sleep(SLEEP_DURATION); await sleep(SLEEP_DURATION);

View File

@ -19,6 +19,7 @@ import {
AddProjectFromTemplateInput, AddProjectFromTemplateInput,
AppDeploymentRecord, AppDeploymentRecord,
AppDeploymentRemovalRecord, AppDeploymentRemovalRecord,
AuctionData,
GitPushEventPayload, GitPushEventPayload,
PackageJSON, PackageJSON,
} from './types'; } from './types';
@ -644,10 +645,145 @@ export class Service {
return newDeployment; return newDeployment;
} }
async createDeploymentFromAuction(
userId: string,
octokit: Octokit,
data: DeepPartial<Deployment>,
auctionData: AuctionData
): Promise<Deployment> {
assert(data.project?.repository, 'Project repository not found');
log(
`Creating deployment in project ${data.project.name} from branch ${data.branch}`,
);
const [owner, repo] = data.project.repository.split('/');
const { data: packageJSONData } = await octokit.rest.repos.getContent({
owner,
repo,
path: 'package.json',
ref: data.commitHash,
});
if (!packageJSONData) {
throw new Error('Package.json file not found');
}
assert(!Array.isArray(packageJSONData) && packageJSONData.type === 'file');
const packageJSON: PackageJSON = JSON.parse(atob(packageJSONData.content));
assert(packageJSON.name, "name field doesn't exist in package.json");
const repoUrl = (
await octokit.rest.repos.get({
owner,
repo,
})
).data.html_url;
// TODO: Set environment variables for each deployment (environment variables can`t be set in application record)
const { applicationRecordId, applicationRecordData } =
await this.registry.createApplicationRecord({
appName: repo,
packageJSON,
appType: data.project!.template!,
commitHash: data.commitHash!,
repoUrl,
});
// Update previous deployment with prod branch domain
// TODO: Fix unique constraint error for domain
if (data.domain) {
await this.db.updateDeployment(
{
domainId: data.domain.id,
},
{
domain: null,
},
);
}
const newDeployment = await this.db.addDeployment({
project: data.project,
branch: data.branch,
commitHash: data.commitHash,
commitMessage: data.commitMessage,
environment: data.environment,
status: DeploymentStatus.Building,
applicationRecordId,
applicationRecordData,
domain: data.domain,
createdBy: Object.assign(new User(), {
id: userId,
}),
});
log(
`Created deployment ${newDeployment.id} and published application record ${applicationRecordId}`,
);
const deploymentAuctionData = await this.registry.createApplicationDeploymentAuction({
deployment: newDeployment,
appName: repo
}, auctionData
);
const deploymentAuctionId = deploymentAuctionData.applicationDeploymentAuctionId;
const environmentVariables =
await this.db.getEnvironmentVariablesByProjectId(data.project.id!, {
environment: Environment.Production,
});
const environmentVariablesObj = environmentVariables.reduce(
(acc, env) => {
acc[env.key] = env.value;
return acc;
},
{} as { [key: string]: string },
);
// To set project DNS
if (data.environment === Environment.Production) {
// On deleting deployment later, project DNS deployment is also deleted
// So publish project DNS deployment first so that ApplicationDeploymentRecord for the same is available when deleting deployment later
await this.registry.createApplicationDeploymentRequest({
deployment: newDeployment,
appName: repo,
repository: repoUrl,
environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}`,
});
}
for (const deployer in deploymentAuctionData.deployerLrns) {
const { applicationDeploymentRequestId, applicationDeploymentRequestData } =
// Create requests for all the deployers
await this.registry.createApplicationDeploymentRequest({
deployment: newDeployment,
appName: repo,
repository: repoUrl,
auctionId: deploymentAuctionId,
lrn: deployer,
environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}-${newDeployment.id}`,
});
await this.db.updateDeploymentById(newDeployment.id, {
applicationDeploymentRequestId,
applicationDeploymentRequestData,
});
}
return newDeployment;
}
async addProjectFromTemplate( async addProjectFromTemplate(
user: User, user: User,
organizationSlug: string, organizationSlug: string,
data: AddProjectFromTemplateInput, data: AddProjectFromTemplateInput,
auctionData?: AuctionData
): Promise<Project | undefined> { ): Promise<Project | undefined> {
try { try {
const octokit = await this.getOctokit(user.id); const octokit = await this.getOctokit(user.id);
@ -678,7 +814,7 @@ export class Service {
repository: gitRepo.data.full_name, repository: gitRepo.data.full_name,
// TODO: Set selected template // TODO: Set selected template
template: 'webapp', template: 'webapp',
}); }, auctionData);
if (!project || !project.id) { if (!project || !project.id) {
throw new Error('Failed to create project from template'); throw new Error('Failed to create project from template');
@ -695,6 +831,7 @@ export class Service {
user: User, user: User,
organizationSlug: string, organizationSlug: string,
data: DeepPartial<Project>, data: DeepPartial<Project>,
auctiondata?: AuctionData
): Promise<Project | undefined> { ): Promise<Project | undefined> {
const organization = await this.db.getOrganization({ const organization = await this.db.getOrganization({
where: { where: {
@ -720,14 +857,18 @@ export class Service {
}); });
// Create deployment with prod branch and latest commit // Create deployment with prod branch and latest commit
const deployment = await this.createDeployment(user.id, octokit, { const deploymentData = {
project, project,
branch: project.prodBranch, branch: project.prodBranch,
environment: Environment.Production, environment: Environment.Production,
domain: null, domain: null,
commitHash: latestCommit.sha, commitHash: latestCommit.sha,
commitMessage: latestCommit.commit.message, commitMessage: latestCommit.commit.message,
}); };
const deployment = auctiondata
? await this.createDeploymentFromAuction(user.id, octokit, deploymentData, auctiondata)
: await this.createDeployment(user.id, octokit, deploymentData);
await this.createRepoHook(octokit, project); await this.createRepoHook(octokit, project);

View File

@ -69,3 +69,13 @@ export interface AddProjectFromTemplateInput {
name: string; name: string;
isPrivate: boolean; isPrivate: boolean;
} }
export interface AuctionData {
commitFee: string,
commitsDuration: string,
revealFee: string,
revealsDuration: string,
denom: string,
maxPrice: string,
numProviders: number,
}