Add back-end function to create deployment with auction
This commit is contained in:
parent
0466632426
commit
da3ecde4a1
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user