@pradyumn-el/pollycli
Version:
pollycli lets users access the functionalities of Polly over a command line interface
217 lines (192 loc) • 7.93 kB
JavaScript
const fs = require('fs');
const pollymsg = require('./message');
const { pollyApi } = require('./api-client');
import {
organizationDetails
} from './organization';
const envTemplate = '{"data":{"type":"environments","attributes":{"org_id":"",\
"resource_info":{"docker_repo":"","is_base_environment":true,"description":"",\
"base_docker_config":{"docker_repo":"","hash":"","tag":""},"base_kernel":"",\
"commit_hash":""},"resource_name":"","resource_access":"","is_hidden":false}}}';
function isFileValid(envFile) {
try {
if (fs.existsSync(envFile)) {
// extracting file
let envInfo = null;
try {
envInfo = fs.readFileSync(envFile)
envInfo = JSON.parse(envInfo);
} catch (error) {
pollymsg.pollyError(`File ${envFile} not valid.`)
}
} else {
pollymsg.pollyError(`File ${envFile} does not exist.`)
}
} catch (error) {
pollymsg.pollyError(`File path ${envFile} is not valid.`)
}
return true;
}
function isEnvInfoValid(envInfo) {
// checking if necessary fiels are present
const minRequiredKeys = ['name', 'description', 'docker_image', 'kernel'];
const missingFields = minRequiredKeys.filter(x => !Object.keys(envInfo).includes(x) || !envInfo[x]);
if (missingFields.length > 0) {
pollymsg.pollyError("missing required fields: " + missingFields.join(", "));
}
if (Object.keys(envInfo).includes('resource_access') &&
envInfo.resource_access != "public" && envInfo.resource_access != "private" ) {
pollymsg.pollyError(`resource_access field should be either "public" or "private"`);
}
return true;
}
function getEnvReqBody(envInfo, org_id) {
const envBody = JSON.parse(envTemplate);
envBody.data.attributes.org_id = org_id;
envBody.data.attributes.resource_name = envInfo.name;
envBody.data.attributes.resource_info.description = envInfo.description;
envBody.data.attributes.resource_info.docker_repo = envInfo.docker_image.split("/")[1].split(":")[0];
envBody.data.attributes.resource_info.base_kernel = envInfo.kernel;
envBody.data.attributes.resource_info.commit_hash = envInfo.commit_hash;
envBody.data.attributes.resource_access = !!envInfo.resource_access ? envInfo.resource_access : "private";
if(!!envInfo.base_env) {
envBody.data.attributes.resource_info.base_docker_config = envInfo.base_env;
envBody.data.attributes.resource_info.is_base_is_base_environment = false;
}
return envBody;
}
async function getDockerRepoInfo(org_id, docker_repo) {
try {
const dockerUrl = `/organizations/${org_id}/resources/dockers`+
`?filter[scope]=global&filter[resource_access]=public`+
`&filter[app_name]=${docker_repo}`;
const dockerRes = await pollyApi.get(dockerUrl);
return dockerRes.data.data[0];
} catch(e) {
pollymsg.pollyError(`docker image ${docker_repo} not found`)
}
}
async function getNotebookEnvInfo(org_id, env_name) {
try {
const envUrl = `/organizations/${org_id}/resources/environments`+
`?filter[app_name]=${env_name}`;
const envRes = await pollyApi.get(envUrl);
return envRes.data.data[0];
} catch(e) {
pollymsg.pollyError(`notebook environment ${env_name} not found`);
}
}
async function getDockerCommitHash(org_id, docker_image) {
const docker_repo = docker_image.split(":")[0];
const docker_tag = docker_image.split(":")[1];
const docker_info = await getDockerRepoInfo(org_id, docker_repo);
const commitUrl = `/organizations/${org_id}/resources/dockers`+
`/${docker_info.id}/commits`;
const commit_res = await pollyApi.get(commitUrl);
// loop through docker all the commits and match the tag
for(let commit of commit_res.data.data) {
if(commit.id == docker_tag) {
// return the latest hash incase multiple hashes exist for same tag
return Object.keys(commit.attributes.commit_info).pop().split("sha256:")[1];
}
}
pollymsg.pollyError(`tag ${docker_tag} for image ${docker_repo} not present`);
}
export async function createEnvironment(envFile, envInfo) {
try {
if(envFile && isFileValid(envFile)) {
envInfo = fs.readFileSync(envFile)
envInfo = JSON.parse(envInfo);
}
isEnvInfoValid(envInfo);
const { org_id } = await organizationDetails();
envInfo.commit_hash = await getDockerCommitHash(org_id, envInfo.docker_image);
if(!!envInfo.base_env) {
const base_env_info = await getNotebookEnvInfo(org_id, envInfo.base_env);
envInfo.base_env = {
"docker_repo": base_env_info.attributes.resource_info.docker_repo,
"hash": base_env_info.attributes.resource_info.latest_commits.pop(),
};
}
const envBody = getEnvReqBody(envInfo, org_id);
const envUrl = `/organizations/${org_id}/resources/environments`
await pollyApi.post(envUrl, envBody);
pollymsg.pollySuccess(`${envInfo.name} notebook environment created`);
} catch(e) {
if(e.response.status == 409) {
return pollymsg.pollyError(`Name ${envInfo.name} already in use`);
}
pollymsg.pollyError("Unable to create notebook environment");
}
}
export async function updateEnvironment(env_name, envInfo) {
try {
if(!env_name) {
pollymsg.pollyError(`missing required argument --env-name`);
}
const { org_id, slug } = await organizationDetails();
const nb_env = await getNotebookEnvInfo(org_id, slug+"/"+env_name);
const envUrl = `/organizations/${org_id}/resources/environments/${nb_env.id}`;
const reqBody = {
"data": {
"type": "environments",
"id": nb_env.id,
"attributes": {
"resource_info": {}
}
}
}
if(!!envInfo.tag) {
let docker_repo = nb_env.attributes.resource_info.docker_repo;
docker_repo = docker_repo.split(":")[0] + ":" + envInfo.tag;
const commit_hash = await getDockerCommitHash(org_id, docker_repo);
reqBody.data.attributes.resource_info.commit_hash = commit_hash;
}
if(!!envInfo.description) {
reqBody.data.attributes.resource_info.description = envInfo.description;
}
if(!!envInfo.resource_access &&
(envInfo.resource_access == "public" || envInfo.resource_access == "private")) {
reqBody.data.attributes.resource_access = envInfo.resource_access;
}
if(Object.keys(reqBody.data.attributes.resource_info).length === 0 &&
!reqBody.data.attributes.resource_access) {
return pollymsg.pollySuccess(`${env_name} notebook environment unchanged`);
}
await pollyApi.patch(envUrl, reqBody);
pollymsg.pollySuccess(`${env_name} notebook environment updated`);
} catch(e) {
pollymsg.pollyError("unable to update notebook environment");
}
}
export async function removeEnvironment(env_name) {
try {
const { org_id, slug } = await organizationDetails();
const nb_env = await getNotebookEnvInfo(org_id, slug+"/"+env_name);
const envUrl = `/organizations/${org_id}/resources/environments/${nb_env.id}`
const reqBody = {
"data": {
"type": "environments",
"id": nb_env.id,
"attributes": {
"is_hidden": true
}
}
}
await pollyApi.patch(envUrl, reqBody);
pollymsg.pollySuccess(`${env_name} notebook environment removed`);
} catch(e) {
pollymsg.pollyError("unable to remove notebook environment");
}
}
export function sampleEnvFormat() {
const envFormat = {
"name": "Display name of you environment",
"description": "small description of you environment",
"docker_image": "docker image with org slug and tag",
"kernel": "base kernel of your image (py2, py3, r, sos)",
"resource_access": "(optional) public/private",
"base_env": "(optional) name of base docker env for building this image"
}
console.log(envFormat);
}