Refactor out common code for creating deployment

This commit is contained in:
IshaVenikar 2024-10-15 11:53:23 +05:30 committed by Nabarun
parent 9d8d2199e2
commit 443a3f2b6e
4 changed files with 134 additions and 168 deletions

View File

@ -1,9 +1,9 @@
import debug from 'debug';
import assert from 'assert'; import assert from 'assert';
import { inc as semverInc } from 'semver'; import debug from 'debug';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { DeepPartial } from 'typeorm';
import { Octokit } from 'octokit'; import { Octokit } from 'octokit';
import { inc as semverInc } from 'semver';
import { DeepPartial } from 'typeorm';
import { Registry as LaconicRegistry, getGasPrice, parseGasAndFees } from '@cerc-io/registry-sdk'; import { Registry as LaconicRegistry, getGasPrice, parseGasAndFees } from '@cerc-io/registry-sdk';
@ -14,8 +14,8 @@ import {
ApplicationDeploymentRequest, ApplicationDeploymentRequest,
ApplicationDeploymentRemovalRequest ApplicationDeploymentRemovalRequest
} from './entity/Deployment'; } from './entity/Deployment';
import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionData, PackageJSON } from './types'; import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionData } from './types';
import { getConfig, sleep } from './utils'; import { getConfig, getRepoDetails, sleep } from './utils';
import { Auction } from '@cerc-io/registry-sdk/dist/proto/cerc/auction/v1/auction'; import { Auction } from '@cerc-io/registry-sdk/dist/proto/cerc/auction/v1/auction';
const log = debug('snowball:registry'); const log = debug('snowball:registry');
@ -46,21 +46,20 @@ export class Registry {
} }
async createApplicationRecord({ async createApplicationRecord({
appName, octokit,
packageJSON, repository,
commitHash, commitHash,
appType, appType,
repoUrl
}: { }: {
appName: string; octokit: Octokit
packageJSON: PackageJSON; repository: string;
commitHash: string; commitHash: string;
appType: string; appType: string;
repoUrl: string;
}): Promise<{ }): Promise<{
applicationRecordId: string; applicationRecordId: string;
applicationRecordData: ApplicationRecord; applicationRecordData: ApplicationRecord;
}> { }> {
const { repo, repoUrl, packageJSON } = await getRepoDetails(octokit, repository, commitHash)
// Use registry-sdk to publish record // Use registry-sdk to publish record
// Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh // Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh
// Fetch previous records // Fetch previous records
@ -94,7 +93,7 @@ export class Registry {
repository_ref: commitHash, repository_ref: commitHash,
repository: [repoUrl], repository: [repoUrl],
app_type: appType, app_type: appType,
name: appName, name: repo,
...(packageJSON.description && { description: packageJSON.description }), ...(packageJSON.description && { description: packageJSON.description }),
...(packageJSON.homepage && { homepage: packageJSON.homepage }), ...(packageJSON.homepage && { homepage: packageJSON.homepage }),
...(packageJSON.license && { license: packageJSON.license }), ...(packageJSON.license && { license: packageJSON.license }),
@ -119,10 +118,11 @@ export class Registry {
fee fee
); );
log(`Published application record ${result.id}`);
log('Application record data:', applicationRecord); log('Application record data:', applicationRecord);
// TODO: Discuss computation of LRN // TODO: Discuss computation of LRN
const lrn = this.getLrn(appName); const lrn = this.getLrn(repo);
log(`Setting name: ${lrn} for record ID: ${result.id}`); log(`Setting name: ${lrn} for record ID: ${result.id}`);
await sleep(SLEEP_DURATION); await sleep(SLEEP_DURATION);
@ -170,52 +170,15 @@ export class Registry {
applicationDeploymentAuctionId: string; applicationDeploymentAuctionId: string;
}> { }> {
assert(data.project?.repository, 'Project repository not found'); assert(data.project?.repository, 'Project repository not found');
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;
const { applicationRecordId } =
await this.createApplicationRecord({ await this.createApplicationRecord({
appName: repo, octokit,
packageJSON, repository: data.project.repository,
appType: data.project!.template!, appType: data.project!.template!,
commitHash: data.commitHash!, commitHash: data.commitHash!,
repoUrl,
}); });
log(
`Published application record ${applicationRecordId}`,
);
const lrn = this.getLrn(appName); const lrn = this.getLrn(appName);
const records = await this.registry.resolveNames([lrn]);
const applicationRecord = records[0];
if (!applicationRecord) {
throw new Error(`No record found for ${lrn}`);
}
const config = await getConfig(); const config = await getConfig();
const auctionConfig = config.auction; const auctionConfig = config.auction;

View File

@ -6,7 +6,7 @@ import { Octokit, RequestError } from 'octokit';
import { OAuthApp } from '@octokit/oauth-app'; import { OAuthApp } from '@octokit/oauth-app';
import { Database } from './database'; import { Database } from './database';
import { Deployment, DeploymentStatus, Environment } from './entity/Deployment'; import { ApplicationRecord, Deployment, DeploymentStatus, Environment } from './entity/Deployment';
import { Domain } from './entity/Domain'; import { Domain } from './entity/Domain';
import { EnvironmentVariable } from './entity/EnvironmentVariable'; import { EnvironmentVariable } from './entity/EnvironmentVariable';
import { Organization } from './entity/Organization'; import { Organization } from './entity/Organization';
@ -21,9 +21,9 @@ import {
AppDeploymentRemovalRecord, AppDeploymentRemovalRecord,
AuctionData, AuctionData,
GitPushEventPayload, GitPushEventPayload,
PackageJSON,
} from './types'; } from './types';
import { Role } from './entity/UserOrganization'; import { Role } from './entity/UserOrganization';
import { getRepoDetails } from './utils';
const log = debug('snowball:service'); const log = debug('snowball:service');
@ -598,45 +598,20 @@ export class Service {
userId: string, userId: string,
octokit: Octokit, octokit: Octokit,
data: DeepPartial<Deployment>, data: DeepPartial<Deployment>,
lrn: string deployerLrn: string
): Promise<Deployment> { ): Promise<Deployment> {
assert(data.project?.repository, 'Project repository not found'); assert(data.project?.repository, 'Project repository not found');
log( log(
`Creating deployment in project ${data.project.name} from branch ${data.branch}`, `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) // TODO: Set environment variables for each deployment (environment variables can`t be set in application record)
const { applicationRecordId, applicationRecordData } = const { applicationRecordId, applicationRecordData } =
await this.laconicRegistry.createApplicationRecord({ await this.laconicRegistry.createApplicationRecord({
appName: repo, octokit,
packageJSON, repository: data.project.repository,
appType: data.project!.template!, appType: data.project!.template!,
commitHash: data.commitHash!, commitHash: data.commitHash!,
repoUrl,
}); });
// Update previous deployment with prod branch domain // Update previous deployment with prod branch domain
@ -652,40 +627,10 @@ export class Service {
); );
} }
const newDeployment = await this.db.addDeployment({ const newDeployment = await this.createDeploymentFromData(userId, data, deployerLrn, applicationRecordId, applicationRecordData);
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,
}),
deployerLrn: lrn,
});
log(
`Created deployment ${newDeployment.id} and published application record ${applicationRecordId}`,
);
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 },
);
const { repo, repoUrl } = await getRepoDetails(octokit, data.project.repository, data.commitHash);
const environmentVariablesObj = await this.getEnvVariables(data.project!.id!);
// To set project DNS // To set project DNS
if (data.environment === Environment.Production) { if (data.environment === Environment.Production) {
// On deleting deployment later, project DNS deployment is also deleted // On deleting deployment later, project DNS deployment is also deleted
@ -696,7 +641,7 @@ export class Service {
repository: repoUrl, repository: repoUrl,
environmentVariables: environmentVariablesObj, environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}`, dns: `${newDeployment.project.name}`,
lrn lrn: deployerLrn
}); });
} }
@ -705,7 +650,7 @@ export class Service {
deployment: newDeployment, deployment: newDeployment,
appName: repo, appName: repo,
repository: repoUrl, repository: repoUrl,
lrn, lrn: deployerLrn,
environmentVariables: environmentVariablesObj, environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}-${newDeployment.id}`, dns: `${newDeployment.project.name}-${newDeployment.id}`,
}); });
@ -716,8 +661,8 @@ export class Service {
}); });
// Save deployer lrn only if present // Save deployer lrn only if present
if (lrn) { if (deployerLrn) {
newDeployment.project.deployerLrns = [lrn]; newDeployment.project.deployerLrns = [deployerLrn];
} }
return newDeployment; return newDeployment;
@ -725,7 +670,7 @@ export class Service {
async createDeploymentFromAuction( async createDeploymentFromAuction(
project: DeepPartial<Project>, project: DeepPartial<Project>,
deployer: string deployerLrn: string
): Promise<Deployment> { ): Promise<Deployment> {
const octokit = await this.getOctokit(project.ownerId!); const octokit = await this.getOctokit(project.ownerId!);
const [owner, repo] = project.repository!.split('/'); const [owner, repo] = project.repository!.split('/');
@ -761,40 +706,9 @@ export class Service {
commitMessage: latestCommit.commit.message, commitMessage: latestCommit.commit.message,
}; };
const newDeployment = await this.db.addDeployment({ const newDeployment = await this.createDeploymentFromData(project.ownerId!, deploymentData, deployerLrn, applicationRecordId, applicationRecordData);
project: project,
branch: deploymentData.branch,
commitHash: deploymentData.commitHash,
commitMessage: deploymentData.commitMessage,
environment: deploymentData.environment,
status: DeploymentStatus.Building,
applicationRecordId,
applicationRecordData,
domain: deploymentData.domain,
createdBy: Object.assign(new User(), {
id: project.ownerId!,
}),
deployerLrn: deployer,
});
log(
`Created deployment ${newDeployment.id} and published application record ${applicationRecordId}`,
);
const environmentVariables =
await this.db.getEnvironmentVariablesByProjectId(project!.id!, {
environment: Environment.Production,
});
const environmentVariablesObj = environmentVariables.reduce(
(acc, env) => {
acc[env.key] = env.value;
return acc;
},
{} as { [key: string]: string },
);
const environmentVariablesObj = await this.getEnvVariables(project!.id!);
// To set project DNS // To set project DNS
if (deploymentData.environment === Environment.Production) { if (deploymentData.environment === Environment.Production) {
// On deleting deployment later, project DNS deployment is also deleted // On deleting deployment later, project DNS deployment is also deleted
@ -806,7 +720,7 @@ export class Service {
environmentVariables: environmentVariablesObj, environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}`, dns: `${newDeployment.project.name}`,
auctionId: project.auctionId!, auctionId: project.auctionId!,
lrn: deployer, lrn: deployerLrn,
}); });
} }
@ -817,7 +731,7 @@ export class Service {
appName: repo, appName: repo,
repository: repoUrl, repository: repoUrl,
auctionId: project.auctionId!, auctionId: project.auctionId!,
lrn: deployer, lrn: deployerLrn,
environmentVariables: environmentVariablesObj, environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}-${newDeployment.id}`, dns: `${newDeployment.project.name}-${newDeployment.id}`,
}); });
@ -830,6 +744,34 @@ export class Service {
return newDeployment; return newDeployment;
} }
async createDeploymentFromData(
userId: string,
data: DeepPartial<Deployment>,
deployerLrn: string,
applicationRecordId: string,
applicationRecordData: ApplicationRecord,
): Promise<Deployment> {
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,
}),
deployerLrn,
});
log(`Created deployment ${newDeployment.id}`);
return newDeployment;
}
async addProjectFromTemplate( async addProjectFromTemplate(
user: User, user: User,
organizationSlug: string, organizationSlug: string,
@ -1297,6 +1239,24 @@ export class Service {
return this.db.updateUser(user, data); return this.db.updateUser(user, data);
} }
async getEnvVariables(
projectId: string,
): Promise<{ [key: string]: string }> {
const environmentVariables = await this.db.getEnvironmentVariablesByProjectId(projectId, {
environment: Environment.Production,
});
const environmentVariablesObj = environmentVariables.reduce(
(acc, env) => {
acc[env.key] = env.value;
return acc;
},
{} as { [key: string]: string },
);
return environmentVariablesObj;
}
async getAuctionData( async getAuctionData(
auctionId: string auctionId: string
): Promise<any> { ): Promise<any> {

View File

@ -1,11 +1,14 @@
import assert from 'assert';
import debug from 'debug';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { Octokit } from 'octokit';
import path from 'path'; import path from 'path';
import toml from 'toml'; import toml from 'toml';
import debug from 'debug';
import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm'; import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm';
import { Config } from './config'; import { Config } from './config';
import { DEFAULT_CONFIG_FILE_PATH } from './constants'; import { DEFAULT_CONFIG_FILE_PATH } from './constants';
import { PackageJSON } from './types';
const log = debug('snowball:utils'); const log = debug('snowball:utils');
@ -77,3 +80,43 @@ export const loadAndSaveData = async <Entity extends ObjectLiteral>(
export const sleep = async (ms: number): Promise<void> => export const sleep = async (ms: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms)); new Promise((resolve) => setTimeout(resolve, ms));
export const getRepoDetails = async (
octokit: Octokit,
repository: string,
commitHash: string | undefined,
): Promise<{
repo: string;
packageJSON: PackageJSON;
repoUrl: string;
}> => {
const [owner, repo] = repository.split('/');
const { data: packageJSONData } = await octokit.rest.repos.getContent({
owner,
repo,
path: 'package.json',
ref: 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;
return {
repo,
packageJSON,
repoUrl
};
}

View File

@ -52,7 +52,7 @@ const Id = () => {
{/* Heading */} {/* Heading */}
<div className="flex flex-col items-center gap-1.5"> <div className="flex flex-col items-center gap-1.5">
<Heading as="h3" className="font-medium text-xl"> <Heading as="h3" className="font-medium text-xl">
{isAuction? 'Project created successfully.' : 'Project deployed successfully.'} {isAuction? 'Auction created successfully.' : 'Project deployed successfully.'}
</Heading> </Heading>
</div> </div>