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 9fb72ba0bc
commit 7b51983f8f
4 changed files with 134 additions and 168 deletions

View File

@ -1,9 +1,9 @@
import debug from 'debug';
import assert from 'assert';
import { inc as semverInc } from 'semver';
import debug from 'debug';
import { DateTime } from 'luxon';
import { DeepPartial } from 'typeorm';
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';
@ -14,8 +14,8 @@ import {
ApplicationDeploymentRequest,
ApplicationDeploymentRemovalRequest
} from './entity/Deployment';
import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionData, PackageJSON } from './types';
import { getConfig, sleep } from './utils';
import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionData } from './types';
import { getConfig, getRepoDetails, sleep } from './utils';
import { Auction } from '@cerc-io/registry-sdk/dist/proto/cerc/auction/v1/auction';
const log = debug('snowball:registry');
@ -33,7 +33,7 @@ export class Registry {
private registry: LaconicRegistry;
private registryConfig: RegistryConfig;
constructor (registryConfig: RegistryConfig) {
constructor(registryConfig: RegistryConfig) {
this.registryConfig = registryConfig;
const gasPrice = getGasPrice(registryConfig.fee.gasPrice);
@ -45,22 +45,21 @@ export class Registry {
);
}
async createApplicationRecord ({
appName,
packageJSON,
async createApplicationRecord({
octokit,
repository,
commitHash,
appType,
repoUrl
}: {
appName: string;
packageJSON: PackageJSON;
octokit: Octokit
repository: string;
commitHash: string;
appType: string;
repoUrl: string;
}): Promise<{
applicationRecordId: string;
applicationRecordData: ApplicationRecord;
}> {
const { repo, repoUrl, packageJSON } = await getRepoDetails(octokit, repository, commitHash)
// 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
// Fetch previous records
@ -94,7 +93,7 @@ export class Registry {
repository_ref: commitHash,
repository: [repoUrl],
app_type: appType,
name: appName,
name: repo,
...(packageJSON.description && { description: packageJSON.description }),
...(packageJSON.homepage && { homepage: packageJSON.homepage }),
...(packageJSON.license && { license: packageJSON.license }),
@ -119,10 +118,11 @@ export class Registry {
fee
);
log(`Published application record ${result.id}`);
log('Application record data:', applicationRecord);
// TODO: Discuss computation of LRN
const lrn = this.getLrn(appName);
const lrn = this.getLrn(repo);
log(`Setting name: ${lrn} for record ID: ${result.id}`);
await sleep(SLEEP_DURATION);
@ -161,7 +161,7 @@ export class Registry {
};
}
async createApplicationDeploymentAuction (
async createApplicationDeploymentAuction(
appName: string,
octokit: Octokit,
auctionData: AuctionData,
@ -170,52 +170,15 @@ export class Registry {
applicationDeploymentAuctionId: string;
}> {
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,
await this.createApplicationRecord({
octokit,
repository: data.project.repository,
appType: data.project!.template!,
commitHash: 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({
appName: repo,
packageJSON,
appType: data.project!.template!,
commitHash: data.commitHash!,
repoUrl,
});
log(
`Published application record ${applicationRecordId}`,
);
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 auctionConfig = config.auction;
@ -264,7 +227,7 @@ export class Registry {
};
}
async createApplicationDeploymentRequest (data: {
async createApplicationDeploymentRequest(data: {
deployment: Deployment,
appName: string,
repository: string,
@ -361,7 +324,7 @@ export class Registry {
/**
* Fetch ApplicationDeploymentRecords for deployments
*/
async getDeploymentRecords (
async getDeploymentRecords(
deployments: Deployment[]
): Promise<AppDeploymentRecord[]> {
// Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments
@ -386,7 +349,7 @@ export class Registry {
/**
* Fetch ApplicationDeploymentRecords by filter
*/
async getDeploymentRecordsByFilter (filter: { [key: string]: any }): Promise<AppDeploymentRecord[]> {
async getDeploymentRecordsByFilter(filter: { [key: string]: any }): Promise<AppDeploymentRecord[]> {
return this.registry.queryRecords(
{
type: APP_DEPLOYMENT_RECORD_TYPE,
@ -399,7 +362,7 @@ export class Registry {
/**
* Fetch ApplicationDeploymentRemovalRecords for deployments
*/
async getDeploymentRemovalRecords (
async getDeploymentRemovalRecords(
deployments: Deployment[]
): Promise<AppDeploymentRemovalRecord[]> {
// Fetch ApplicationDeploymentRemovalRecords for corresponding ApplicationDeploymentRecord set in deployments
@ -420,7 +383,7 @@ export class Registry {
);
}
async createApplicationDeploymentRemovalRequest (data: {
async createApplicationDeploymentRemovalRequest(data: {
deploymentId: string;
deployerLrn: string;
}): Promise<{

View File

@ -6,7 +6,7 @@ import { Octokit, RequestError } from 'octokit';
import { OAuthApp } from '@octokit/oauth-app';
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 { EnvironmentVariable } from './entity/EnvironmentVariable';
import { Organization } from './entity/Organization';
@ -21,9 +21,9 @@ import {
AppDeploymentRemovalRecord,
AuctionData,
GitPushEventPayload,
PackageJSON,
} from './types';
import { Role } from './entity/UserOrganization';
import { getRepoDetails } from './utils';
const log = debug('snowball:service');
@ -598,45 +598,20 @@ export class Service {
userId: string,
octokit: Octokit,
data: DeepPartial<Deployment>,
lrn: string
deployerLrn: string
): 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.laconicRegistry.createApplicationRecord({
appName: repo,
packageJSON,
octokit,
repository: data.project.repository,
appType: data.project!.template!,
commitHash: data.commitHash!,
repoUrl,
});
// Update previous deployment with prod branch domain
@ -652,40 +627,10 @@ export class Service {
);
}
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: 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 newDeployment = await this.createDeploymentFromData(userId, data, deployerLrn, applicationRecordId, applicationRecordData);
const { repo, repoUrl } = await getRepoDetails(octokit, data.project.repository, data.commitHash);
const environmentVariablesObj = await this.getEnvVariables(data.project!.id!);
// To set project DNS
if (data.environment === Environment.Production) {
// On deleting deployment later, project DNS deployment is also deleted
@ -696,7 +641,7 @@ export class Service {
repository: repoUrl,
environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}`,
lrn
lrn: deployerLrn
});
}
@ -705,7 +650,7 @@ export class Service {
deployment: newDeployment,
appName: repo,
repository: repoUrl,
lrn,
lrn: deployerLrn,
environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}-${newDeployment.id}`,
});
@ -716,8 +661,8 @@ export class Service {
});
// Save deployer lrn only if present
if (lrn) {
newDeployment.project.deployerLrns = [lrn];
if (deployerLrn) {
newDeployment.project.deployerLrns = [deployerLrn];
}
return newDeployment;
@ -725,7 +670,7 @@ export class Service {
async createDeploymentFromAuction(
project: DeepPartial<Project>,
deployer: string
deployerLrn: string
): Promise<Deployment> {
const octokit = await this.getOctokit(project.ownerId!);
const [owner, repo] = project.repository!.split('/');
@ -761,40 +706,9 @@ export class Service {
commitMessage: latestCommit.commit.message,
};
const newDeployment = await this.db.addDeployment({
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 newDeployment = await this.createDeploymentFromData(project.ownerId!, deploymentData, deployerLrn, applicationRecordId, applicationRecordData);
const environmentVariablesObj = await this.getEnvVariables(project!.id!);
// To set project DNS
if (deploymentData.environment === Environment.Production) {
// On deleting deployment later, project DNS deployment is also deleted
@ -806,7 +720,7 @@ export class Service {
environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}`,
auctionId: project.auctionId!,
lrn: deployer,
lrn: deployerLrn,
});
}
@ -817,7 +731,7 @@ export class Service {
appName: repo,
repository: repoUrl,
auctionId: project.auctionId!,
lrn: deployer,
lrn: deployerLrn,
environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}-${newDeployment.id}`,
});
@ -830,6 +744,34 @@ export class Service {
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(
user: User,
organizationSlug: string,
@ -1297,6 +1239,24 @@ export class Service {
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(
auctionId: string
): Promise<any> {

View File

@ -1,11 +1,14 @@
import assert from 'assert';
import debug from 'debug';
import fs from 'fs-extra';
import { Octokit } from 'octokit';
import path from 'path';
import toml from 'toml';
import debug from 'debug';
import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm';
import { Config } from './config';
import { DEFAULT_CONFIG_FILE_PATH } from './constants';
import { PackageJSON } from './types';
const log = debug('snowball:utils');
@ -77,3 +80,43 @@ export const loadAndSaveData = async <Entity extends ObjectLiteral>(
export const sleep = async (ms: number): Promise<void> =>
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 */}
<div className="flex flex-col items-center gap-1.5">
<Heading as="h3" className="font-medium text-xl">
{isAuction? 'Project created successfully.' : 'Project deployed successfully.'}
{isAuction? 'Auction created successfully.' : 'Project deployed successfully.'}
</Heading>
</div>