@pradyumn-el/pollycli
Version:
pollycli lets users access the functionalities of Polly over a command line interface
318 lines (291 loc) • 9.99 kB
JavaScript
const { pollyApi } = require('./api-client');
import { organizationDetails } from './organization';
const fs = require('fs');
const axios = require('axios');
const pollyEnv = require('./env.json');
const pollyHeaders = require('./pollyheaders');
const pollymsg = require('./message');
const pollyHelpers = require('./polly-helpers');
const pollyLogs = require('./logs');
export async function getLogList(run_id, log_id) {
try {
let runUrl = `${pollyEnv.baseV2Api}/runs/${run_id}?state=project`;
const runDetails = await axios.get(runUrl, await pollyHeaders.getV2Headers());
const projectId = runDetails.data.data['attributes']['project']['id'];
if (log_id) {
await pollyLogs.getAppLogs(projectId, run_id, log_id);
} else {
const logsUrl = `/workspaces/${projectId}/runs/${run_id}/compute-app-sessions/logs`;
const logDetailsResponse = await pollyApi.get(logsUrl);
const logDetails = logDetailsResponse.data.data;
const tableData = [
['Log ID', 'Created Time']
];
const tableConfig = {
columns: {
0: {
width: 80
}
}
};
for (const logDetail of logDetails) {
tableData.push([logDetail['attributes']['log_id'], pollyHelpers.dateFormat(logDetail['attributes']['timestamp'])]);
}
pollymsg.pollyTable(tableData, 1, tableData.length, tableConfig);
}
} catch (error) {
pollymsg.pollyError(`Not able to access the logs for run ID ${run_id}`);
}
}
function isAppFileValid(appFile) {
try {
if (fs.existsSync(appFile)) {
// extracting file
let appInfo = null;
try {
appInfo = fs.readFileSync(appFile)
appInfo = JSON.parse(appInfo);
} catch (error) {
pollymsg.pollyError(`File ${appFile} not valid.`)
}
} else {
pollymsg.pollyError(`File ${appFile} does not exist.`)
}
} catch (error) {
pollymsg.pollyError(`File path ${appFile} is not valid.`)
}
return true;
}
function isAppInfoValid(appInfo) {
// checking if necessary fields are present
const minRequiredKeys = ["name", "display_name", "description", "docker_image", "cpu", "memory", "port"];
const missingFields = minRequiredKeys.filter(x => !Object.keys(appInfo).includes(x) || !appInfo[x]);
if (missingFields.length > 0) {
pollymsg.pollyError("missing required fields: " + missingFields.join(", "));
}
if (Object.keys(appInfo).includes('resource_access') &&
appInfo.resource_access != "public" && appInfo.resource_access != "private" ) {
pollymsg.pollyError(`resource_access field should be either "public" or "private"`);
}
return true;
}
function readAppFile(appFile) {
isAppFileValid(appFile);
let appInfo = fs.readFileSync(appFile);
appInfo = JSON.parse(appInfo);
isAppInfoValid(appInfo);
return appInfo;
}
export async function getAppInfo(appName) {
const organizationInfo = await organizationDetails();
const orgId = organizationInfo.org_id;
const orgSlug = organizationInfo.slug;
let baseUrl = `/organizations/${orgId}/resources/compute-apps?filter[app_name]=${orgSlug}/${appName}`;
try {
const appData = (await pollyApi.get(baseUrl)).data.data;
return appData
} catch (error) {
return []
}
}
export async function appUpdate(name, appFile, appInfo) {
if(appFile) {
isAppFileValid(appFile);
appInfo = fs.readFileSync(appFile);
appInfo = JSON.parse(appInfo);
}
const appDetails = await getAppInfo(name);
const appUrl = appDetails[0].links.self;
let patchObject = { "app_spec": appDetails[0].attributes.resource_info.app_spec };
let updates = 0;
if (appInfo.description) {
updates++;
patchObject['description'] = appInfo.description;
}
if (appInfo.cpu) {
updates++;
patchObject['app_spec']['app_container_cpu_limit'] = appInfo.cpu;
patchObject['app_spec']['app_container_cpu_requests'] = appInfo.cpu;
}
if (appInfo.memory) {
updates++;
patchObject['app_spec']['app_container_memory_requests'] = appInfo.memory;
patchObject['app_spec']['app_container_memory_limit'] = appInfo.memory;
}
if (appInfo.docker_image) {
updates++;
patchObject['app_spec']['app_image'] = appInfo.docker_image;
}
if (appInfo.port) {
updates++;
patchObject['app_spec']['port'] = parseInt(appInfo.port);
}
const patchBaseData = {
"data": {
"type": "compute-apps",
"id": appUrl.split('compute-apps/')[1],
"attributes": {
"resource_info": patchObject
}
}
}
if (updates < 1) {
pollymsg.pollyMessage("Nothing to update");
process.exit(0);
}
try {
await pollyApi.patch(appUrl, patchBaseData);
pollymsg.pollyMessage("Update complete");
} catch (error) {
pollymsg.pollyMessage("Not able to update");
}
}
export async function appAdd(appFile, appInfo) {
if(appFile) {
appInfo = readAppFile(appFile);
}
const { name, description, cpu, memory, display_name,
port, docker_image, group, resource_access } = appInfo;
const organizationInfo = await organizationDetails();
const orgId = organizationInfo.org_id;
const orgSlug = organizationInfo.slug;
const postData = {
"data": {
"type": "compute-apps",
"attributes": {
"resource_access": resource_access,
"resource_name" : name,
"resource_info": {
"app_type": "docker_app",
"display_name": display_name,
"app_spec": {
"app_image": docker_image,
"app_container_memory_requests": parseInt(memory),
"app_container_cpu_requests": cpu,
"app_container_memory_limit": parseInt(memory),
"app_container_cpu_limit": cpu,
"port": parseInt(port)
},
"description": description
}
}
}
}
try {
const val = name.split("-")
if (val.length < 2) {
pollymsg.pollyError("Name should have atleast one -");
}
const finalUrl = `/organizations/${orgId}/resources/compute-apps`
await pollyApi.post(finalUrl, postData);
const appConfigurationJson = {
component: `${orgSlug}/${name}`,
url: `/apps/${name}`,
frontend_info: {
name: display_name,
version: `elucidata/${val[0]}`,
icon_image_url: 'https://s3-us-west-2.amazonaws.com/mithoo-public-data/MithooImages/MetScape.svg',
description: description,
app_type: 'docker',
requestURL: val.splice(1).join("-"),
app_initials: "",
group: group ||"miscellaneous"
}
}
if(group) {
appConfigurationJson.frontend_info["tags"] = [group.toUpperCase()]
}
// Discover, Miscellaneous, Metabolomic Data, Screening Data, Multi-omic Data, Lipidomic Data
// Proteomic Data, Sequencing Data
const submitBody = {
"data": [{
"type": "components",
"attributes": appConfigurationJson
}]
}
const appUrl = "/components";
try {
await pollyApi.post(appUrl, submitBody);
pollymsg.pollySuccess("Added the new application");
} catch (error) {
pollymsg.pollyError("Not able to add application");
}
} catch (error) {
pollymsg.pollyError("Not able to add application");
}
}
export async function getAppsList(internalCall = false) {
const organizationInfo = await organizationDetails();
const orgId = organizationInfo.org_id;
const orgSlug = organizationInfo.slug;
let baseUrl = `/organizations/${orgId}/resources/compute-apps`;
let appsList = []
try {
let tempData = null;
do {
tempData = (await pollyApi.get(baseUrl)).data;
appsList.push(...tempData.data);
baseUrl = tempData.links.next;
} while (tempData.links && tempData.links.next);
} catch (error) {
pollymsg.pollyError("Not able to get the list of applications");
}
if (internalCall) {
const finalList = []
for (const eachApp of appsList) {
if(!/elucidata\/.+3-\d-\dr$/.test(eachApp.attributes.resource_name) && !/elucidata\/.+el-maven$/.test(eachApp.attributes.resource_name)) {
finalList.push(eachApp);
}
}
return finalList;
}
const hostedApps = [];
const displayList = [["Resource ID", "Resource Name", "Description", "Display Name", "Resource Info"]];
for (const eachApp of appsList) {
// This is shiny App regex matcher and elmaven
if(!/elucidata\/.+3-\d-\dr$/.test(eachApp.attributes.resource_name) && !/elucidata\/.+el-maven$/.test(eachApp.attributes.resource_name)) {
hostedApps.push(eachApp.attributes);
// console.log(eachApp.attributes.resource_info);
const reso = JSON.stringify({"cpu": eachApp.attributes.resource_info.app_spec.app_container_cpu_requests,
"memory": eachApp.attributes.resource_info.app_spec.app_container_memory_requests,
"port": eachApp.attributes.resource_info.app_spec.port,
"docker": eachApp.attributes.resource_info.app_spec.app_image
}, null , ' ');
displayList.push([eachApp.attributes.resource_id, eachApp.attributes.resource_name, eachApp.attributes.resource_info.description, eachApp.attributes.resource_info.display_name, reso])
}
}
const tableConfig = {
columns: {
0: {
width: 35
},
1: {
width: 30
},
2: {
width: 25
},
3: {
width: 25
},
4: {
width: 30
}
}
};
pollymsg.pollyTable(displayList, 1, displayList.length, tableConfig);
}
export function sampleAppFile() {
const appFormat = {
"name": "name of the application",
"display_name": "name to be displayed on UI",
"description": "small description about app",
"docker_image": "docker image 7 tag for the app",
"cpu": "no. of cpu cores to be assigned to the app (2,4,8) etc",
"memory": "ram to be assigned to the app (2Gi, 4Gi, 8Gi) etc",
"port": "port on which your app is running",
"group": "group of app (metabolomics, proteomics, single cell etc)",
"resource_access": "public/private",
}
console.log(appFormat);
}