diff --git a/CHANGELOG.md b/CHANGELOG.md index 081fa9d..f34b130 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ The format is based on and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.0] - 2020-02-17 + +### Changed + - Added databot deploy command. + - Added shortening of the command line arguments. + - Added get app url command. + +### Fixed + ## [0.2.8] - 2020-02-11 ### Changed diff --git a/README.md b/README.md index 923752e..713fa25 100644 --- a/README.md +++ b/README.md @@ -32,23 +32,25 @@ The client app can be accessed by running the command ```tdxcli```. Usage: tdxcli [options] Commands: - tdxcli signin [id] [secret] Sign in to tdx - tdxcli signout Sign out of tdx - tdxcli info [type] [id] Output current account info - tdxcli config Output tdx config - tdxcli list [type] List all configured aliases or secrets - tdxcli runapi Run a tdx api command - tdxcli download [filepath] Download resource - tdxcli upload Upload resource - tdxcli copyalias Makes a copy of an existing alias configuration - tdxcli modifyalias Modifies an existing alias configuration - tdxcli removealias Removes an existing alias configuration - tdxcli databot [configjson] Starts, stops or aborts a databot instance - tdxcli token Get or revoke a token for a give alias + tdxcli signin [id] [secret] Sign in to tdx + tdxcli signout Sign out of tdx + tdxcli info [type] [id] Output current account info + tdxcli config Output tdx config + tdxcli list [type] List all configured aliases or secrets + tdxcli token Get or revoke a token for a give alias + tdxcli runapi Run a tdx api command + tdxcli download [filepath] Download resource + tdxcli upload Upload resource + tdxcli copyalias Makes a copy of an existing alias configuration + tdxcli modifyalias Modifies an existing alias configuration + tdxcli removealias Removes an existing alias configuration + tdxcli databot [config] Starts, stops or aborts a databot instance + tdxcli deploy Deploys a databot stop->upload->start Options: -a, --alias Alias name [string] - -c, --credentials Input credentials in base64 [string] + -c, --credentials TDX credentials {id:"",secret:""} in base64 [string] + -t, --tdx-configs The path to the TDX config file [string] -h, --help Show help [boolean] -v, --version Show version number [boolean] ``` @@ -74,6 +76,41 @@ tdxcli list credentials ``` The output of the last command will show the credentials under the alias name ```name```. +### Tdx config file +One can also pass a custom tdx config file with param ```--tdx-config``` as follows: +```bash +tdxcli commandtoexecute ...variousparams --alias=name --tdx-config=pathtoconfig +``` + +The config file contains the tdx configuration for each defined alias as follows: +```json +{ + "nqminds": { + "tokenHref": "https://tbx.nqminds.com", + "config": { + "commandServer": "https://cmd.nqminds.com", + "ddpServer": "https://ddp.nqminds.com", + "queryServer": "https://q.nqminds.com", + "tdxServer": "https://tdx.nqminds.com", + "databotServer": "http://databot.nqminds.com", + "accessTokenTTL": 31622400 + } + }, + "nq_m": { + "tokenHref": "https://tbx.nq-m.com", + "config": { + "commandServer": "https://cmd.nq-m.com", + "ddpServer": "https://ddp.nq-m.com", + "queryServer": "https://q.nq-m.com", + "tdxServer": "https://tdx.nq-m.com", + "databotServer": "http://databot.nq-m.com", + "accessTokenTTL": 31622400 + } + } +} +``` +In the above example there are two defined aliases ```nqminds``` and ```nq-m```. + ### ```signin``` Usage ```bash @@ -89,32 +126,7 @@ tdxcli signin --alias=nqminds tdxcli signin emailorsharetokenid thesecret --alias=nq_m ``` -The aliases configurations are stored in ```config.json```: -```json - "tdxConfigs": { - "nqminds": { - "tokenHref": "https://tbx.nqminds.com", - "config": { - "commandServer": "https://cmd.nqminds.com", - "ddpServer": "https://ddp.nqminds.com", - "queryServer": "https://q.nqminds.com", - "tdxServer": "https://tdx.nqminds.com", - "accessTokenTTL": 31622400 - } - }, - "nq_m": { - "tokenHref": "https://tbx.nq-m.com", - "config": { - "commandServer": "https://cmd.nq-m.com", - "ddpServer": "https://ddp.nq-m.com", - "queryServer": "https://q.nq-m.com", - "tdxServer": "https://tdx.nq-m.com", - "accessTokenTTL": 31622400 - } - } - } -``` -An new alias can be copied from an existing alias, it can be modified or removed. +Note, the aliases configurations are stored in ```config.json``` in home folder ```.tdxcli``` of the user. A new alias can be copied from an existing alias, it can be modified or removed. The ```tdxcli signin``` allows storing access tokens and secrets for every configured alias. So, that the user can change among them by providing the ```tdxcli signin --alias=name``` option. @@ -142,6 +154,7 @@ tdxcli info tdxcli info account tdxcli info serverfolderid appid tdxcli info databotsid +tdxcli info appurl instanceid ``` The above command can also be run with the ```--alias``` option. @@ -151,6 +164,8 @@ The above command can also be run with the ```--alias``` option. ```tdxcli info databotsid``` will return all databot ids. +```tdxcli info appurl instanceid``` will return the app url for databot with instance ```instanceid```. + ## ```config``` Usage ```bash @@ -281,4 +296,18 @@ Usage tdxcli token get ``` -The command returns the access token for a the default alias or an alias passed with ```--alias```. \ No newline at end of file +The command returns the access token for a the default alias or an alias passed with ```--alias```. + +## ```deploy``` +Usage +```bash +tdxcli deploy databotid resourceid databot.json filetoupload +``` + +The above command will deploy a databot with the following steps: + +[1] Will stop a running databot instance with the databot instance id from config file ```databot.json```. + +[2] Will upload the file ```filetoupload``` to tdx resource id ```resourceid```. + +[3] Will start a new databot instance id for the databot ```databotid```. \ No newline at end of file diff --git a/config.app.json b/config.app.json index 15cbb42..41875f9 100644 --- a/config.app.json +++ b/config.app.json @@ -1,3 +1,6 @@ { - "scraperTimeout": 5000 + "scraperTimeout": 5000, + "tdxcliFoldername": ".tdxcli", + "envFilename": ".env", + "configFilename": "config.json" } \ No newline at end of file diff --git a/config.default.json b/config.default.json index bafd104..87baf65 100644 --- a/config.default.json +++ b/config.default.json @@ -6,6 +6,7 @@ "ddpServer": "https://ddp.nqminds.com", "queryServer": "https://q.nqminds.com", "tdxServer": "https://tdx.nqminds.com", + "databotServer": "http://databot.nqminds.com", "accessTokenTTL": 31622400 } }, @@ -16,6 +17,7 @@ "ddpServer": "https://ddp.nq-m.com", "queryServer": "https://q.nq-m.com", "tdxServer": "https://tdx.nq-m.com", + "databotServer": "http://databot.nq-m.com", "accessTokenTTL": 31622400 } } diff --git a/main.js b/main.js index 9c1d5f0..9b6e8e8 100644 --- a/main.js +++ b/main.js @@ -30,26 +30,39 @@ const { const CommandHandler = require("./src"); const homePath = os.homedir(); -const tdxcliConfigPath = path.join(homePath, ".tdxcli"); -const envPath = path.join(tdxcliConfigPath, ".env"); -const configPath = path.join(tdxcliConfigPath, "config.json"); +const tdxcliConfigPath = path.join(homePath, appConfig.tdxcliFoldername); +const envPath = path.join(tdxcliConfigPath, appConfig.envFilename); +const configPath = path.join(tdxcliConfigPath, appConfig.configFilename); require("dotenv").config({path: envPath}); +async function getConfigs(commandlineConfigPath, configPath) { + if (commandlineConfigPath) { + return readJsonFromFile(commandlineConfigPath); + } else { + try { + const output = await readJsonFromFile(configPath); + return output; + } catch (error) { + return {}; + } + } +} + async function argumentHandler(argv) { const command = argv._[0]; const commandProps = { alias: numberToString(argv.alias || ""), id: numberToString(argv.id || ""), secret: numberToString(argv.secret || ""), + resourceId: numberToString(argv.rid || ""), type: numberToString(argv.type || ""), command: numberToString(argv.command || ""), filepath: numberToString(argv.filepath || ""), - aliasName: numberToString(argv.aliasname || ""), - configJson: numberToString(argv.configjson || ""), - instanceId: numberToString(argv.instanceid || ""), - databotId: numberToString(argv.databotid || ""), + aliasName: numberToString(argv.name || ""), + configJson: numberToString(argv.config || ""), credentials: numberToString(argv.credentials || ""), + commandlineConfigPath: numberToString(argv["tdx-configs"] || ""), apiArgs: filterObjectByIdentifier(argv, "@"), apiArgsStringify: filterListByIdentifier(argv._.slice(1), "@"), }; @@ -61,15 +74,11 @@ async function run(commandName, commandProps) { let alias = commandProps.alias; let credentials = commandProps.credentials; const { - id, secret, type, command, filepath, - aliasName, configJson, apiArgs, apiArgsStringify, + id, secret, type, command, filepath, commandlineConfigPath, + aliasName, configJson, apiArgs, apiArgsStringify, resourceId, } = commandProps; try { - await mkdir(tdxcliConfigPath); - await createFile(envPath); - await createFile(configPath, JSON.stringify(defaultConfig, null, 2)); - if (alias === "") alias = envToAlias(process.env[TDX_CURRENT_ALIAS] || ""); if (credentials === "") credentials = process.env[TDX_CREDENTIALS] || ""; @@ -77,7 +86,11 @@ async function run(commandName, commandProps) { throw Error("No alias or wrong alias name. Only allowed [a-zA-Z0-9_]"); } - const tdxConfigs = await readJsonFromFile(configPath); + const tdxConfigs = await getConfigs(commandlineConfigPath, configPath); + if (!(alias in tdxConfigs) && (!["signin", "modifyalias"].includes(commandName))) { + throw Error(`No configuration found for alias=${alias}`); + } + const argumentSecret = {id, secret}; const configArgs = {tdxConfig: tdxConfigs[alias] || {}, timeout: appConfig.scraperTimeout}; let commandHandler; @@ -99,11 +112,20 @@ async function run(commandName, commandProps) { let output; switch (commandName) { case "signin": + // Create the .tdxcli folder in the home directory + await mkdir(tdxcliConfigPath); + await createFile(envPath); + await createFile(configPath, JSON.stringify(defaultConfig, null, 2)); + const newTdxConfigs = await readJsonFromFile(configPath); + + commandHandler.setTdxConfig(newTdxConfigs[alias]); await commandHandler.signin(argumentSecret); setEnv({key: TDX_CURRENT_ALIAS, value: aliasToEnv(alias), envPath}); // Store the argument secret - if (argumentSecret.id) setEnv({key: getSecretAliasName(alias), value: jsonToBase64(argumentSecret), envPath}); + if (argumentSecret.id) { + setEnv({key: getSecretAliasName(alias), value: jsonToBase64(argumentSecret), envPath}); + } setEnv({key: getTokenAliasName(alias), value: commandHandler.getToken(), envPath}); output = "OK"; break; @@ -113,7 +135,11 @@ async function run(commandName, commandProps) { setEnv({key: getSecretAliasName(alias), value: "", envPath}); break; case "info": - output = await commandHandler.getInfo({id, type}); + output = await commandHandler.getInfo({ + id, + type, + tdxConfig: tdxConfigs[alias] || {}, + }); break; case "config": output = tdxConfigs[alias] || {}; @@ -131,10 +157,10 @@ async function run(commandName, commandProps) { output = JSON.stringify(output, null, 2); break; case "download": - await commandHandler.download(id, filepath); + await commandHandler.download(resourceId, filepath); break; case "upload": - output = await commandHandler.upload(id, filepath); + output = await commandHandler.upload(resourceId, filepath); break; case "copyalias": await copyAliasConfig({tdxConfigs, alias, copyAliasName: aliasName, configPath}); @@ -146,7 +172,9 @@ async function run(commandName, commandProps) { output = "OK"; break; case "removealias": - if (alias === aliasName) throw Error("Can't remove the running alias."); + if (alias === aliasName) { + throw Error(`Can't remove the running alias=${alias}.`); + } await removeAliasConfig({tdxConfigs, aliasName, configPath}); output = "OK"; break; @@ -156,6 +184,10 @@ async function run(commandName, commandProps) { case "token": output = await commandHandler.runTokenCommand(command); break; + case "deploy": + output = await commandHandler.deploy({id, resourceId, configJson, filepath}); + output = JSON.stringify(output, null, 2); + break; } if (output) console.log(output); @@ -175,14 +207,15 @@ const argv = require("yargs") .command("info [type] [id]", "Output current account info", {}, argumentHandler) .command("config", "Output tdx config", {}, argumentHandler) .command("list [type]", "List all configured aliases or secrets", {}, argumentHandler) - .command("runapi ", "Run a tdx api command", {}, argumentHandler) - .command("download [filepath]", "Download resource", {}, argumentHandler) - .command("upload ", "Upload resource", {}, argumentHandler) - .command("copyalias ", "Makes a copy of an existing alias configuration", {}, argumentHandler) - .command("modifyalias ", "Modifies an existing alias configuration", {}, argumentHandler) - .command("removealias ", "Removes an existing alias configuration", {}, argumentHandler) - .command("databot [configjson]", "Starts, stops or aborts a databot instance", {}, argumentHandler) .command("token ", "Get or revoke a token for a give alias", {}, argumentHandler) + .command("runapi ", "Run a tdx api command", {}, argumentHandler) + .command("download [filepath]", "Download resource", {}, argumentHandler) + .command("upload ", "Upload resource", {}, argumentHandler) + .command("copyalias ", "Makes a copy of an existing alias configuration", {}, argumentHandler) + .command("modifyalias ", "Modifies an existing alias configuration", {}, argumentHandler) + .command("removealias ", "Removes an existing alias configuration", {}, argumentHandler) + .command("databot [config]", "Starts, stops or aborts a databot instance", {}, argumentHandler) + .command("deploy ", "Deploys a databot stop->upload->start", {}, argumentHandler) .demandCommand(1, 1, "You need at least one command to run.") .option("a", { alias: "alias", @@ -194,7 +227,14 @@ const argv = require("yargs") .option("c", { alias: "credentials", nargs: 1, - describe: "Input credentials in base64", + describe: "TDX credentials {id:\"\",secret:\"\"} in base64", + type: "string", + requiresArg: true, + }) + .option("t", { + alias: "tdx-configs", + nargs: 1, + describe: "The path to the TDX config file", type: "string", requiresArg: true, }) diff --git a/package.json b/package.json index 70a81bb..d95c4c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nqminds/nqm-tdx-terminal-cli", - "version": "0.2.8", + "version": "1.0.0", "description": "Command-line interface for accessing the TDX API", "main": "main.js", "directories": { diff --git a/src/command-handler.js b/src/command-handler.js index 23eea1c..b28f516 100644 --- a/src/command-handler.js +++ b/src/command-handler.js @@ -8,7 +8,7 @@ const {getInfo} = require("./info"); const {runDatabotCommand} = require("./databot"); const {getAliasesArray} = require("./alias"); const {getSecretAliasName} = require("./utils"); - +const {deploy} = require("./deploy"); class CommandHandler { constructor({tdxConfig, secret, token, timeout}) { @@ -23,6 +23,11 @@ class CommandHandler { return this.accessToken; } + setTdxConfig({tokenHref = {}, config = {}}) { + this.tokenHref = tokenHref; + this.config = config; + } + async connect() { const api = await connect({ config: this.config, @@ -69,9 +74,9 @@ class CommandHandler { return runApi({command, apiArgs, apiArgsStringify, api}); } - async getInfo({id, type}) { + async getInfo({id, type, tdxConfig}) { const api = await this.connect(); - return getInfo({api, type, id}); + return getInfo({api, type, id, tdxConfig}); } async download(id, filepath) { @@ -116,6 +121,11 @@ class CommandHandler { throw Error("Wrong input list type"); } } + + async deploy({id, resourceId, configJson, filepath}) { + const api = await this.connect(); + return deploy({id, resourceId, configJson, filepath, api}); + } } module.exports = CommandHandler; diff --git a/src/databot.js b/src/databot.js index 94ab954..eef9faa 100644 --- a/src/databot.js +++ b/src/databot.js @@ -1,27 +1,15 @@ const nqmUtils = require("@nqminds/nqm-core-utils"); const {readJsonFromFile} = require("./utils"); -function abortDatabot(api, id) { - return api.abortDatabotInstance(id); -} - -function stopDatabot(api, id) { - return api.stopDatabotInstance(id, nqmUtils.constants.stopDatabotInstance); -} - -function startDatabot({api, id, functionPayload}) { - return api.startDatabotInstance(id, functionPayload); -} - async function runDatabotCommand({api, command, id, configJson}) { switch (command) { case "start": const functionPayload = await readJsonFromFile(configJson); - return startDatabot({api, id, functionPayload}); + return api.startDatabotInstance(id, functionPayload); case "stop": - return stopDatabot(api, id); + return api.stopDatabotInstance(id, nqmUtils.constants.stopDatabotInstance); case "abort": - return abortDatabot(api, id); + return api.abortDatabotInstance(id); default: throw Error("Unknown databot command."); } diff --git a/src/deploy.js b/src/deploy.js new file mode 100644 index 0000000..1155ca5 --- /dev/null +++ b/src/deploy.js @@ -0,0 +1,23 @@ +const nqmUtils = require("@nqminds/nqm-core-utils"); +const {readJsonFromFile} = require("./utils"); +const {uploadResource} = require("./upload"); + +async function stop(api, id) { + try { + await api.stopDatabotInstance(id, nqmUtils.constants.stopDatabotInstance); + // eslint-disable-next-line no-empty + } catch (error) { + // console.log(error); + } +} +async function deploy({id, resourceId, configJson, filepath, api}) { + const startPayload = await readJsonFromFile(configJson); + const databotInstanceId = startPayload.id || ""; + await stop(api, databotInstanceId); + await uploadResource({id: resourceId, filepath, api}); + return api.startDatabotInstance(id, startPayload); +} + +module.exports = { + deploy, +}; diff --git a/src/info.js b/src/info.js index d4749c2..d883a11 100644 --- a/src/info.js +++ b/src/info.js @@ -20,7 +20,14 @@ async function getDatabotsIds(api) { return JSON.stringify(result, 0, 2); } -async function getInfo({api, id = "", type = ""}) { +async function getAppUrl({api, id, tdxConfig}) { + const instance = await api.getDatabotInstance(id); + const urlProtocol = tdxConfig.config.tdxServer.split(":")[0]; + const urlComponents = tdxConfig.config.tdxServer.split("."); + return `${urlProtocol}://${instance.subDomain}.${urlComponents.slice(1).join(".")}`; +} + +async function getInfo({api, id = "", type = "", tdxConfig}) { switch (type) { case "": case "account": @@ -29,6 +36,8 @@ async function getInfo({api, id = "", type = ""}) { return getServerFolderId(id); case "databotsid": return getDatabotsIds(api); + case "appurl": + return getAppUrl({api, id, tdxConfig}); default: throw Error("Unknow info type term.") } diff --git a/tests/deploy-test/databot.zip b/tests/deploy-test/databot.zip new file mode 100644 index 0000000..336852f Binary files /dev/null and b/tests/deploy-test/databot.zip differ diff --git a/tests/deploy-test/deploy.json b/tests/deploy-test/deploy.json new file mode 100644 index 0000000..b518c57 --- /dev/null +++ b/tests/deploy-test/deploy.json @@ -0,0 +1,11 @@ +{ + "inputs": {}, + "id": "ryK7fHNX8", + "name": "test-databot 1", + "overwriteExisting": "ryK7fHNX8", + "schedule": { + "always": true + }, + "shareKeyId": "mereacre@nquiringminds.com/tdx.nqminds.com", + "shareKeySecret": "" +} \ No newline at end of file