@pradyumn-el/pollycli
Version:
pollycli lets users access the functionalities of Polly over a command line interface
371 lines (345 loc) • 12.9 kB
JavaScript
const axios = require('axios');
const pollyEnv = require('./env.json');
const pollyHeaders = require('./pollyheaders');
const pollymsg = require('./message');
const chalk = require('chalk');
const fs = require('fs');
const path = require('path')
const tar = require('tar');
const s3 = require('@elucidatainc/s3-node-client');
const cliProgress = require('cli-progress');
const git = require('simple-git');
import {
submitJob,
jobStatus
} from './job';
import {
getJobLogs
} from './logs';
import {
organizationDetails
} from './organization';
import {
getAllDockerRepos,
getAllDockerCommits,
createADockerRepo
} from './docker';
const { pollyApi } = require('./api-client');
const rmdir = require('rimraf');
const ETA_BUFFER = 10000
const PROGRESS_FORMAT = 'progress [{bar}] {percentage}% | {fileSizeProgress} | ETA: {eta}s | time elapsed: {duration}s';
const helper = require('./helper-functions');
export async function generateStudioComponentTemplate(templateType, componentName) {
const orgDetails = await organizationDetails();
const orgSlug = orgDetails.slug;
const orgOrgId = orgDetails.org_id;
const ownDockers = await getAllDockerRepos(orgOrgId, 'own');
for (const ownDocker of ownDockers) {
if (ownDocker.attributes.resource_name == `${orgSlug}/${componentName}`) {
pollymsg.pollyError(`studio component with the name ${componentName} already exists`);
}
}
let gitBranches = await axios.get('https://api.github.com/repos/ElucidataInc/studio-templates/branches');
const allowedTemplate = []
for (const eachBranches of gitBranches.data) {
if (eachBranches.name != 'master') {
allowedTemplate.push(eachBranches.name)
}
}
if (allowedTemplate.indexOf(templateType) < 0) {
pollymsg.pollyError('We not support this type. If you would like to add this please do get in touch with out customer success team');
}
try {
await git().silent(true)
.clone('https://github.com/ElucidataInc/studio-templates.git', componentName, ['--single-branch', '--branch', templateType]);
} catch (error) {
pollymsg.pollyError('Not able to get the template');
}
const removedGit = await (async () => {
return new Promise((resolve, reject) => {
rmdir(`${path.resolve(".")}/${componentName}/.git`, function (error) {
if (error) {
resolve(false);
} else {
resolve(true);
}
});
});
})();
const studioConfig = JSON.stringify({
dockerName: `${pollyEnv.dockerDomain}/${orgSlug}/${componentName}`,
componentName: `${componentName}`
},null, ' ');
fs.writeFileSync(`${path.resolve(".")}/${componentName}/.studioConfig.json`, studioConfig);
const createStatus = await createADockerRepo(componentName, `This is a docker for studio component ${componentName}`, false, true);
if (createStatus) {
pollymsg.pollyMessage(`Studio component ${componentName} generated`);
} else {
if (fs.existsSync(`${path.resolve(".")}/${componentName}`)) {
await (async () => {
return new Promise((resolve, reject) => {
rmdir(`${path.resolve(".")}/${componentName}`, function (error) {
if (error) {
resolve(false);
} else {
resolve(true);
}
});
});
})();
}
pollymsg.pollyError("Not able to create component please contact the customer success team");
}
}
export async function componentBuildStatus(buildID) {
let buff = Buffer.from(buildID, 'base64');
let extractedSecret = buff.toString('ascii');
const jsonData = JSON.parse(extractedSecret);
const statusData = await jobStatus(jsonData.data.project_id, jsonData.data.job_id, true);
let dataArray = [
['Build state'],
[statusData[0].attributes.state]
]
pollymsg.pollyTable(dataArray);
await getJobLogs(jsonData.data.project_id, jsonData.data.job_id);
}
export async function buildStudioComponent(localComponentPath = '.') {
const organizationInfo = await organizationDetails();
const orgSlug = organizationInfo.slug;
const orgId = organizationInfo.org_id;
const configFilePath = `${path.resolve(".")}/.studioConfig.json`;
if (!fs.existsSync(configFilePath)) {
pollymsg.pollyError("Seems to be not building from the studio component directory");
}
const dockerNameWithOutTag = JSON.parse(fs.readFileSync(configFilePath)).dockerName;
const dockerName = dockerNameWithOutTag.split(`${pollyEnv.dockerDomain}/${orgSlug}/`)[1];
let foundDocker = false;
const allDockerImages = await getAllDockerRepos(orgId);
let dockerCommits = [];
for (const dockers of allDockerImages) {
if (`${pollyEnv.dockerDomain}/${dockers.attributes.resource_name}` == `${pollyEnv.dockerDomain}/${orgSlug}/${dockerName}`) {
foundDocker = true;
dockerCommits = await getAllDockerCommits(orgId, dockers.attributes.resource_id,'public' == dockers.attributes.resource_access, true);
break;
}
}
if (!foundDocker) {
pollymsg.pollyError("Seems like new component creation was not successful or the component is deleted");
}
let dockerTag = "V1";
if (dockerCommits.length != 0) {
const dockerTagAppender = "V";
let allTags = [];
for (const dockerCommit of dockerCommits) {
allTags.push(parseInt(dockerCommit.attributes.tag_name.split(dockerTagAppender)[1]));
}
dockerTag = `V${Math.max(...allTags) + 1}`;
}
const dockerNameWithTag = `${dockerName}:${dockerTag}`
let localPath = `${path.resolve(localComponentPath)}`;
if (!localPath.endsWith('/')) {
localPath = `${localPath}/`;
}
if (!fs.existsSync(`${localPath}Dockerfile`)) {
pollymsg.pollyError('Not able to find the Dockerfile in the given path');
}
const compressedFileName = `/tmp/${Math.random().toString(36).slice(2)}.tar.gz`
const isCompressed = await compressStudioCode(compressedFileName);
if (!isCompressed) {
pollymsg.pollyError('Not able to package the folder');
}
const workspaceId = await getWorkSpaceId();
if (!workspaceId) {
pollymsg.pollyError('Not able to get the workspaceId to bundle the component');
}
let fileName = dockerNameWithTag.replace(/\//g, '_');
fileName = fileName.replace(/\:/g, '-');
fileName = fileName.replace(/\./g, '-');
fileName = `polly://${fileName}.tar.gz`;
if (!fs.existsSync(compressedFileName)) {
pollymsg.pollyError('Packaging of code did not happen');
}
const fileUploadStatus = await uploadFiles(workspaceId, compressedFileName, fileName, true);
if (!fileUploadStatus) {
pollymsg.pollyError('Error in uploading the packaged file');
} else {
pollymsg.pollyMessage('Studio component is uploaded. The build process will start soon.')
}
const jobTemplate = {
"cpu": "1",
"memory": "2Gi",
"image": `${pollyEnv.dockerDomain}/elucidata/polly-docker-builder`,
"tag": "latest",
"name": `Docker build for ${dockerNameWithTag}`,
"env": {
"POLLY_PATH_TO_DOCKER_TAR_GZ": fileName,
"POLLY_DOMAIN_DOCKER_NAME_WITH_TAG": `${pollyEnv.dockerDomain}/${orgSlug}/${dockerNameWithTag}`,
"STUDIO_BUILD": "TRUE"
},
"secret_env": {
"POLLY_ENV": pollyEnv.dockerDomain,
}
}
const jobFile = `/tmp/${Math.random().toString(36).slice(2)}.json`
const jobFileData = JSON.stringify(jobTemplate, null, 2);
fs.writeFileSync(jobFile, jobFileData);
if (!fs.existsSync(jobFile)) {
pollymsg.pollyError('Not able to start the component build');
}
const submittedData = await submitJob(workspaceId.toString(), jobFile, true);
const buff = new Buffer(JSON.stringify(submittedData));
const base64data = buff.toString('base64');
pollymsg.pollyMessage('\nStudio component building have started. \n \n');
let dataArray = [
['Build ID'],
[base64data]
]
// pollymsg.pollyTable(dataArray);
pollymsg.pollyMessage(chalk.white(`To get the status and logs run:
${chalk.italic('polly studio component-build-status --id ')} ${chalk.italic(base64data)}`))
}
async function getWorkSpaceId() {
const workspaceName = 'studio_component_builder_default'
const workspaceDesc = 'A workspace in which all the build code for component is kept'
const workspaceUrl = `${pollyEnv.baseV2Api}`
const workspaceDataComplete = [];
let requestUrl = ''
let workspaceData = null;
let workSpaceFound = false;
let workSpaceId = null;
requestUrl = "/workspaces?page%5Bsize%5D=100";
do {
workspaceData = await pollyApi.get(requestUrl);
workspaceDataComplete.push(...workspaceData.data.data)
if (workspaceData.data.links.next) {
requestUrl = `${workspaceData.data.links.next}`
}
} while (workspaceData.data.links.next);
for (const eachWorkspace of workspaceDataComplete) {
if (eachWorkspace.attributes.name == workspaceName) {
workSpaceFound = true;
workSpaceId = eachWorkspace.attributes.id
break;
}
}
if (!workSpaceFound) {
const workSpacePayload = {
"data": {
"type": "workspaces",
"attributes": {
"name": workspaceName,
"description": workspaceDesc,
"project_property": {
"type": "workspaces",
"labels": ""
}
}
}
}
try {
const createdData = await pollyApi.post("/workspaces", workSpacePayload, await pollyHeaders.getV2Headers());
workSpaceId = createdData.data.data.attributes.id;
} catch (error) {
pollymsg.pollyError('Not able to create workspaces for build purpose');
}
}
return workSpaceId;
}
async function compressStudioCode(compressedFileName) {
return new Promise((resolve, rejects) => {
tar.c({
gzip: true,
file: compressedFileName
},
['.']
).then(_ => {
resolve(true);
}).catch((err) => {
resolve(false);
})
});
}
async function getPollyProjectsCreds(workspaceId) {
if (!workspaceId) {
pollymsg.pollyError("No workspace ID given.")
}
const projectFilesCredsUrl = `/projects/${workspaceId}/credentials/files`;
try {
const credsResult = await pollyApi.get(projectFilesCredsUrl, await pollyHeaders.getV2Headers());
const projectCreds = credsResult.data.data[0].attributes.credentials;
return projectCreds;
} catch (error) {
pollymsg.pollyError(`Not enough permission over the workspace ${workspaceId}`)
}
}
async function uploadFiles(workspaceId, sourcePath, destinationPath, internalCall = false) {
return new Promise(async (resolve, reject) => {
const projectCreds = await getPollyProjectsCreds(workspaceId);
const client = s3.createClient({
s3Options: {
accessKeyId: projectCreds.AccessKeyId,
secretAccessKey: projectCreds.SecretAccessKey,
sessionToken: projectCreds.SessionToken,
region: process.env.POLLY_ENV ? pollyEnv.region[process.env.POLLY_ENV] : pollyEnv.region["prod"]
},
});
let localPath = path.resolve(sourcePath);
if (!fs.existsSync(localPath)) {
pollymsg.pollyError("Local path does not exist");
}
var params = {
localFile: localPath,
s3Params: {
Bucket: projectCreds.bucket,
Key: `${workspaceId}/${destinationPath.split("polly://")[1]}`,
},
};
let ext = path.extname(sourcePath);
if (!destinationPath.split("polly://")[1].endsWith(ext)) {
pollymsg.pollyError("remote destination should not be a directory for copy operation");
}
if (destinationPath.split("polly://")[1].trim() == '') {
pollymsg.pollyError("remote destination should not be a directory for copy operation");
}
const uploadSync = client.uploadFile(params);
if (!internalCall) {
var uploadBar = new cliProgress.SingleBar({
format: PROGRESS_FORMAT,
etaBuffer: ETA_BUFFER
}, cliProgress.Presets.shades_classic);
}
let totalData = 0;
let sizeProgress = 'NA';
let doneOnce = true;
uploadSync.on('error', function (err) {
if (!internalCall) {
uploadBar.stop();
}
resolve(false);
});
uploadSync.on('progress', function (data) {
if (!internalCall && uploadSync.progressTotal > 0) {
if (totalData != uploadSync.progressTotal && uploadSync.progressTotal > 0) {
if (doneOnce) {
uploadBar.start(uploadSync.progressTotal, uploadSync.progressAmount, {
fileSizeProgress: sizeProgress
});
doneOnce = false;
}
uploadBar.setTotal(uploadSync.progressTotal)
totalData = uploadSync.progressTotal;
}
sizeProgress = `${helper.formatBytes(uploadSync.progressAmount)}/${helper.formatBytes(uploadSync.progressTotal)}`
uploadBar.update(uploadSync.progressAmount, {
fileSizeProgress: sizeProgress
});
}
});
uploadSync.on('end', function () {
if (!internalCall) {
uploadBar.stop();
}
resolve(true);
});
});
}