diff --git a/.gitignore b/.gitignore index 8f13b3a..4cae0f4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,8 @@ lerna-debug.log* *.variables.yaml .npmrc test.json - +automatiqalCLI-example-16.yaml +automatiqal-sample.yaml # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/README.md b/README.md index 4509f40..dd3b051 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Automatiqal CLI +[![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/T6T0148ZP) + `Automatiqal CLI` is a `NodeJS` wrapper around [Automatiqal](https://github.com/informatiqal/automatiqal) package that allows automating `Qlik Sense` administration/deployment tasks by describing them in `yaml`/`json` files. As the name suggests `Automatiqal CLI` is a command line/terminal tool. @@ -16,11 +18,12 @@ Install as global module: `$ automatiqal --help` `$ automatiqal` - ``` + ```text --file -f Location of the file, containing the run book data --variables, -v Location of the variable file (if needed) --json Indicates that the run book file is in JSON format --output, -o Saves the result in the provided path + --connect, -c Test the connectivity. No tasks are ran --sample, -s Generate sample run book and variables files in the current folder --help, -h Shows this message ``` @@ -48,8 +51,11 @@ Have a look at the [examples folder](https://github.com/Informatiqal/automatiqal Have a look at `Automatiqal` package [wiki pages](https://github.com/Informatiqal/automatiqal/wiki) on how to structure the file and list of operations. More information will be added soon here as well +## Schema + +Great little addition is the availability of [YAML schema](https://github.com/Informatiqal/automatiqal-cli-schema). The schema greatly helps when writing runbooks. Please refer to the schema's repo on how to use it (in `VSCode`) + ## Limitations - no way to test the runbook (aka `dry run`) [automatiqal #6](https://github.com/Informatiqal/automatiqal/issues/6) - handling large files. In the current implementation all external files are read before the runbook is executed. This can be a problem when uploading large files [automatiqal #4](https://github.com/Informatiqal/automatiqal/issues/4) -- schema. It will be very helpful to have dedicated runbook schema. Such schema will ease the writing of the runbook by suggesting operation names, tasks parameters, task detail parameters and indicate their type and if they are optional. Its being worked on schema. When its in a bit better shape it will be published in its own repository with instructions how to be used diff --git a/check-version.js b/check-version.js new file mode 100644 index 0000000..69a42ca --- /dev/null +++ b/check-version.js @@ -0,0 +1,12 @@ +import semver from "semver"; +import { readFileSync } from "fs"; + +const { engines } = JSON.parse(readFileSync("./package.json")); + +const version = engines.node; +if (!semver.satisfies(process.version, version)) { + console.log( + `Required node version ${version} not satisfied with current version ${process.version}.` + ); + process.exit(1); +} diff --git a/package.json b/package.json index a391470..2dd5122 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "automatiqal-cli", - "version": "0.0.15", + "version": "0.1.0", "description": "CLI wrapper for automatiqal", "author": { "email": "info@informatiqal.com", diff --git a/rollup.config.js b/rollup.config.js index 7547c09..5821862 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,14 +1,17 @@ -import typescript from "rollup-plugin-typescript2"; +import typescript from "@rollup/plugin-typescript"; import del from "rollup-plugin-delete"; -import commonjs from "@rollup/plugin-commonjs"; -import pkg from "./package.json"; +import replace from "@rollup/plugin-replace"; +import { readFileSync } from "fs"; + +const pkg = JSON.parse(readFileSync("./package.json")); export default { input: "src/index.ts", output: [ { + sourcemap: true, file: pkg.main, - format: "cjs", + format: "es", }, ], external: [ @@ -18,12 +21,15 @@ export default { "https", ], plugins: [ - commonjs(), del({ targets: "dist/*", }), - typescript({ - typescript: require("typescript"), + typescript(), + replace({ + values: { + __VERSION: pkg.version, + }, + preventAssignment: true, }), ], }; diff --git a/runbook-examples/automatiqalCLI-example-1.yaml b/runbook-examples/automatiqalCLI-example-1.yaml index 5639bff..51bca18 100644 --- a/runbook-examples/automatiqalCLI-example-1.yaml +++ b/runbook-examples/automatiqalCLI-example-1.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + # This runbook will: # import qvf, create stream, # publish the imported app to the stream (under new name) diff --git a/runbook-examples/automatiqalCLI-example-10.yaml b/runbook-examples/automatiqalCLI-example-10.yaml index 93f7128..e565e35 100644 --- a/runbook-examples/automatiqalCLI-example-10.yaml +++ b/runbook-examples/automatiqalCLI-example-10.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: Content library operations edition: windows environment: @@ -48,6 +50,9 @@ tasks: operation: contentLibrary.exportMany location: ${export_location} filter: name eq 'Temp content library' + details: + files: + - "" - name: Remove multiple files to content library filter: name eq 'Temp content library' operation: contentLibrary.removeFileMany diff --git a/runbook-examples/automatiqalCLI-example-11.yaml b/runbook-examples/automatiqalCLI-example-11.yaml index cdd43a4..8e68ecf 100644 --- a/runbook-examples/automatiqalCLI-example-11.yaml +++ b/runbook-examples/automatiqalCLI-example-11.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: Virtual Proxy operations edition: windows environment: diff --git a/runbook-examples/automatiqalCLI-example-12.yaml b/runbook-examples/automatiqalCLI-example-12.yaml index 5122351..cf76dae 100644 --- a/runbook-examples/automatiqalCLI-example-12.yaml +++ b/runbook-examples/automatiqalCLI-example-12.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: Tasks operations edition: windows environment: diff --git a/runbook-examples/automatiqalCLI-example-13.yaml b/runbook-examples/automatiqalCLI-example-13.yaml index c8dda9d..f9ea472 100644 --- a/runbook-examples/automatiqalCLI-example-13.yaml +++ b/runbook-examples/automatiqalCLI-example-13.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: External Task operations edition: windows environment: diff --git a/runbook-examples/automatiqalCLI-example-14.yaml b/runbook-examples/automatiqalCLI-example-14.yaml index 442a8d9..10f74f5 100644 --- a/runbook-examples/automatiqalCLI-example-14.yaml +++ b/runbook-examples/automatiqalCLI-example-14.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: Export operations edition: windows environment: diff --git a/runbook-examples/automatiqalCLI-example-15.yaml b/runbook-examples/automatiqalCLI-example-15.yaml index e9b6dfa..b6db349 100644 --- a/runbook-examples/automatiqalCLI-example-15.yaml +++ b/runbook-examples/automatiqalCLI-example-15.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: Sample run book edition: windows environment: diff --git a/runbook-examples/automatiqalCLI-example-2.yaml b/runbook-examples/automatiqalCLI-example-2.yaml index c022fda..24f5999 100644 --- a/runbook-examples/automatiqalCLI-example-2.yaml +++ b/runbook-examples/automatiqalCLI-example-2.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + # This runbook will: # create stream, create new custom properties and tags, # will update the stream by adding diff --git a/runbook-examples/automatiqalCLI-example-3.yaml b/runbook-examples/automatiqalCLI-example-3.yaml index a703f06..32d89f7 100644 --- a/runbook-examples/automatiqalCLI-example-3.yaml +++ b/runbook-examples/automatiqalCLI-example-3.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: Certificate operations edition: windows environment: @@ -13,7 +15,7 @@ tasks: description: > Export new set of certificates for the listed machineNames - operation: certificate.export + operation: certificate.generate details: machineNames: - ${host} diff --git a/runbook-examples/automatiqalCLI-example-4.yaml b/runbook-examples/automatiqalCLI-example-4.yaml index 0e709c1..44260f0 100644 --- a/runbook-examples/automatiqalCLI-example-4.yaml +++ b/runbook-examples/automatiqalCLI-example-4.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: Node operations edition: windows environment: diff --git a/runbook-examples/automatiqalCLI-example-5.yaml b/runbook-examples/automatiqalCLI-example-5.yaml index 475fb84..513f738 100644 --- a/runbook-examples/automatiqalCLI-example-5.yaml +++ b/runbook-examples/automatiqalCLI-example-5.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: User operations edition: windows environment: diff --git a/runbook-examples/automatiqalCLI-example-6.yaml b/runbook-examples/automatiqalCLI-example-6.yaml index 54d721d..1da42d0 100644 --- a/runbook-examples/automatiqalCLI-example-6.yaml +++ b/runbook-examples/automatiqalCLI-example-6.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: Tasks operations edition: windows environment: diff --git a/runbook-examples/automatiqalCLI-example-7.yaml b/runbook-examples/automatiqalCLI-example-7.yaml index 5b6755f..26eb917 100644 --- a/runbook-examples/automatiqalCLI-example-7.yaml +++ b/runbook-examples/automatiqalCLI-example-7.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: System (security and license) rules operations edition: windows environment: diff --git a/runbook-examples/automatiqalCLI-example-8.yaml b/runbook-examples/automatiqalCLI-example-8.yaml index 12ebedf..6dc0e69 100644 --- a/runbook-examples/automatiqalCLI-example-8.yaml +++ b/runbook-examples/automatiqalCLI-example-8.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: Data connections operations edition: windows environment: diff --git a/runbook-examples/automatiqalCLI-example-9.yaml b/runbook-examples/automatiqalCLI-example-9.yaml index e8d84b3..06c9b78 100644 --- a/runbook-examples/automatiqalCLI-example-9.yaml +++ b/runbook-examples/automatiqalCLI-example-9.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + name: Extension operations edition: windows environment: diff --git a/src/lib/CLI.ts b/src/lib/CLI.ts index f6cfb9b..bd3eca4 100644 --- a/src/lib/CLI.ts +++ b/src/lib/CLI.ts @@ -1,6 +1,6 @@ import { readFileSync, writeFileSync } from "fs"; -import * as https from "https"; -import * as yaml from "js-yaml"; +import { Agent } from "https"; +import { load as yamlLoad } from "js-yaml"; import { Automatiqal } from "automatiqal"; import { IRunBook } from "automatiqal/dist/RunBook/RunBook.interfaces"; @@ -20,7 +20,10 @@ export class AutomatiqalCLI { this.result = []; try { - this.rawRunBook = readFileSync(this.argv.file, "utf8").toString(); + this.rawRunBook = readFileSync( + this.argv.file || this.argv.f, + "utf8" + ).toString(); } catch (e) { console.log(`\u274C ERROR 1000: while reading the runbook file`); console.log(e.message); @@ -30,9 +33,14 @@ export class AutomatiqalCLI { // match all strings in between ${ and } const variables = this.rawRunBook.match(/(?<=\${)(.*?)(?=})/g); - if (variables.length > 0 && !this.argv.v && !this.argv.variables) { + if ( + variables && + variables.length > 0 && + !this.argv.v && + !this.argv.variables + ) { console.log( - `\u274C ERROR 1012: Variable(s) declaration found but no variables file was provided ` + `\u274C ERROR 1012: Variable(s) declaration found but no variables file was provided` ); console.log(""); console.log("Variables found:"); @@ -40,9 +48,18 @@ export class AutomatiqalCLI { process.exit(1); } - this.replaceVariables(); + if (this.argv.var || this.argv.v || this.argv.variables) + this.replaceVariables(); + this.runbookSet(); - this.prepareCertificates(); + + if ((this.runBook.environment.authentication as any).cert) { + this.prepareCertificates(); + } else { + this.httpsAgent = new Agent({ + rejectUnauthorized: false, + }); + } // no need to read any files if only connection is being tested if (!this.argv.c && !this.argv.connect) this.readBuffers(); @@ -83,7 +100,7 @@ export class AutomatiqalCLI { // if the config is yaml if (!this.argv.json) { try { - this.runBook = yaml.load(this.rawRunBook); + this.runBook = yamlLoad(this.rawRunBook) as IRunBook; } catch (e) { console.log(`\u274C ERROR 1003: while parsing the yaml file`); console.log(e.message); @@ -125,13 +142,18 @@ export class AutomatiqalCLI { process.exit(1); } try { - _this.writeExports(b.data, b.task.location); + _this.writeExports( + Array.isArray(b.data) ? b.data : [b.data], + b.task.location + ); - if (b.data && b.data.length > 0) { - b.data = b.data.map((r: any) => { - if (r.file) r.file = "BINARY CONTENT REPLACED!"; - return r; - }); + if (Array.isArray(b.data)) { + if (b.data && b.data.length > 0) { + b.data = b.data.map((r: any) => { + if (r.file) r.file = "BINARY CONTENT REPLACED!"; + return r; + }); + } } } catch (e) { console.log( @@ -163,7 +185,10 @@ export class AutomatiqalCLI { */ private writeOut() { try { - writeFileSync(this.argv.output, JSON.stringify(this.result, null, 4)); + writeFileSync( + this.argv.output || this.argv.o, + JSON.stringify(this.result, null, 4) + ); } catch (e) { console.log(`\u274C ERROR 1005: while writing the output file`); console.log(e.message); @@ -176,28 +201,28 @@ export class AutomatiqalCLI { * read the certificates and prepare the httpsAgent */ private prepareCertificates() { - if (this.runBook.environment.authentication.cert) { + { let cert: Buffer; let key: Buffer; try { - cert = readFileSync(this.runBook.environment.authentication.cert); - key = readFileSync(this.runBook.environment.authentication.key); + cert = readFileSync( + (this.runBook.environment.authentication as any).cert + ); + key = readFileSync( + (this.runBook.environment.authentication as any).key + ); } catch (e) { console.log(`\u274C ERROR 1006: reding certificates`); console.log(e.message); process.exit(1); } - this.httpsAgent = new https.Agent({ + this.httpsAgent = new Agent({ rejectUnauthorized: false, cert: cert, key: key, }); - } else { - this.httpsAgent = new https.Agent({ - rejectUnauthorized: false, - }); } } @@ -244,31 +269,29 @@ export class AutomatiqalCLI { * @description replace all runbook variables with their content */ private replaceVariables() { - if (this.argv.var || this.argv.v || this.argv.variables) { - let variablesFileLocation = - this.argv.var || this.argv.v || this.argv.variables; - let rawVariables: string[]; + let variablesFileLocation = + this.argv.var || this.argv.v || this.argv.variables; + let rawVariables: string[]; - try { - rawVariables = readFileSync(variablesFileLocation) - .toString() - .split(/\r?\n/); - } catch (e) { - console.log( - `\u274C ERROR 1008: reading variables file "${variablesFileLocation}"` - ); - console.log(e.message); - process.exit(1); - } + try { + rawVariables = readFileSync(variablesFileLocation) + .toString() + .split(/\r?\n/); + } catch (e) { + console.log( + `\u274C ERROR 1008: reading variables file "${variablesFileLocation}"` + ); + console.log(e.message); + process.exit(1); + } - for (let line of rawVariables) { - let [varName, varContent] = line.split("="); + for (let line of rawVariables) { + let [varName, varContent] = line.split("="); - const v = "\\$\\{" + varName + "\\}"; - const re = new RegExp(v, "g"); + const v = "\\$\\{" + varName + "\\}"; + const re = new RegExp(v, "g"); - this.rawRunBook = this.rawRunBook.replace(re, varContent); - } + this.rawRunBook = this.rawRunBook.replace(re, varContent); } } diff --git a/src/lib/help.ts b/src/lib/help.ts index 271f313..4b3b655 100644 --- a/src/lib/help.ts +++ b/src/lib/help.ts @@ -1,4 +1,4 @@ -const pkg = { version: "0.0.14" }; +const pkg = { version: "__VERSION" }; export function printHelp() { const messages = [ diff --git a/src/lib/interfaces.ts b/src/lib/interfaces.ts index 495afe8..9be6add 100644 --- a/src/lib/interfaces.ts +++ b/src/lib/interfaces.ts @@ -6,7 +6,7 @@ export interface IArguments { sample?: string; help?: boolean; h?: boolean; - o?: boolean; + o?: string; s?: string; var?: string; variables?: string; diff --git a/src/lib/sample.ts b/src/lib/sample.ts index 7fc3bfe..6766b76 100644 --- a/src/lib/sample.ts +++ b/src/lib/sample.ts @@ -1,5 +1,5 @@ import { writeFileSync } from "fs"; -import yaml from "js-yaml"; +import { dump } from "js-yaml"; export function generateSample(format: string) { if (!format) format = "yaml"; @@ -77,18 +77,22 @@ user_name=SOME-USER`; try { writeFileSync( ".\\automatiqal-sample.yaml", - yaml.dump(qseowSample, null, 4) + `# yaml-language-server: $schema=https://github.com/Informatiqal/automatiqal-cli-schema/blob/main/schemas/runbook.json?raw=true + +${dump(qseowSample, { + // indent: 4, + lineWidth: 300, +})}` ); } catch (e) { console.log(`\u274C ERROR: Unable to create sample file"`); + console.log(e.message); } try { - writeFileSync( - ".\\automatiqal-sample.variables.yaml", - yaml.dump(variables, null, 4) - ); + writeFileSync(".\\automatiqal-sample.variables.yaml", variables); } catch (e) { console.log(`\u274C ERROR: Unable to create sample variables file"`); + console.log(e.message); } } diff --git a/test/mian.spec.ts b/test/mian.spec.ts index 7329816..59c2ca7 100644 --- a/test/mian.spec.ts +++ b/test/mian.spec.ts @@ -21,7 +21,7 @@ async function runProcess(args: string[]): Promise { reject({ code: err }); }); - process.on("close", function (code) { + process.on("close", function (code: number) { resolve({ code, output: output.split("\n") }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 47c6d29..9df0f4a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "target": "ES6", "declaration": true, "declarationDir": "./dist", - "module": "ESNext", + "module": "ES2020", "noImplicitAny": false, "sourceMap": true, "moduleResolution": "node", diff --git a/tsconfig.test.json b/tsconfig.test.json index fc85aff..9df0f4a 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -4,14 +4,16 @@ "target": "ES6", "declaration": true, "declarationDir": "./dist", - "module": "CommonJS", + "module": "ES2020", "noImplicitAny": false, "sourceMap": true, "moduleResolution": "node", - "esModuleInterop": true, + "lib": [ + "es2021" + ], }, "include": [ - "src/**/*" + "src/**/*", ], "exclude": [ "node_modules"