Display deployment build logs #8
@ -23,6 +23,7 @@ import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
||||
import { Domain } from './entity/Domain';
|
||||
import { getEntities, loadAndSaveData } from './utils';
|
||||
import { UserOrganization } from './entity/UserOrganization';
|
||||
import { Deployer } from './entity/Deployer';
|
||||
|
||||
const ORGANIZATION_DATA_PATH = '../test/fixtures/organizations.json';
|
||||
|
||||
@ -215,7 +216,8 @@ export class Database {
|
||||
relations: {
|
||||
project: true,
|
||||
domain: true,
|
||||
createdBy: true
|
||||
createdBy: true,
|
||||
deployer: true,
|
||||
},
|
||||
where: {
|
||||
project: {
|
||||
@ -488,6 +490,12 @@ export class Database {
|
||||
return projectRepository.save(newProject);
|
||||
}
|
||||
|
||||
async saveProject (project: Project): Promise<Project> {
|
||||
const projectRepository = this.dataSource.getRepository(Project);
|
||||
|
||||
return projectRepository.save(project);
|
||||
}
|
||||
|
||||
async updateProjectById (
|
||||
projectId: string,
|
||||
data: DeepPartial<Project>
|
||||
@ -573,4 +581,19 @@ export class Database {
|
||||
|
||||
return domains;
|
||||
}
|
||||
|
||||
async addDeployer (data: DeepPartial<Deployer>): Promise<Deployer> {
|
||||
const deployerRepository = this.dataSource.getRepository(Deployer);
|
||||
const newDomain = await deployerRepository.save(data);
|
||||
|
||||
return newDomain;
|
||||
}
|
||||
|
||||
async getDeployerById (deployerId: string): Promise<Deployer | null> {
|
||||
const deployerRepository = this.dataSource.getRepository(Deployer);
|
||||
|
||||
const deployer = await deployerRepository.findOne({ where: { deployerId } });
|
||||
|
||||
return deployer;
|
||||
}
|
||||
}
|
||||
|
20
packages/backend/src/entity/Deployer.ts
Normal file
20
packages/backend/src/entity/Deployer.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Entity, PrimaryColumn, Column, ManyToMany } from 'typeorm';
|
||||
import { Project } from './Project';
|
||||
|
||||
@Entity()
|
||||
export class Deployer {
|
||||
@PrimaryColumn('varchar')
|
||||
deployerId!: string;
|
||||
|
||||
@Column('varchar')
|
||||
deployerLrn!: string;
|
||||
|
||||
@Column('varchar')
|
||||
deployerApiUrl!: string;
|
||||
|
||||
@Column('varchar')
|
||||
baseDomain!: string;
|
||||
|
||||
@ManyToMany(() => Project, (project) => project.deployers)
|
||||
projects!: Project[];
|
||||
}
|
@ -13,6 +13,7 @@ import {
|
||||
import { Project } from './Project';
|
||||
import { Domain } from './Domain';
|
||||
import { User } from './User';
|
||||
import { Deployer } from './Deployer';
|
||||
import { AppDeploymentRecordAttributes, AppDeploymentRemovalRecordAttributes } from '../types';
|
||||
|
||||
export enum Environment {
|
||||
@ -127,8 +128,9 @@ export class Deployment {
|
||||
@Column('simple-json', { nullable: true })
|
||||
applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null;
|
||||
|
||||
@Column('varchar')
|
||||
deployerLrn!: string;
|
||||
@ManyToOne(() => Deployer)
|
||||
@JoinColumn({ name: 'deployerId' })
|
||||
deployer!: Deployer;
|
||||
|
||||
@Column({
|
||||
enum: Environment
|
||||
@ -138,9 +140,6 @@ export class Deployment {
|
||||
@Column('boolean', { default: false })
|
||||
isCurrent!: boolean;
|
||||
|
||||
@Column('varchar', { nullable: true })
|
||||
baseDomain!: string | null;
|
||||
|
||||
@Column({
|
||||
enum: DeploymentStatus
|
||||
})
|
||||
|
@ -7,13 +7,16 @@ import {
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
OneToMany,
|
||||
DeleteDateColumn
|
||||
DeleteDateColumn,
|
||||
JoinTable,
|
||||
ManyToMany
|
||||
} from 'typeorm';
|
||||
|
||||
import { User } from './User';
|
||||
import { Organization } from './Organization';
|
||||
import { ProjectMember } from './ProjectMember';
|
||||
import { Deployment } from './Deployment';
|
||||
import { Deployer } from './Deployer';
|
||||
|
||||
@Entity()
|
||||
export class Project {
|
||||
@ -49,8 +52,9 @@ export class Project {
|
||||
@Column('varchar', { nullable: true })
|
||||
auctionId!: string | null;
|
||||
|
||||
@Column({ type: 'simple-array', nullable: true })
|
||||
deployerLrns!: string[] | null;
|
||||
@ManyToMany(() => Deployer, (deployer) => (deployer.projects))
|
||||
@JoinTable()
|
||||
deployers!: Deployer[]
|
||||
|
||||
@Column('boolean', { default: false, nullable: true })
|
||||
fundsReleased!: boolean;
|
||||
@ -70,9 +74,6 @@ export class Project {
|
||||
@Column('varchar')
|
||||
icon!: string;
|
||||
|
||||
@Column({ type: 'simple-array', nullable: true })
|
||||
baseDomains!: string[] | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
ApplicationDeploymentRequest,
|
||||
ApplicationDeploymentRemovalRequest
|
||||
} from './entity/Deployment';
|
||||
import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionParams } from './types';
|
||||
import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionParams, DeployerRecord } from './types';
|
||||
import { getConfig, getRepoDetails, sleep } from './utils';
|
||||
|
||||
const log = debug('snowball:registry');
|
||||
@ -25,6 +25,7 @@ const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest';
|
||||
const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest';
|
||||
const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord';
|
||||
const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord';
|
||||
const WEBAPP_DEPLOYER_RECORD_TYPE = 'WebappDeployer'
|
||||
const SLEEP_DURATION = 1000;
|
||||
|
||||
// TODO: Move registry code to registry-sdk/watcher-ts
|
||||
@ -291,32 +292,29 @@ export class Registry {
|
||||
};
|
||||
}
|
||||
|
||||
async getAuctionWinningDeployers(
|
||||
async getAuctionWinningDeployerRecords(
|
||||
auctionId: string
|
||||
): Promise<string[]> {
|
||||
): Promise<DeployerRecord[]> {
|
||||
const records = await this.registry.getAuctionsByIds([auctionId]);
|
||||
const auctionResult = records[0];
|
||||
|
||||
let deployerLrns = [];
|
||||
let deployerRecords = [];
|
||||
const { winnerAddresses } = auctionResult;
|
||||
|
||||
for (const auctionWinner of winnerAddresses) {
|
||||
const deployerRecords = await this.registry.queryRecords(
|
||||
{
|
||||
const records = await this.getDeployerRecordsByFilter({
|
||||
paymentAddress: auctionWinner,
|
||||
},
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
for (const record of deployerRecords) {
|
||||
if (record.names && record.names.length > 0) {
|
||||
deployerLrns.push(record.names[0]);
|
||||
for (const record of records) {
|
||||
if (record.id) {
|
||||
deployerRecords.push(record);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deployerLrns;
|
||||
return deployerRecords;
|
||||
}
|
||||
|
||||
async releaseDeployerFunds(
|
||||
@ -359,6 +357,19 @@ export class Registry {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch WebappDeployer Records by filter
|
||||
*/
|
||||
async getDeployerRecordsByFilter(filter: { [key: string]: any }): Promise<DeployerRecord[]> {
|
||||
return this.registry.queryRecords(
|
||||
{
|
||||
type: WEBAPP_DEPLOYER_RECORD_TYPE,
|
||||
...filter
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch ApplicationDeploymentRecords by filter
|
||||
*/
|
||||
|
@ -104,7 +104,8 @@ type Deployment {
|
||||
commitMessage: String!
|
||||
url: String
|
||||
environment: Environment!
|
||||
deployerLrn: String
|
||||
deployer: Deployer
|
||||
applicationDeploymentRequestId: String
|
||||
isCurrent: Boolean!
|
||||
baseDomain: String
|
||||
status: DeploymentStatus!
|
||||
@ -132,6 +133,14 @@ type EnvironmentVariable {
|
||||
updatedAt: String!
|
||||
}
|
||||
|
||||
type Deployer {
|
||||
deployerId: String!
|
||||
deployerLrn: String!
|
||||
deployerApiUrl: String!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
}
|
||||
|
||||
type AuthResult {
|
||||
token: String!
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import { Project } from './entity/Project';
|
||||
import { Permission, ProjectMember } from './entity/ProjectMember';
|
||||
import { User } from './entity/User';
|
||||
import { Registry } from './registry';
|
||||
import { Deployer } from './entity/Deployer';
|
||||
import { GitHubConfig, RegistryConfig } from './config';
|
||||
import {
|
||||
AddProjectFromTemplateInput,
|
||||
@ -174,6 +175,7 @@ export class Service {
|
||||
applicationDeploymentRequestId: record.attributes.request,
|
||||
})),
|
||||
relations: {
|
||||
deployer: true,
|
||||
project: true,
|
||||
},
|
||||
order: {
|
||||
@ -193,30 +195,23 @@ export class Service {
|
||||
const deploymentUpdatePromises = records.map(async (record) => {
|
||||
const deployment = recordToDeploymentsMap[record.attributes.request];
|
||||
|
||||
const parts = record.attributes.url.replace('https://', '').split('.');
|
||||
const baseDomain = parts.slice(1).join('.');
|
||||
|
||||
if (!deployment.project) {
|
||||
log(`Project ${deployment.projectId} not found`);
|
||||
return;
|
||||
} else {
|
||||
deployment.applicationDeploymentRecordId = record.id;
|
||||
deployment.applicationDeploymentRecordData = record.attributes;
|
||||
deployment.url = record.attributes.url;
|
||||
deployment.baseDomain = baseDomain;
|
||||
deployment.status = DeploymentStatus.Ready;
|
||||
deployment.isCurrent = deployment.environment === Environment.Production;
|
||||
|
||||
await this.db.updateDeploymentById(deployment.id, deployment);
|
||||
|
||||
const baseDomains = deployment.project.baseDomains || [];
|
||||
|
||||
if (!baseDomains.includes(baseDomain)) {
|
||||
baseDomains.push(baseDomain);
|
||||
}
|
||||
|
||||
// Release deployer funds on successful deployment
|
||||
if (!deployment.project.fundsReleased) {
|
||||
const fundsReleased = await this.releaseDeployerFundsByProjectId(deployment.projectId);
|
||||
|
||||
await this.db.updateProjectById(deployment.projectId, {
|
||||
baseDomains,
|
||||
fundsReleased,
|
||||
});
|
||||
}
|
||||
@ -224,6 +219,7 @@ export class Service {
|
||||
log(
|
||||
`Updated deployment ${deployment.id} with URL ${record.attributes.url}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(deploymentUpdatePromises);
|
||||
@ -235,7 +231,7 @@ export class Service {
|
||||
for (const deployment of prodDeployments) {
|
||||
const projectDeployments = await this.db.getDeploymentsByProjectId(deployment.projectId);
|
||||
const oldDeployments = projectDeployments
|
||||
.filter(projectDeployment => projectDeployment.deployerLrn === deployment.deployerLrn && projectDeployment.id !== deployment.id);
|
||||
.filter(projectDeployment => projectDeployment.deployer.deployerLrn === deployment.deployer.deployerLrn && projectDeployment.id !== deployment.id);
|
||||
for (const oldDeployment of oldDeployments) {
|
||||
await this.db.updateDeployment(
|
||||
{ id: oldDeployment.id },
|
||||
@ -308,17 +304,37 @@ export class Service {
|
||||
);
|
||||
|
||||
for (const project of projectsToBedeployed) {
|
||||
const deployerLrns = await this.laconicRegistry.getAuctionWinningDeployers(project.auctionId!);
|
||||
const deployerRecords = await this.laconicRegistry.getAuctionWinningDeployerRecords(project!.auctionId!);
|
||||
|
||||
if (!deployerLrns) {
|
||||
if (!deployerRecords) {
|
||||
log(`No winning deployer for auction ${project!.auctionId}`);
|
||||
} else {
|
||||
// Update project with deployer LRNs
|
||||
await this.db.updateProjectById(project.id!, {
|
||||
deployerLrns
|
||||
});
|
||||
const deployerIds = [];
|
||||
|
||||
for (const deployer of deployerLrns) {
|
||||
for (const record of deployerRecords) {
|
||||
const deployerId = record.id;
|
||||
const deployerLrn = record.names[0];
|
||||
|
||||
deployerIds.push(deployerId);
|
||||
|
||||
const deployerApiUrl = record.attributes.apiUrl;
|
||||
const baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1);
|
||||
|
||||
const deployerData = {
|
||||
deployerId,
|
||||
deployerLrn,
|
||||
deployerApiUrl,
|
||||
baseDomain
|
||||
};
|
||||
|
||||
// Store the deployer in the DB
|
||||
const deployer = await this.db.addDeployer(deployerData);
|
||||
|
||||
// Update project with deployer
|
||||
await this.updateProjectWithDeployer(project.id, deployer);
|
||||
}
|
||||
|
||||
for (const deployer of deployerIds) {
|
||||
log(`Creating deployment for deployer LRN ${deployer}`);
|
||||
await this.createDeploymentFromAuction(project, deployer);
|
||||
}
|
||||
@ -587,7 +603,7 @@ export class Service {
|
||||
domain: prodBranchDomains[0],
|
||||
commitHash: oldDeployment.commitHash,
|
||||
commitMessage: oldDeployment.commitMessage,
|
||||
deployerLrn: oldDeployment.deployerLrn
|
||||
deployer: oldDeployment.deployer
|
||||
});
|
||||
|
||||
return newDeployment;
|
||||
@ -596,7 +612,8 @@ export class Service {
|
||||
async createDeployment(
|
||||
userId: string,
|
||||
octokit: Octokit,
|
||||
data: DeepPartial<Deployment>
|
||||
data: DeepPartial<Deployment>,
|
||||
deployerLrn?: string
|
||||
): Promise<Deployment> {
|
||||
assert(data.project?.repository, 'Project repository not found');
|
||||
log(
|
||||
@ -625,7 +642,14 @@ export class Service {
|
||||
);
|
||||
}
|
||||
|
||||
const newDeployment = await this.createDeploymentFromData(userId, data, data.deployerLrn!, applicationRecordId, applicationRecordData);
|
||||
let deployer;
|
||||
if (deployerLrn) {
|
||||
deployer = await this.createDeployerFromLRN(deployerLrn);
|
||||
} else {
|
||||
deployer = data.deployer;
|
||||
}
|
||||
|
||||
const newDeployment = await this.createDeploymentFromData(userId, data, deployer!.deployerId!, applicationRecordId, applicationRecordData);
|
||||
|
||||
const { repo, repoUrl } = await getRepoDetails(octokit, data.project.repository, data.commitHash);
|
||||
const environmentVariablesObj = await this.getEnvVariables(data.project!.id!);
|
||||
@ -639,7 +663,7 @@ export class Service {
|
||||
repository: repoUrl,
|
||||
environmentVariables: environmentVariablesObj,
|
||||
dns: `${newDeployment.project.name}`,
|
||||
lrn: data.deployerLrn!
|
||||
lrn: deployer!.deployerLrn!
|
||||
});
|
||||
}
|
||||
|
||||
@ -648,7 +672,7 @@ export class Service {
|
||||
deployment: newDeployment,
|
||||
appName: repo,
|
||||
repository: repoUrl,
|
||||
lrn: data.deployerLrn!,
|
||||
lrn: deployer!.deployerLrn!,
|
||||
environmentVariables: environmentVariablesObj,
|
||||
dns: `${newDeployment.project.name}-${newDeployment.id}`,
|
||||
});
|
||||
@ -663,7 +687,7 @@ export class Service {
|
||||
|
||||
async createDeploymentFromAuction(
|
||||
project: DeepPartial<Project>,
|
||||
deployerLrn: string
|
||||
deployerId: string
|
||||
): Promise<Deployment> {
|
||||
const octokit = await this.getOctokit(project.ownerId!);
|
||||
const [owner, repo] = project.repository!.split('/');
|
||||
@ -689,6 +713,9 @@ export class Service {
|
||||
const applicationRecordId = record.id;
|
||||
const applicationRecordData = record.attributes;
|
||||
|
||||
const deployer = await this.db.getDeployerById(deployerId);
|
||||
const deployerLrn = deployer!.deployerLrn
|
||||
|
||||
// Create deployment with prod branch and latest commit
|
||||
const deploymentData = {
|
||||
project,
|
||||
@ -699,7 +726,7 @@ export class Service {
|
||||
commitMessage: latestCommit.commit.message,
|
||||
};
|
||||
|
||||
const newDeployment = await this.createDeploymentFromData(project.ownerId!, deploymentData, deployerLrn, applicationRecordId, applicationRecordData);
|
||||
const newDeployment = await this.createDeploymentFromData(project.ownerId!, deploymentData, deployerId, applicationRecordId, applicationRecordData);
|
||||
|
||||
const environmentVariablesObj = await this.getEnvVariables(project!.id!);
|
||||
// To set project DNS
|
||||
@ -740,7 +767,7 @@ export class Service {
|
||||
async createDeploymentFromData(
|
||||
userId: string,
|
||||
data: DeepPartial<Deployment>,
|
||||
deployerLrn: string,
|
||||
deployerId: string,
|
||||
applicationRecordId: string,
|
||||
applicationRecordData: ApplicationRecord,
|
||||
): Promise<Deployment> {
|
||||
@ -757,7 +784,9 @@ export class Service {
|
||||
createdBy: Object.assign(new User(), {
|
||||
id: userId,
|
||||
}),
|
||||
deployerLrn,
|
||||
deployer: Object.assign(new Deployer(), {
|
||||
deployerId,
|
||||
}),
|
||||
});
|
||||
|
||||
log(`Created deployment ${newDeployment.id}`);
|
||||
@ -765,6 +794,50 @@ export class Service {
|
||||
return newDeployment;
|
||||
}
|
||||
|
||||
async createDeployerFromLRN(deployerLrn: string): Promise<Deployer | null> {
|
||||
const records = await this.laconicRegistry.getRecordsByName(deployerLrn);
|
||||
|
||||
if (records.length === 0) {
|
||||
log('No records found for deployer LRN:', deployerLrn);
|
||||
return null;
|
||||
}
|
||||
|
||||
const deployerId = records[0].id;
|
||||
const deployerApiUrl = records[0].attributes.apiUrl;
|
||||
const baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1);
|
||||
|
||||
const deployerData = {
|
||||
deployerId,
|
||||
deployerLrn,
|
||||
deployerApiUrl,
|
||||
baseDomain
|
||||
};
|
||||
|
||||
const deployer = await this.db.addDeployer(deployerData);
|
||||
|
||||
return deployer;
|
||||
}
|
||||
|
||||
async updateProjectWithDeployer(
|
||||
projectId: string,
|
||||
deployer: Deployer
|
||||
): Promise<Deployer> {
|
||||
const deploymentProject = await this.db.getProjects({
|
||||
where: { id: projectId },
|
||||
relations: ['deployers']
|
||||
});
|
||||
|
||||
if (!deploymentProject[0].deployers) {
|
||||
deploymentProject[0].deployers = [];
|
||||
}
|
||||
|
||||
deploymentProject[0].deployers.push(deployer);
|
||||
|
||||
await this.db.saveProject(deploymentProject[0]);
|
||||
|
||||
return deployer;
|
||||
}
|
||||
|
||||
async addProjectFromTemplate(
|
||||
user: User,
|
||||
organizationSlug: string,
|
||||
@ -828,6 +901,7 @@ export class Service {
|
||||
slug: organizationSlug,
|
||||
},
|
||||
});
|
||||
|
||||
if (!organization) {
|
||||
throw new Error('Organization does not exist');
|
||||
}
|
||||
@ -858,15 +932,15 @@ export class Service {
|
||||
domain: null,
|
||||
commitHash: latestCommit.sha,
|
||||
commitMessage: latestCommit.commit.message,
|
||||
deployerLrn: lrn
|
||||
};
|
||||
|
||||
if (auctionParams) {
|
||||
const { applicationDeploymentAuctionId } = await this.laconicRegistry.createApplicationDeploymentAuction(repo, octokit, auctionParams!, deploymentData);
|
||||
await this.updateProject(project.id, { auctionId: applicationDeploymentAuctionId })
|
||||
await this.updateProject(project.id, { auctionId: applicationDeploymentAuctionId });
|
||||
} else {
|
||||
await this.createDeployment(user.id, octokit, deploymentData);
|
||||
await this.updateProject(project.id, { deployerLrns: [lrn!] })
|
||||
const newDeployment = await this.createDeployment(user.id, octokit, deploymentData, lrn);
|
||||
// Update project with deployer
|
||||
await this.updateProjectWithDeployer(newDeployment.projectId, newDeployment.deployer);
|
||||
}
|
||||
|
||||
await this.createRepoHook(octokit, project);
|
||||
@ -920,6 +994,9 @@ export class Service {
|
||||
);
|
||||
const projects = await this.db.getProjects({
|
||||
where: { repository: repository.full_name },
|
||||
relations: {
|
||||
deployers: true,
|
||||
}
|
||||
});
|
||||
|
||||
if (!projects.length) {
|
||||
@ -936,7 +1013,7 @@ export class Service {
|
||||
branch,
|
||||
});
|
||||
|
||||
const deployers = project.deployerLrns;
|
||||
const deployers = project.deployers;
|
||||
if (!deployers) {
|
||||
log(`No deployer present for project ${project.id}`)
|
||||
return;
|
||||
@ -955,7 +1032,7 @@ export class Service {
|
||||
domain,
|
||||
commitHash: headCommit.id,
|
||||
commitMessage: headCommit.message,
|
||||
deployerLrn: deployer
|
||||
deployer: deployer
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -995,6 +1072,7 @@ export class Service {
|
||||
relations: {
|
||||
project: true,
|
||||
domain: true,
|
||||
deployer: true,
|
||||
createdBy: true,
|
||||
},
|
||||
where: {
|
||||
@ -1011,8 +1089,7 @@ export class Service {
|
||||
let newDeployment: Deployment;
|
||||
|
||||
if (oldDeployment.project.auctionId) {
|
||||
// TODO: Discuss creating applicationRecord for redeployments
|
||||
newDeployment = await this.createDeploymentFromAuction(oldDeployment.project, oldDeployment.deployerLrn);
|
||||
newDeployment = await this.createDeploymentFromAuction(oldDeployment.project, oldDeployment.deployer.deployerId);
|
||||
} else {
|
||||
newDeployment = await this.createDeployment(user.id, octokit,
|
||||
{
|
||||
@ -1023,7 +1100,7 @@ export class Service {
|
||||
domain: oldDeployment.domain,
|
||||
commitHash: oldDeployment.commitHash,
|
||||
commitMessage: oldDeployment.commitMessage,
|
||||
deployerLrn: oldDeployment.deployerLrn
|
||||
deployer: oldDeployment.deployer
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1072,13 +1149,14 @@ export class Service {
|
||||
},
|
||||
relations: {
|
||||
project: true,
|
||||
deployer: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (deployment && deployment.applicationDeploymentRecordId) {
|
||||
// If deployment is current, remove deployment for project subdomain as well
|
||||
if (deployment.isCurrent) {
|
||||
const currentDeploymentURL = `https://${(deployment.project.name).toLowerCase()}.${deployment.baseDomain}`;
|
||||
const currentDeploymentURL = `https://${(deployment.project.name).toLowerCase()}.${deployment.deployer.baseDomain}`;
|
||||
|
||||
// TODO: Store the latest DNS deployment record
|
||||
const deploymentRecords =
|
||||
@ -1101,14 +1179,14 @@ export class Service {
|
||||
|
||||
await this.laconicRegistry.createApplicationDeploymentRemovalRequest({
|
||||
deploymentId: latestRecord.id,
|
||||
deployerLrn: deployment.deployerLrn
|
||||
deployerLrn: deployment.deployer.deployerLrn
|
||||
});
|
||||
}
|
||||
|
||||
const result =
|
||||
await this.laconicRegistry.createApplicationDeploymentRemovalRequest({
|
||||
deploymentId: deployment.applicationDeploymentRecordId,
|
||||
deployerLrn: deployment.deployerLrn
|
||||
deployerLrn: deployment.deployer.deployerLrn
|
||||
});
|
||||
|
||||
await this.db.updateDeploymentById(deployment.id, {
|
||||
|
@ -82,3 +82,20 @@ export interface EnvironmentVariables {
|
||||
key: string,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export interface DeployerRecord {
|
||||
id: string;
|
||||
names: string[];
|
||||
owners: string[];
|
||||
bondId: string;
|
||||
createTime: string;
|
||||
expiryTime: string;
|
||||
attributes: {
|
||||
apiUrl: string;
|
||||
name: string;
|
||||
paymentAddress: string;
|
||||
publicKey: string;
|
||||
type: string;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
@ -139,30 +139,8 @@ const Configure = () => {
|
||||
|
||||
const projectId = await createProject(createFormData, environmentVariables);
|
||||
|
||||
const { environmentVariables: isEnvironmentVariablesAdded } =
|
||||
await client.getEnvironmentVariables(projectId);
|
||||
|
||||
if (isEnvironmentVariablesAdded.length > 0) {
|
||||
toast({
|
||||
id:
|
||||
createFormData.variables.length > 1
|
||||
? 'env_variable_added'
|
||||
: 'env_variables_added',
|
||||
title:
|
||||
createFormData.variables.length > 1
|
||||
? `${createFormData.variables.length} variables added`
|
||||
: `Variable added`,
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
id: 'env_variables_not_added',
|
||||
title: 'Environment variables not added',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
}
|
||||
if (templateId) {
|
||||
createFormData.option === 'Auction'
|
||||
? navigate(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import {
|
||||
Deployment,
|
||||
DeploymentStatus,
|
||||
@ -6,6 +6,14 @@ import {
|
||||
Environment,
|
||||
Project,
|
||||
} from 'gql-client';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from '@mui/material';
|
||||
|
||||
import { Avatar } from 'components/shared/Avatar';
|
||||
import {
|
||||
BranchStrokeIcon,
|
||||
@ -18,12 +26,23 @@ import {
|
||||
import { Heading } from 'components/shared/Heading';
|
||||
import { OverflownText } from 'components/shared/OverflownText';
|
||||
import { Tag, TagTheme } from 'components/shared/Tag';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import { getInitials } from 'utils/geInitials';
|
||||
import { relativeTimeMs } from 'utils/time';
|
||||
import { SHORT_COMMIT_HASH_LENGTH } from '../../../../constants';
|
||||
import { formatAddress } from '../../../../utils/format';
|
||||
import { DeploymentMenu } from './DeploymentMenu';
|
||||
|
||||
const DEPLOYMENT_LOGS_STYLE = {
|
||||
backgroundColor: "rgba(0,0,0, .9)",
|
||||
padding: "2em",
|
||||
borderRadius: "0.5em",
|
||||
marginLeft: "0.5em",
|
||||
marginRight: "0.5em",
|
||||
color: "gray",
|
||||
fontSize: "small",
|
||||
};
|
||||
|
||||
interface DeployDetailsCardProps {
|
||||
deployment: Deployment;
|
||||
currentDeployment: Deployment;
|
||||
@ -48,6 +67,12 @@ const DeploymentDetailsCard = ({
|
||||
project,
|
||||
prodBranchDomains,
|
||||
}: DeployDetailsCardProps) => {
|
||||
const [openDialog, setOpenDialog] = useState<boolean>(false);
|
||||
const [deploymentLogs, setDeploymentLogs] = useState<string>();
|
||||
|
||||
const handleOpenDialog = () => setOpenDialog(true);
|
||||
const handleCloseDialog = () => setOpenDialog(false);
|
||||
|
||||
const getIconByDeploymentStatus = (status: DeploymentStatus) => {
|
||||
if (
|
||||
status === DeploymentStatus.Building ||
|
||||
@ -64,6 +89,14 @@ const DeploymentDetailsCard = ({
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDeploymentLogs = useCallback(async () => {
|
||||
let url = `${deployment.deployer.deployerApiUrl}/log/${deployment.applicationDeploymentRequestId}`;
|
||||
const res = await fetch(url, { cache: 'no-store' });
|
||||
const logs = await res.text();
|
||||
setDeploymentLogs(logs);
|
||||
handleOpenDialog();
|
||||
}, [deployment.deployer.deployerApiUrl, deployment.applicationDeploymentRequestId, handleOpenDialog]);
|
||||
|
||||
const renderDeploymentStatus = useCallback(
|
||||
(className?: string) => {
|
||||
return (
|
||||
@ -72,6 +105,7 @@ const DeploymentDetailsCard = ({
|
||||
leftIcon={getIconByDeploymentStatus(deployment.status)}
|
||||
size="xs"
|
||||
type={STATUS_COLORS[deployment.status] ?? 'neutral'}
|
||||
onClick={fetchDeploymentLogs}
|
||||
>
|
||||
{deployment.status}
|
||||
</Tag>
|
||||
@ -96,9 +130,9 @@ const DeploymentDetailsCard = ({
|
||||
</OverflownText>
|
||||
</Heading>
|
||||
)}
|
||||
{deployment.deployerLrn && (
|
||||
{deployment.deployer.deployerLrn && (
|
||||
<span className="text-sm text-elements-low-em tracking-tight block mt-2">
|
||||
Deployer LRN: {deployment.deployerLrn}
|
||||
Deployer LRN: {deployment.deployer.deployerLrn}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-sm text-elements-low-em tracking-tight block">
|
||||
@ -167,6 +201,15 @@ const DeploymentDetailsCard = ({
|
||||
prodBranchDomains={prodBranchDomains}
|
||||
/>
|
||||
</div>
|
||||
<Dialog open={openDialog} onClose={handleCloseDialog} fullWidth maxWidth="md">
|
||||
<DialogTitle>Deployment logs</DialogTitle>
|
||||
<DialogContent style={DEPLOYMENT_LOGS_STYLE} >
|
||||
{deploymentLogs && <pre >{deploymentLogs}</pre>}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleCloseDialog}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -36,7 +36,11 @@ const deployment: Deployment = {
|
||||
url: 'https://deploy1.example.com',
|
||||
environment: Environment.Production,
|
||||
isCurrent: true,
|
||||
deployerLrn: 'lrn://example/deployers/webapp-deployer-api.test.com',
|
||||
deployer: {
|
||||
deployerApiUrl: 'https://webapp-deployer-api.example.com',
|
||||
deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu',
|
||||
deployerLrn:'lrn://example/deployers/webapp-deployer-api.example.com'
|
||||
},
|
||||
status: DeploymentStatus.Ready,
|
||||
createdBy: {
|
||||
id: 'user1',
|
||||
@ -49,6 +53,7 @@ const deployment: Deployment = {
|
||||
},
|
||||
createdAt: '1677676800', // 2023-03-01T12:00:00Z
|
||||
updatedAt: '1677680400', // 2023-03-01T13:00:00Z
|
||||
applicationDeploymentRequestId: 'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize',
|
||||
};
|
||||
|
||||
const domains: Domain[] = [
|
||||
|
@ -102,7 +102,12 @@ export const deployment0: Deployment = {
|
||||
domain: domain0,
|
||||
commitMessage: 'Commit Message',
|
||||
createdBy: user,
|
||||
deployerLrn: 'lrn://deployer.apps.snowballtools.com ',
|
||||
deployer: {
|
||||
deployerApiUrl: 'https://webapp-deployer-api.example.com',
|
||||
deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu',
|
||||
deployerLrn:'lrn://deployer.apps.snowballtools.com '
|
||||
},
|
||||
applicationDeploymentRequestId: 'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize',
|
||||
};
|
||||
|
||||
export const project: Project = {
|
||||
|
@ -136,7 +136,11 @@ query ($projectId: String!) {
|
||||
commitHash
|
||||
commitMessage
|
||||
url
|
||||
deployerLrn
|
||||
deployer {
|
||||
deployerId
|
||||
deployerLrn,
|
||||
deployerApiUrl,
|
||||
}
|
||||
environment
|
||||
isCurrent
|
||||
baseDomain
|
||||
@ -148,6 +152,7 @@ query ($projectId: String!) {
|
||||
name
|
||||
email
|
||||
}
|
||||
applicationDeploymentRequestId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -105,7 +105,7 @@ export type Deployment = {
|
||||
commitHash: string;
|
||||
commitMessage: string;
|
||||
url?: string;
|
||||
deployerLrn: string;
|
||||
deployer: Deployer;
|
||||
environment: Environment;
|
||||
isCurrent: boolean;
|
||||
baseDomain?: string;
|
||||
@ -113,8 +113,15 @@ export type Deployment = {
|
||||
createdBy: User;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
applicationDeploymentRequestId: string;
|
||||
};
|
||||
|
||||
export type Deployer = {
|
||||
deployerApiUrl: string;
|
||||
deployerId: string;
|
||||
deployerLrn: string;
|
||||
}
|
||||
|
||||
export type OrganizationMember = {
|
||||
id: string;
|
||||
member: User;
|
||||
|
Loading…
Reference in New Issue
Block a user