@liara/cli
Version:
The command line interface for Liara
289 lines (288 loc) • 11.3 kB
JavaScript
import ora from 'ora';
import inquirer from 'inquirer';
import { ux } from '@oclif/core';
import { Flags } from '@oclif/core';
import Command from '../../base.js';
import parseJSON from '../../utils/json-parse.js';
import { createDebugLogger } from '../../utils/output.js';
import checkRegexPattern from '../../utils/name-regex.js';
class Create extends Command {
async run() {
this.spinner = ora();
const { flags } = await this.parse(Create);
const debug = createDebugLogger(flags.debug);
await this.setGotConfig(flags);
const hostname = flags.name || (await this.promptHostname());
const type = flags.type || (await this.promptType());
const version = flags.version || (await this.promptVersion(type));
const network = flags.network
? await this.getNetwork(flags.network)
: await this.promptNetwork();
const publicNetwork = flags['public-network'] || (await this.promptPublicNetwork()) === 'y';
const planID = flags.plan || (await this.promptPlan(type));
const bundlePlanID = flags['feature-plan'] || (await this.promptBundlePlan(planID));
const sayYes = flags.yes;
if (planID === 'free' && bundlePlanID !== 'free') {
this.error(`Only "free" feature bundle plan is available for free plan.`);
}
try {
const tableData = [
{
type,
version,
hostname,
network: network === null || network === void 0 ? void 0 : network.name,
publicNetwork: publicNetwork.toString(),
},
];
const tableConfig = {
Hostname: { get: (row) => row.hostname },
Type: { get: (row) => row.type },
Version: { get: (row) => row.version },
Network: { get: (row) => row.network },
'Public Network': { get: (row) => row.publicNetwork },
};
ux.table(tableData, tableConfig, {
title: 'Database Specification',
});
if (!sayYes && (await this.promptContinue()) === 'n') {
this.log('Operation cancelled');
return;
}
await this.got.post(Create.PATH, {
json: {
hostname,
planID,
bundlePlanID,
publicNetwork,
type,
version,
network: network === null || network === void 0 ? void 0 : network._id,
},
});
this.log(`Database ${hostname} created.`);
}
catch (error) {
debug(error.message);
const err = parseJSON(error.response.body);
if (error.response && error.response.body) {
debug(JSON.stringify(error.response.body));
}
if (error.response &&
err.statusCode === 400 &&
err.message.includes('bundlePlan is not available for free plan.')) {
this.error(`The selected feature bundle plan is not available for free plan.`);
}
if (error.response && error.response.statusCode === 404) {
this.error(`Could not create the database.`);
}
if (error.response && error.response.statusCode === 409) {
this.error(`The database already exists. Please use a unique name for your database.`);
}
if (error.response &&
error.response.statusCode === 403 &&
error.response.body) {
const body = JSON.parse(error.response.body);
if (body.data.code === 'free_plan_platform') {
this.error(`The free plan is not available for ${type} database.`);
}
if (body.data.code === 'free_plan_count') {
this.error(`You are allowed to create only one database on the free plan`);
}
}
this.error(`Unable to Create Database
Please follow these steps:
1. Check your internet connection.
2. Ensure you have enough balance.
2. Try again later.
3. If you still have problems, please contact support by submitting a ticket at https://console.liara.ir/tickets.`);
}
}
async promptBundlePlan(plan) {
this.spinner.start('Loading...');
try {
const { plans } = await this.got('v1/me').json();
this.spinner.stop();
const { bundlePlan } = (await inquirer.prompt({
name: 'bundlePlan',
type: 'list',
message: 'Please select a feature bundle plan:',
choices: plan === 'free'
? [
{
name: `Plan: free, Price: 0 Tomans/Month`,
value: 'free',
},
]
: [
...Object.keys(plans.databaseBundlePlans)
.filter((bundlePlan) => {
return bundlePlan === plan;
})
.map((bundlePlan) => {
const planDetails = plans.databaseBundlePlans[bundlePlan];
return Object.keys(planDetails)
.filter((key) => planDetails[key].available)
.map((key) => {
const { displayPrice } = planDetails[key];
return {
name: `Plan: ${key}, Price: ${displayPrice.toLocaleString()} Tomans/Month`,
value: key,
};
});
})
.flat(),
],
}));
return bundlePlan;
}
catch (error) {
this.spinner.stop();
throw error;
}
}
async promptHostname() {
const { name } = (await inquirer.prompt({
name: 'name',
type: 'input',
message: 'Enter name:',
validate: (input) => input.length > 2,
}));
if (!checkRegexPattern(name)) {
this.error('Please enter a valid name for your app.');
}
return name;
}
async promptPlan(databaseType) {
this.spinner.start('Loading...');
try {
const { plans } = await this.got('v1/me').json();
this.spinner.stop();
const { plan } = (await inquirer.prompt({
name: 'plan',
type: 'list',
message: 'Please select a plan:',
choices: [
...Object.keys(plans.databases)
.filter((plan) => {
if ((plan === 'free' || plan.includes('g2')) &&
plans.databases[plan].available &&
plans.databases[plan].supports.includes(databaseType)) {
return true;
}
})
.map((plan) => {
const availablePlan = plans.databases[plan];
const ram = availablePlan.RAM.amount;
const cpu = availablePlan.CPU.amount;
const disk = availablePlan.volume;
const price = availablePlan.price * 720;
const storageClass = availablePlan.storageClass;
return {
value: plan,
name: `RAM: ${ram}${' '.repeat(5 - ram.toString().length)} GB, CPU: ${cpu}${' '.repeat(6 - cpu.toString().length)}Core, Disk: ${disk}${' '.repeat(5 - disk.toString().length) + 'GB'}${storageClass || 'SSD'}, Price: ${price.toLocaleString()}${price
? ' '.repeat(7 - Math.floor(price).toString().length) +
'Tomans/Month'
: ''}`,
};
}),
],
}));
return plan;
}
catch (error) {
this.spinner.stop();
throw error;
}
}
async promptPublicNetwork() {
const { is_public } = (await inquirer.prompt({
name: 'is_public',
type: 'input',
message: 'use public network?(y/n):',
validate: (input) => input === 'y' || input === 'n',
}));
return is_public;
}
async promptType() {
this.spinner.start('Loading...');
try {
const { databaseVersions } = await this.got('v1/me').json();
this.spinner.stop();
const { databaseType } = (await inquirer.prompt({
name: 'databaseType',
type: 'list',
message: 'Please select a database type:',
choices: [...Object.keys(databaseVersions)],
}));
return databaseType;
}
catch (error) {
this.spinner.stop();
throw error;
}
}
async promptVersion(type) {
this.spinner.start('Loading...');
try {
const { databaseVersions } = await this.got('v1/me').json();
this.spinner.stop();
const { databaseVersion } = (await inquirer.prompt({
name: 'databaseVersion',
type: 'list',
message: 'Please select a version:',
choices: [
...databaseVersions[type].map((obj) => obj.value),
],
}));
return databaseVersion;
}
catch (error) {
this.spinner.stop();
throw error;
}
}
async promptContinue() {
const { yes } = (await inquirer.prompt({
name: 'yes',
type: 'input',
message: 'continue? (y/n):',
validate: (input) => input === 'y' || input === 'n',
}));
return yes;
}
}
Create.description = 'create a new database';
Create.PATH = 'v1/databases';
Create.flags = {
...Command.flags,
name: Flags.string({
char: 'n',
description: 'name of your database',
}),
plan: Flags.string({
description: 'plan',
}),
'public-network': Flags.boolean({
description: 'use public network or not',
}),
'feature-plan': Flags.string({
description: 'feature bundle plan',
}),
type: Flags.string({
char: 't',
description: 'choose which database to use',
}),
version: Flags.string({
char: 'v',
description: 'version of the database',
}),
yes: Flags.boolean({
char: 'y',
description: 'say yes to continue prompt',
}),
network: Flags.string({
description: 'network',
}),
};
export default Create;