UNPKG

102 kBJavaScriptView Raw
1#!/usr/bin/env node
2/*----------------------------------------------
3 * Generated by rollup. Written by John Schmidt.
4 * Rally Tools CLI v1.13.0
5 *--------------------------------------------*/
6const importLazy = require("import-lazy")(require);
7
8'use strict';
9
10function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
11
12var argparse = _interopDefault(require('minimist'));
13var chalk$1 = _interopDefault(require('chalk'));
14var os = require('os');
15var fs = require('fs');
16var fs__default = _interopDefault(fs);
17var child_process = require('child_process');
18var perf_hooks = require('perf_hooks');
19var path = require('path');
20var path__default = _interopDefault(path);
21var moment = _interopDefault(require('moment'));
22
23function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
24 var desc = {};
25 Object['ke' + 'ys'](descriptor).forEach(function (key) {
26 desc[key] = descriptor[key];
27 });
28 desc.enumerable = !!desc.enumerable;
29 desc.configurable = !!desc.configurable;
30
31 if ('value' in desc || desc.initializer) {
32 desc.writable = true;
33 }
34
35 desc = decorators.slice().reverse().reduce(function (desc, decorator) {
36 return decorator(target, property, desc) || desc;
37 }, desc);
38
39 if (context && desc.initializer !== void 0) {
40 desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
41 desc.initializer = undefined;
42 }
43
44 if (desc.initializer === void 0) {
45 Object['define' + 'Property'](target, property, desc);
46 desc = null;
47 }
48
49 return desc;
50}
51
52let configFile = os.homedir() + "/.rallyconfig";
53let configObject;
54function loadConfig(file) {
55 if (file) configFile = file;
56 configObject = {
57 hasConfig: true
58 };
59
60 try {
61 let json = fs.readFileSync(configFile);
62 configObject = JSON.parse(json);
63 configObject.hasConfig = true;
64 } catch (e) {
65 if (e.code == "ENOENT") {
66 configObject.hasConfig = false; //ok, they should probably make a config
67 } else {
68 throw e;
69 }
70 }
71}
72function setConfig(obj) {
73 configObject = obj;
74}
75loadConfig();
76
77//these are the help entries for each command
78let helpEntries = {};
79
80let helpEntry = name => helpEntries[name] ? helpEntries[name] : helpEntries[name] = {
81 name
82}; //short description
83
84
85function helpText(text) {
86 return function (func, name) {
87 helpEntry(name).text = text;
88 return func;
89 };
90} //flag type argument like -f or --file
91
92function arg(long, short, desc) {
93 return function (func, name) {
94 let args = helpEntry(name).args = helpEntry(name).args || [];
95 args.unshift({
96 long,
97 short,
98 desc
99 });
100 return func;
101 };
102} //normal argument
103
104function param(param, desc) {
105 return function (func, name) {
106 let params = helpEntry(name).params = helpEntry(name).params || [];
107 params.unshift({
108 param,
109 desc
110 });
111 return func;
112 };
113} //usage string
114
115function usage(usage) {
116 return function (func, name) {
117 usage = usage.replace(/[\[<](\w+)[\]>]/g, chalk`[{blue $1}]`);
118 helpEntry(name).usage = usage;
119 return func;
120 };
121}
122//function retuns obj.a.b.c
123
124function deepAccess(obj, path) {
125 let o = obj;
126
127 for (let key of path) {
128 if (!o) return [];
129 o = o[key];
130 }
131
132 return o;
133} //This takes a class as the first argument, then adds a getter/setter pair that
134//corresponds to an object in this.data
135
136
137function defineAssoc(classname, shortname, path) {
138 path = path.split(".");
139 let lastKey = path.pop();
140 Object.defineProperty(classname.prototype, shortname, {
141 get() {
142 return deepAccess(this, path)[lastKey];
143 },
144
145 set(val) {
146 deepAccess(this, path)[lastKey] = val;
147 }
148
149 });
150}
151
152function spawn(options, ...args) {
153 if (typeof options !== "object") {
154 args.unshift(options);
155 options = {};
156 } //todo options
157
158
159 return new Promise((resolve, reject) => {
160 let start = perf_hooks.performance.now();
161 let stdout = "";
162 let stderr = "";
163 let cp = child_process.spawn(...args);
164 if (cp.stdout) cp.stdout.on("data", chunk => {
165 stdout += chunk;
166 write(chunk);
167 });
168 if (cp.stderr) cp.stderr.on("data", chunk => {
169 stderr += chunk;
170 write(chunk);
171 });
172 cp.on("error", reject);
173 cp.on("close", code => {
174 let end = perf_hooks.performance.now();
175 let time = end - start;
176 let timestr = time > 1000 ? (time / 100 | 0) / 10 + "s" : (time | 0) + "ms";
177 resolve({
178 stdout,
179 stderr,
180 exitCode: code,
181 time,
182 timestr
183 });
184 });
185 });
186}
187
188const rp = importLazy("request-promise");
189global.chalk = chalk$1;
190
191global.log = (...text) => console.log(...text);
192
193global.write = (...text) => process.stdout.write(...text);
194
195global.elog = (...text) => console.log(...text);
196
197global.ewrite = (...text) => process.stderr.write(...text);
198
199global.errorLog = (...text) => log(...text.map(chalk$1.red));
200
201class lib {
202 //This function takes 2 required arguemnts:
203 // env: the enviornment you wish to use
204 // and either:
205 // 'path', the short path to the resource. ex '/presets/'
206 // 'path_full', the full path to the resource like 'https://discovery-dev.sdvi.com/presets'
207 //
208 // If the method is anything but GET, either payload or body should be set.
209 // payload should be a javascript object to be turned into json as the request body
210 // body should be a string that is passed as the body. for example: the python code of a preset.
211 //
212 // qs are the querystring parameters, in a key: value object.
213 // {filter: "name=test name"} becomes something like 'filter=name=test+name'
214 //
215 // headers are the headers of the request. "Content-Type" is already set if
216 // payload is given as a parameter
217 //
218 // fullResponse should be true if you want to receive the request object,
219 // not just the returned data.
220 static async makeAPIRequest({
221 env,
222 path,
223 path_full,
224 fullPath,
225 payload,
226 body,
227 method = "GET",
228 qs,
229 headers = {},
230 fullResponse = false,
231 timeout = configObject.timeout || 20000
232 }) {
233 var _configObject$api;
234
235 //backwards compatability from ruby script
236 if (fullPath) path_full = fullPath; //Keys are defined in enviornment variables
237
238 let config = configObject === null || configObject === void 0 ? void 0 : (_configObject$api = configObject.api) === null || _configObject$api === void 0 ? void 0 : _configObject$api[env];
239
240 if (!config) {
241 throw new UnconfiguredEnvError(env);
242 }
243
244 if (method !== "GET" && !configObject.dangerModify) {
245 if (env === "UAT" && configObject.restrictUAT || env === "PROD") {
246 throw new ProtectedEnvError(env);
247 }
248 }
249
250 let rally_api_key = config.key;
251 let rally_api = config.url;
252
253 if (path && path.startsWith("/v1.0/")) {
254 rally_api = rally_api.replace("/api/v2", "/api");
255 }
256
257 path = path_full || rally_api + path;
258
259 if (payload) {
260 body = JSON.stringify(payload, null, 4);
261 }
262
263 if (payload) {
264 headers["Content-Type"] = "application/vnd.api+json";
265 }
266
267 let fullHeaders = {
268 //SDVI ignores this header sometimes.
269 Accept: "application/vnd.api+json",
270 "X-SDVI-Client-Application": "Discovery-rtlib-" + (configObject.appName || "commandline"),
271 ...headers
272 };
273
274 if (configObject.vvverbose) {
275 log(`${method} @ ${path}`);
276 log(JSON.stringify(fullHeaders, null, 4));
277
278 if (body) {
279 log(body);
280 } else {
281 log("(No body");
282 }
283 }
284
285 let requestOptions = {
286 method,
287 body,
288 qs,
289 uri: path,
290 timeout,
291 auth: {
292 bearer: rally_api_key
293 },
294 headers: fullHeaders,
295 simple: false,
296 resolveWithFullResponse: true
297 };
298 let response;
299
300 try {
301 response = await rp(requestOptions);
302
303 if (configObject.vverbose || configObject.vvverbose) {
304 log(chalk$1`${method} @ ${response.request.uri.href}`);
305 }
306 } catch (e) {
307 if ((e === null || e === void 0 ? void 0 : e.cause.code) === "ESOCKETTIMEDOUT") {
308 throw new APIError(response || {}, requestOptions, body);
309 } else {
310 throw e;
311 }
312 } //Throw an error for any 5xx or 4xx
313
314
315 if (!fullResponse && ![200, 201, 202, 203, 204].includes(response.statusCode)) {
316 throw new APIError(response, requestOptions, body);
317 }
318
319 let contentType = response.headers["content-type"];
320 let isJSONResponse = contentType === "application/vnd.api+json" || contentType === "application/json";
321
322 if (configObject.vvverbose) {
323 log(response.body);
324 }
325
326 if (fullResponse) {
327 return response;
328 } else if (isJSONResponse) {
329 try {
330 return JSON.parse(response.body);
331 } catch (e) {
332 log(response.body);
333 throw new AbortError("Body is not valid json: ");
334 }
335 } else {
336 return response.body;
337 }
338 } //Index a json endpoint that returns a {links} field.
339 //This function returns the merged data objects as an array
340 //
341 //Additonal options (besides makeAPIRequest options):
342 // - Observe: function to be called for each set of data from the api
343
344
345 static async indexPath(env, path) {
346 let all = [];
347 let opts = typeof env === "string" ? {
348 env,
349 path
350 } : env;
351 let json = await this.makeAPIRequest(opts);
352 let [numPages, pageSize] = this.numPages(json.links.last); //log(`num pages: ${numPages} * ${pageSize}`);
353
354 all = [...json.data];
355
356 while (json.links.next) {
357 json = await this.makeAPIRequest({ ...opts,
358 path_full: json.links.next
359 });
360 if (opts.observe) await opts.observe(json.data);
361 all = [...all, ...json.data];
362 }
363
364 return all;
365 } //Returns number of pages and pagination size
366
367
368 static numPages(str) {
369 return /page=(\d+)p(\d+)/.exec(str).slice(1);
370 }
371
372 static arrayChunk(array, chunkSize) {
373 let newArr = [];
374
375 for (let i = 0; i < array.length; i += chunkSize) {
376 newArr.push(array.slice(i, i + chunkSize));
377 }
378
379 return newArr;
380 }
381
382 static async doPromises(promises, result = [], cb) {
383 for (let promise of promises) {
384 let res = await promise;
385 result.push(res);
386
387 if (cb) {
388 cb(res.data);
389 }
390 }
391
392 return result;
393 }
394
395 static clearProgress(size = 30) {
396 process.stderr.write(`\r${" ".repeat(size + 15)}\r`);
397 }
398
399 static async drawProgress(i, max, size = process.stdout.columns - 15 || 15) {
400 if (size > 45) size = 45;
401 let pct = Number(i) / Number(max); //clamp between 0 and 1
402
403 pct = pct < 0 ? 0 : pct > 1 ? 1 : pct;
404 let numFilled = Math.floor(pct * size);
405 let numEmpty = size - numFilled;
406 this.clearProgress(size);
407 process.stderr.write(`[${"*".repeat(numFilled)}${" ".repeat(numEmpty)}] ${i} / ${max}`);
408 }
409
410 static async keepalive(func, inputData, {
411 chunksize,
412 observe = async _ => _
413 } = {}) {
414 let total = inputData ? inputData.length : func.length;
415 let i = 0;
416
417 let createPromise = () => {
418 let ret;
419 if (i >= total) return [];
420
421 if (inputData) {
422 ret = [i, func(inputData[i])];
423 } else {
424 ret = [i, func[i]()];
425 }
426
427 i++;
428 return ret;
429 };
430
431 let values = [];
432 let finished = 0;
433 process.stderr.write("\n");
434 let threads = [...this.range(20)].map(async whichThread => {
435 while (true) {
436 let [i, currentPromise] = createPromise();
437 if (i == undefined) break;
438 values[i] = await observe((await currentPromise));
439 this.drawProgress(++finished, total);
440 }
441 });
442 await Promise.all(threads);
443 process.stderr.write("\n");
444 return values;
445 }
446
447 static *range(start, end) {
448 if (end === undefined) {
449 end = start;
450 start = 0;
451 }
452
453 while (start < end) yield start++;
454 } //Index a json endpoint that returns a {links} field.
455 //
456 //This function is faster than indexPath because it can guess the pages it
457 //needs to retreive so that it can request all assets at once.
458 //
459 //This function assumes that the content from the inital request is the
460 //first page, so starting on another page may cause issues. Consider
461 //indexPath for that.
462 //
463 //Additional opts, besides default indexPath opts:
464 // - chunksize[10]: How often to break apart concurrent requests
465
466
467 static async indexPathFast(env, path) {
468 let opts = typeof env === "string" ? {
469 env,
470 path
471 } : env; //Create a copy of the options in case we need to have a special first request
472
473 let start = opts.start || 1;
474 let initOpts = { ...opts
475 };
476
477 if (opts.pageSize) {
478 initOpts.qs = { ...opts.qs
479 };
480 initOpts.qs.page = `${start}p${opts.pageSize}`;
481 }
482
483 let json = await this.makeAPIRequest(initOpts);
484 if (opts.observe && opts.start !== 1) json = await opts.observe(json);
485 let baselink = json.links.first;
486
487 const linkToPage = page => baselink.replace(`page=1p`, `page=${page}p`);
488
489 let [numPages, pageSize] = this.numPages(json.links.last); //Construct an array of all the requests that are done simultanously.
490 //Assume that the content from the inital request is the first page.
491
492 let allResults = await this.keepalive(this.makeAPIRequest, [...this.range(start + 1, Number(numPages) + 1 || opts.limit + 1)].map(i => ({ ...opts,
493 path_full: linkToPage(i)
494 })), {
495 chunksize: opts.chunksize,
496 observe: opts.observe
497 });
498
499 if (start == 1) {
500 allResults.unshift(json);
501 }
502
503 this.clearProgress();
504 let all = [];
505
506 for (let result of allResults) {
507 for (let item of result.data) {
508 all.push(item);
509 }
510 }
511
512 return all;
513 }
514
515 static isLocalEnv(env) {
516 return !env || env === "LOCAL" || env === "LOC";
517 }
518
519 static envName(env) {
520 if (this.isLocalEnv(env)) return "LOCAL";
521 return env;
522 }
523
524}
525class AbortError extends Error {
526 constructor(message) {
527 super(message);
528 Error.captureStackTrace(this, this.constructor);
529 this.name = "AbortError";
530 }
531
532}
533class APIError extends Error {
534 constructor(response, opts, body) {
535 super(chalk$1`
536{reset Request returned} {yellow ${response === null || response === void 0 ? void 0 : response.statusCode}}{
537{green ${JSON.stringify(opts, null, 4)}}
538{green ${body}}
539{reset ${response.body}}
540===============================
541{red ${response.body ? "Request timed out" : "Bad response from API"}}
542===============================
543 `);
544 this.response = response;
545 this.opts = opts;
546 this.body = body;
547 Error.captureStackTrace(this, this.constructor);
548 this.name = "ApiError";
549 }
550
551}
552class UnconfiguredEnvError extends AbortError {
553 constructor(env) {
554 super("Unconfigured enviornment: " + env);
555 this.name = "Unconfigured Env Error";
556 }
557
558}
559class ProtectedEnvError extends AbortError {
560 constructor(env) {
561 super("Protected enviornment: " + env);
562 this.name = "Protected Env Error";
563 }
564
565}
566class FileTooLargeError extends Error {
567 constructor(file) {
568 super(`File ${file.parentAsset ? file.parentAsset.name : "(unknown)"}/${file.name} size is: ${file.sizeGB}g (> ~.2G)`);
569 this.name = "File too large error";
570 }
571
572}
573class Collection {
574 constructor(arr) {
575 this.arr = arr;
576 }
577
578 [Symbol.iterator]() {
579 return this.arr[Symbol.iterator]();
580 }
581
582 findById(id) {
583 return this.arr.find(x => x.id == id);
584 }
585
586 findByName(name) {
587 return this.arr.find(x => x.name == name);
588 }
589
590 findByNameContains(name) {
591 return this.arr.find(x => x.name.includes(name));
592 }
593
594 log() {
595 for (let d of this) {
596 if (d) {
597 log(d.chalkPrint(true));
598 } else {
599 log(chalk$1`{red (None)}`);
600 }
601 }
602 }
603
604 get length() {
605 return this.arr.length;
606 }
607
608}
609class RallyBase {
610 static handleCaching() {
611 if (!this.cache) this.cache = [];
612 }
613
614 static isLoaded(env) {
615 if (!this.hasLoadedAll) return;
616 return this.hasLoadedAll[env];
617 }
618
619 static async getById(env, id, qs) {
620 this.handleCaching();
621
622 for (let item of this.cache) {
623 if (item.id == id && item.remote === env || `${env}-${id}` === item.metastring) return item;
624 }
625
626 let data = await lib.makeAPIRequest({
627 env,
628 path: `/${this.endpoint}/${id}`,
629 qs
630 });
631
632 if (data.data) {
633 let o = new this({
634 data: data.data,
635 remote: env,
636 included: data.included
637 });
638 this.cache.push(o);
639 return o;
640 }
641 }
642
643 static async getByName(env, name, qs) {
644 this.handleCaching();
645
646 for (let item of this.cache) {
647 if (item.name === name && item.remote === env) return item;
648 }
649
650 let data = await lib.makeAPIRequest({
651 env,
652 path: `/${this.endpoint}`,
653 qs: { ...qs,
654 filter: `name=${name}` + (qs ? qs.filter : "")
655 }
656 }); //TODO included might not wokr correctly here
657
658 if (data.data[0]) {
659 let o = new this({
660 data: data.data[0],
661 remote: env,
662 included: data.included
663 });
664 this.cache.push(o);
665 return o;
666 }
667 }
668
669 static async getAllPreCollect(d) {
670 return d;
671 }
672
673 static async getAll(env) {
674 this.handleCaching();
675 let datas = await lib.indexPathFast({
676 env,
677 path: `/${this.endpoint}`,
678 pageSize: "50",
679 qs: {
680 sort: "id"
681 }
682 });
683 datas = await this.getAllPreCollect(datas);
684 let all = new Collection(datas.map(data => new this({
685 data,
686 remote: env
687 })));
688 this.cache = [...this.cache, ...all.arr];
689 return all;
690 }
691
692 static async removeCache(env) {
693 this.handleCaching();
694 this.cache = this.cache.filter(x => x.remote !== env);
695 } //Specific turns name into id based on env
696 //Generic turns ids into names
697
698
699 async resolveApply(type, dataObj, direction) {
700 let obj;
701
702 if (direction == "generic") {
703 obj = await type.getById(this.remote, dataObj.id);
704
705 if (obj) {
706 dataObj.name = obj.name;
707 }
708 } else if (direction == "specific") {
709 obj = await type.getByName(this.remote, dataObj.name);
710
711 if (obj) {
712 dataObj.id = obj.id;
713 }
714 }
715
716 return obj;
717 } //Type is the baseclass you are looking for (should extend RallyBase)
718 //name is the name of the field
719 //isArray is true if it has multiple cardinailty, false if it is single
720 //direction gets passed directly to resolveApply
721
722
723 async resolveField(type, name, isArray = false, direction = "generic") {
724 // ignore empty fields
725 let field = this.relationships[name];
726 if (!(field === null || field === void 0 ? void 0 : field.data)) return;
727
728 if (isArray) {
729 return await Promise.all(field.data.map(o => this.resolveApply(type, o, direction)));
730 } else {
731 return await this.resolveApply(type, field.data, direction);
732 }
733 }
734
735 cleanup() {
736 for (let [key, val] of Object.entries(this.relationships)) {
737 //Remove ids from data
738 if (val.data) {
739 if (val.data.id) {
740 delete val.data.id;
741 } else if (val.data[0]) {
742 for (let x of val.data) delete x.id;
743 }
744 }
745
746 delete val.links;
747 } // organization is unused (?)
748
749
750 delete this.relationships.organization; // id is specific to envs
751 // but save source inside meta string in case we need it
752
753 this.metastring = this.remote + "-" + this.data.id;
754 delete this.data.id; // links too
755
756 delete this.data.links;
757 }
758
759}
760
761class Provider extends RallyBase {
762 constructor({
763 data,
764 remote
765 }) {
766 super();
767 this.data = data;
768 this.meta = {};
769 this.remote = remote;
770 } //cached
771
772
773 async getEditorConfig() {
774 if (this.editorConfig) return this.editorConfig;
775 this.editorConfig = await lib.makeAPIRequest({
776 env: this.remote,
777 path_full: this.data.links.editorConfig
778 });
779 this.editorConfig.fileExt = await this.getFileExtension();
780 return this.editorConfig;
781 }
782
783 static async getAllPreCollect(providers) {
784 return providers.sort((a, b) => {
785 return a.attributes.category.localeCompare(b.attributes.category) || a.attributes.name.localeCompare(b.attributes.name);
786 });
787 }
788
789 async getFileExtension() {
790 let config = await this.getEditorConfig();
791 let map = {
792 python: "py",
793 text: "txt",
794
795 getmap(key) {
796 if (this.name === "Aurora") return "zip";
797 if (this[key]) return this[key];
798 return key;
799 }
800
801 };
802 return map.getmap(config.lang);
803 }
804
805 chalkPrint(pad = true) {
806 let id = String(this.id);
807 if (pad) id = id.padStart(4);
808 return chalk`{green ${id}}: {blue ${this.category}} - {green ${this.name}}`;
809 }
810
811}
812
813defineAssoc(Provider, "id", "data.id");
814defineAssoc(Provider, "name", "data.attributes.name");
815defineAssoc(Provider, "category", "data.attributes.category");
816defineAssoc(Provider, "remote", "meta.remote");
817defineAssoc(Provider, "editorConfig", "meta.editorConfig");
818Provider.endpoint = "providerTypes";
819
820class File extends RallyBase {
821 constructor({
822 data,
823 remote,
824 included,
825 parent
826 }) {
827 super();
828 this.data = data;
829 this.meta = {};
830 this.remote = remote;
831 this.parentAsset = parent;
832 }
833
834 chalkPrint(pad = false) {
835 let id = String("F-" + (this.remote && this.remote + "-" + this.id || "LOCAL"));
836 if (pad) id = id.padStart(15);
837 return chalk`{green ${id}}: {blue ${this.data.attributes ? this.name : "(lite asset)"}}`;
838 }
839
840 canBeDownloaded() {
841 return this.sizeGB <= .2;
842 }
843
844 async getContent(force = false) {
845 if (!this.canBeDownloaded() && !force) {
846 throw new FileTooLargeError(this);
847 }
848
849 return lib.makeAPIRequest({
850 env: this.remote,
851 fullPath: this.contentLink
852 });
853 }
854
855 get size() {
856 return Object.values(this.data.attributes.instances)[0].size;
857 }
858
859 get sizeGB() {
860 return Math.round(this.size / 1024 / 1024 / 1024 * 10) / 10;
861 }
862
863}
864
865defineAssoc(File, "id", "data.id");
866defineAssoc(File, "name", "data.attributes.label");
867defineAssoc(File, "contentLink", "data.links.content");
868defineAssoc(File, "label", "data.attributes.label");
869defineAssoc(File, "md5", "data.attributes.md5");
870defineAssoc(File, "sha512", "data.attributes.sha512");
871defineAssoc(File, "tags", "data.attributes.tagList");
872defineAssoc(File, "instances", "data.attributes.instances");
873File.endpoint = null;
874
875class Asset extends RallyBase {
876 constructor({
877 data,
878 remote,
879 included
880 }) {
881 super();
882 this.data = data;
883 this.meta = {};
884 this.remote = remote;
885
886 if (included) {
887 this.meta.metadata = Asset.normalizeMetadata(included);
888 }
889 }
890
891 static normalizeMetadata(payload) {
892 let newMetadata = {};
893
894 for (let md of payload) {
895 if (md.type !== "metadata") continue;
896 newMetadata[md.attributes.usage] = md.attributes.metadata;
897 }
898
899 return newMetadata;
900 }
901
902 static lite(id, remote) {
903 return new this({
904 data: {
905 id
906 },
907 remote
908 });
909 }
910
911 chalkPrint(pad = false) {
912 let id = String("A-" + (this.remote && this.remote + "-" + this.id || "LOCAL"));
913 if (pad) id = id.padStart(15);
914 return chalk`{green ${id}}: {blue ${this.data.attributes ? this.name : "(lite asset)"}}`;
915 }
916
917 static async createNew(name, env) {
918 let req = await lib.makeAPIRequest({
919 env,
920 path: "/assets",
921 method: "POST",
922 payload: {
923 data: {
924 attributes: {
925 name
926 },
927 type: "assets"
928 }
929 }
930 });
931 return new this({
932 data: req.data,
933 remote: env
934 });
935 }
936
937 async delete() {
938 let req = await lib.makeAPIRequest({
939 env: this.remote,
940 path: "/assets/" + this.id,
941 method: "DELETE"
942 });
943 }
944
945 async getFiles() {
946 let req = await lib.indexPathFast({
947 env: this.remote,
948 path: `/assets/${this.id}/files`,
949 method: "GET"
950 }); //return req;
951
952 return new Collection(req.map(x => new File({
953 data: x,
954 remote: this.remote,
955 parent: this
956 })));
957 }
958
959 async addFile(label, fileuris) {
960 if (!Array.isArray(fileuris)) fileuris = [fileuris];
961 let instances = {};
962
963 for (let i = 0; i < fileuris.length; i++) {
964 instances[String(i + 1)] = {
965 uri: fileuris[i]
966 };
967 }
968
969 let req = await lib.makeAPIRequest({
970 env: this.remote,
971 path: "/files",
972 method: "POST",
973 payload: {
974 "data": {
975 "attributes": {
976 label,
977 instances
978 },
979 "relationships": {
980 "asset": {
981 "data": {
982 id: this.id,
983 "type": "assets"
984 }
985 }
986 },
987 "type": "files"
988 }
989 }
990 });
991 return req;
992 }
993
994 async startWorkflow(jobName, initData) {
995 let attributes;
996
997 if (initData) {
998 //Convert init data to string
999 initData = typeof initData === "string" ? initData : JSON.stringify(initData);
1000 attributes = {
1001 initData
1002 };
1003 }
1004
1005 let req = await lib.makeAPIRequest({
1006 env: this.remote,
1007 path: "/workflows",
1008 method: "POST",
1009 payload: {
1010 "data": {
1011 "type": "workflows",
1012 attributes,
1013 "relationships": {
1014 "movie": {
1015 "data": {
1016 id: this.id,
1017 "type": "movies"
1018 }
1019 },
1020 "rule": {
1021 "data": {
1022 "attributes": {
1023 "name": jobName
1024 },
1025 "type": "rules"
1026 }
1027 }
1028 }
1029 }
1030 }
1031 });
1032 return req;
1033 }
1034
1035 static async startAnonWorkflow(env, jobName, initData) {
1036 let attributes;
1037
1038 if (initData) {
1039 //Convert init data to string
1040 initData = typeof initData === "string" ? initData : JSON.stringify(initData);
1041 attributes = {
1042 initData
1043 };
1044 }
1045
1046 let req = await lib.makeAPIRequest({
1047 env,
1048 path: "/workflows",
1049 method: "POST",
1050 payload: {
1051 "data": {
1052 "type": "workflows",
1053 attributes,
1054 "relationships": {
1055 "rule": {
1056 "data": {
1057 "attributes": {
1058 "name": jobName
1059 },
1060 "type": "rules"
1061 }
1062 }
1063 }
1064 }
1065 }
1066 });
1067 return req;
1068 }
1069
1070 async startEvaluate(presetid) {
1071 // Fire and forget.
1072 let data = await lib.makeAPIRequest({
1073 env: this.remote,
1074 path: "/jobs",
1075 method: "POST",
1076 payload: {
1077 data: {
1078 type: "jobs",
1079 relationships: {
1080 movie: {
1081 data: {
1082 id: this.id,
1083 type: "movies"
1084 }
1085 },
1086 preset: {
1087 data: {
1088 id: presetid,
1089 type: "presets"
1090 }
1091 }
1092 }
1093 }
1094 }
1095 });
1096 return data;
1097 }
1098
1099}
1100
1101defineAssoc(Asset, "id", "data.id");
1102defineAssoc(Asset, "name", "data.attributes.name");
1103defineAssoc(Asset, "remote", "meta.remote");
1104defineAssoc(Asset, "md", "meta.metadata");
1105Asset.endpoint = "movies";
1106
1107const home = os.homedir();
1108const colon = /:/g;
1109const siloLike = /(silo\-\w+?)s?\/([^\/]+)\.([\w1234567890]+)$/g;
1110function pathTransform(path) {
1111 if (path.includes(":")) {
1112 //Ignore the first colon in window-like filesystems
1113 path = path.slice(0, 3) + path.slice(3).replace(colon, "--");
1114 }
1115
1116 if (configObject.invertedPath) {
1117 path = path.replace(siloLike, "$2-$1.$3");
1118 }
1119
1120 if (path.includes("\\342\\200\\220")) {
1121 path = path.replace("\\342\\200\\220", "‐");
1122 }
1123
1124 return path;
1125}
1126function readFileSync(path, options) {
1127 return fs__default.readFileSync(pathTransform(path), options);
1128} //Create writefilesync, with ability to create directory if it doesnt exist
1129
1130function writeFileSync(path$1, data, options, dircreated = false) {
1131 path$1 = pathTransform(path$1);
1132
1133 try {
1134 return fs__default.writeFileSync(path$1, data, options);
1135 } catch (e) {
1136 if (dircreated) throw e;
1137 let directory = path.dirname(path$1);
1138
1139 try {
1140 fs__default.statSync(directory);
1141 throw e;
1142 } catch (nodir) {
1143 fs__default.mkdirSync(directory);
1144 return writeFileSync(path$1, data, options, true);
1145 }
1146 }
1147}
1148
1149let exists = {};
1150
1151class Preset extends RallyBase {
1152 constructor({
1153 path: path$1,
1154 remote,
1155 data,
1156 subProject
1157 } = {}) {
1158 // Get full path if possible
1159 if (path$1) {
1160 path$1 = path.resolve(path$1);
1161
1162 if (path.dirname(path$1).includes("silo-metadata")) {
1163 throw new AbortError("Constructing preset from metadata file");
1164 }
1165 }
1166
1167 super(); // Cache by path
1168
1169 if (path$1) {
1170 if (exists[pathTransform(path$1)]) return exists[pathTransform(path$1)];
1171 exists[pathTransform(path$1)] = this;
1172 }
1173
1174 this.meta = {};
1175 this.subproject = subProject;
1176 this.remote = remote;
1177
1178 if (lib.isLocalEnv(this.remote)) {
1179 if (path$1) {
1180 this.path = path$1;
1181 let pathspl = this.path.split(".");
1182 this.ext = pathspl[pathspl.length - 1];
1183
1184 try {
1185 this.code = this.getLocalCode();
1186 } catch (e) {
1187 if (e.code === "ENOENT" && configObject.ignoreMissing) {
1188 this.missing = true;
1189 return undefined;
1190 } else {
1191 log(chalk`{red Node Error} ${e.message}`);
1192 throw new AbortError("Could not load code of local file");
1193 }
1194 }
1195
1196 let name = this.parseFilenameForName() || this.parseCodeForName();
1197
1198 try {
1199 this.data = this.getLocalMetadata();
1200 this.isGeneric = true;
1201 name = this.name;
1202 } catch (e) {
1203 log(chalk`{yellow Warning}: ${path$1} does not have a readable metadata file! Looking for ${this.localmetadatapath}`);
1204 this.data = Preset.newShell();
1205 this.isGeneric = false;
1206 }
1207
1208 this.name = name;
1209 } else {
1210 this.data = Preset.newShell();
1211 }
1212 } else {
1213 this.data = data; //this.name = data.attributes.name;
1214 //this.id = data.id;
1215
1216 this.isGeneric = false;
1217 }
1218
1219 this.data.attributes.rallyConfiguration = undefined;
1220 this.data.attributes.systemManaged = undefined;
1221 } //Given a metadata file, get its actualy file
1222
1223
1224 static async fromMetadata(path, subproject) {
1225 let data;
1226
1227 try {
1228 data = JSON.parse(readFileSync(path));
1229 } catch (e) {
1230 if (e.code === "ENOENT" && configObject.ignoreMissing) {
1231 return null;
1232 } else {
1233 throw e;
1234 }
1235 }
1236
1237 let providerType = data.relationships.providerType.data.name;
1238 let provider = await Provider.getByName("DEV", providerType);
1239
1240 if (!provider) {
1241 log(chalk`{red The provider type {green ${providerType}} does not exist}`);
1242 log(chalk`{red Skipping {green ${path}}.}`);
1243 return null;
1244 }
1245
1246 let ext = await provider.getFileExtension();
1247 let name = data.attributes.name;
1248 let realpath = Preset.getLocalPath(name, ext, subproject);
1249 return new Preset({
1250 path: realpath,
1251 subProject: subproject
1252 });
1253 }
1254
1255 static newShell() {
1256 return {
1257 "attributes": {
1258 "providerSettings": {}
1259 },
1260 "relationships": {},
1261 "type": "presets"
1262 };
1263 }
1264
1265 cleanup() {
1266 super.cleanup();
1267 delete this.attributes["createdAt"];
1268 delete this.attributes["updatedAt"];
1269 }
1270
1271 async acclimatize(env) {
1272 if (!this.isGeneric) throw new AbortError("Cannot acclimatize non-generics or shells");
1273 let providers = await Provider.getAll(env);
1274 let ptype = this.relationships["providerType"];
1275 ptype = ptype.data;
1276 let provider = providers.findByName(ptype.name);
1277 ptype.id = provider.id;
1278 }
1279
1280 get test() {
1281 if (!this.code) return [];
1282 const regex = /[^-]autotest:\s?([\w\d_\-. \/]+)[\r\s\n]*?/gm;
1283 let match;
1284 let matches = [];
1285
1286 while (match = regex.exec(this.code)) {
1287 matches.push(match[1]);
1288 }
1289
1290 return matches;
1291 }
1292
1293 async runTest(env) {
1294 let remote = await Preset.getByName(env, this.name);
1295
1296 for (let test of this.test) {
1297 log("Tests...");
1298 let asset;
1299
1300 if (test.startsWith("id")) {
1301 let match = /id:\s*(\d+)/g.exec(test);
1302
1303 if (!match) {
1304 log(chalk`{red Could not parse autotest} ${test}.`);
1305 throw new AbortError("Could not properly parse the preset header");
1306 }
1307
1308 asset = await Asset.getById(env, match[1]);
1309 } else {
1310 asset = await Asset.getByName(env, test);
1311 }
1312
1313 if (!asset) {
1314 log(chalk`{yellow No movie found}, skipping test.`);
1315 continue;
1316 }
1317
1318 log(chalk`Starting job {green ${this.name}} on ${asset.chalkPrint(false)}... `);
1319 await asset.startEvaluate(remote.id);
1320 }
1321 }
1322
1323 async resolve() {
1324 if (this.isGeneric) return;
1325 let proType = await this.resolveField(Provider, "providerType");
1326 this.ext = await proType.getFileExtension();
1327 this.isGeneric = true;
1328 return {
1329 proType
1330 };
1331 }
1332
1333 async saveLocal() {
1334 await this.saveLocalMetadata();
1335 await this.saveLocalFile();
1336 }
1337
1338 async saveLocalMetadata() {
1339 if (!this.isGeneric) {
1340 await this.resolve();
1341 this.cleanup();
1342 }
1343
1344 writeFileSync(this.localmetadatapath, JSON.stringify(this.data, null, 4));
1345 }
1346
1347 async saveLocalFile() {
1348 writeFileSync(this.localpath, this.code);
1349 }
1350
1351 async uploadRemote(env) {
1352 await this.uploadCodeToEnv(env, true);
1353 }
1354
1355 async save(env) {
1356 this.saved = true;
1357
1358 if (!this.isGeneric) {
1359 await this.resolve();
1360 }
1361
1362 this.cleanup();
1363
1364 if (lib.isLocalEnv(env)) {
1365 log(chalk`Saving preset {green ${this.name}} to {blue ${lib.envName(env)}}.`);
1366 await this.saveLocal();
1367 } else {
1368 await this.uploadRemote(env);
1369 }
1370 }
1371
1372 async downloadCode() {
1373 if (!this.remote || this.code) return this.code;
1374 return this.code = await lib.makeAPIRequest({
1375 env: this.remote,
1376 path_full: this.data.links.providerData,
1377 json: false
1378 });
1379 }
1380
1381 get code() {
1382 if (this._code) return this._code;
1383 }
1384
1385 set code(v) {
1386 this._code = v;
1387 }
1388
1389 chalkPrint(pad = true) {
1390 let id = String("P-" + (this.remote && this.remote + "-" + this.id || "LOCAL"));
1391 let sub = "";
1392
1393 if (this.subproject) {
1394 sub = chalk`{yellow ${this.subproject}}`;
1395 }
1396
1397 if (pad) id = id.padStart(10);
1398
1399 if (this.name == undefined) {
1400 return chalk`{green ${id}}: ${sub}{red ${this.path}}`;
1401 } else if (this.meta.proType) {
1402 return chalk`{green ${id}}: ${sub}{red ${this.meta.proType.name}} {blue ${this.name}}`;
1403 } else {
1404 return chalk`{green ${id}}: ${sub}{blue ${this.name}}`;
1405 }
1406 }
1407
1408 parseFilenameForName() {
1409 if (this.path.endsWith(".jinja") || this.path.endsWith(".json")) {
1410 return path.basename(this.path).replace("_", " ").replace("-", " ").replace(".json", "").replace(".jinja", "");
1411 }
1412 }
1413
1414 parseCodeForName() {
1415 const name_regex = /name\s?:\s([\w\d. \/]+)[\r\s\n]*?/;
1416 const match = name_regex.exec(this.code);
1417 if (match) return match[1];
1418 }
1419
1420 findStringsInCode(strings) {
1421 if (!this.code) return [];
1422 return strings.filter(str => {
1423 let regex = new RegExp(str);
1424 return !!this.code.match(regex);
1425 });
1426 }
1427
1428 static getLocalPath(name, ext, subproject) {
1429 return path__default.join(configObject.repodir, subproject || "", "silo-presets", name + "." + ext);
1430 }
1431
1432 get localpath() {
1433 return Preset.getLocalPath(this.name, this.ext, this.subproject);
1434 }
1435
1436 get path() {
1437 if (this._path) return this._path;
1438 }
1439
1440 set path(val) {
1441 this._path = val;
1442 }
1443
1444 get name() {
1445 return this._nameOuter;
1446 }
1447
1448 set name(val) {
1449 if (!this._nameInner) this._nameInner = val;
1450 this._nameOuter = val;
1451 }
1452
1453 set providerType(value) {
1454 this.relationships["providerType"] = {
1455 data: { ...value,
1456 type: "providerTypes"
1457 }
1458 };
1459 }
1460
1461 get localmetadatapath() {
1462 if (this.path) {
1463 return this.path.replace("silo-presets", "silo-metadata").replace(new RegExp(this.ext + "$"), "json");
1464 }
1465
1466 return path__default.join(configObject.repodir, this.subproject || "", "silo-metadata", this.name + ".json");
1467 }
1468
1469 get immutable() {
1470 return this.name.includes("Constant");
1471 }
1472
1473 async uploadPresetData(env, id) {
1474 let res = await lib.makeAPIRequest({
1475 env,
1476 path: `/presets/${id}/providerData`,
1477 body: this.code,
1478 method: "PUT",
1479 fullResponse: true,
1480 timeout: 5000
1481 });
1482 write(chalk`code up {yellow ${res.statusCode}}, `);
1483 }
1484
1485 async grabMetadata(env) {
1486 let remote = await Preset.getByName(env, this.name);
1487 this.isGeneric = false;
1488
1489 if (!remote) {
1490 throw new AbortError(`No file found on remote ${env} with name ${this.name}`);
1491 }
1492
1493 this.data = remote.data;
1494 this.remote = env;
1495 }
1496
1497 async uploadCodeToEnv(env, includeMetadata, shouldTest = true) {
1498 if (!this.name) {
1499 log(chalk`Failed uploading {red ${this.path}}. No name found.`);
1500 return;
1501 }
1502
1503 write(chalk`Uploading preset {green ${this.name}} to {green ${env}}: `);
1504
1505 if (this.immutable) {
1506 log(chalk`{magenta IMMUTABLE}. Nothing to do.`);
1507 return;
1508 } //First query the api to see if this already exists.
1509
1510
1511 let remote = await Preset.getByName(env, this.name);
1512
1513 if (remote) {
1514 //If it exists we can replace it
1515 write("replace, ");
1516
1517 if (includeMetadata) {
1518 let res = await lib.makeAPIRequest({
1519 env,
1520 path: `/presets/${remote.id}`,
1521 method: "PATCH",
1522 payload: {
1523 data: {
1524 attributes: this.data.attributes,
1525 type: "presets"
1526 }
1527 },
1528 fullResponse: true
1529 });
1530 write(chalk`metadata {yellow ${res.statusCode}}, `);
1531 }
1532
1533 await this.uploadPresetData(env, remote.id);
1534 } else {
1535 write("create, ");
1536 let metadata = {
1537 data: this.data
1538 };
1539
1540 if (!this.relationships["providerType"]) {
1541 throw new AbortError("Cannot acclimatize shelled presets. (try creating it on the env first)");
1542 }
1543
1544 await this.acclimatize(env);
1545 write("Posting to create preset... ");
1546 let res = await lib.makeAPIRequest({
1547 env,
1548 path: `/presets`,
1549 method: "POST",
1550 payload: metadata,
1551 timeout: 5000
1552 });
1553 let id = res.data.id;
1554 write(chalk`Created id {green ${id}}... Uploading Code... `);
1555 await this.uploadPresetData(env, id);
1556 }
1557
1558 if (this.test[0] && shouldTest) {
1559 await this.runTest(env);
1560 } else {
1561 log("No tests. Done.");
1562 }
1563 }
1564
1565 getLocalMetadata() {
1566 return JSON.parse(readFileSync(this.localmetadatapath, "utf-8"));
1567 }
1568
1569 getLocalCode() {
1570 return readFileSync(this.path, "utf-8");
1571 }
1572
1573}
1574
1575defineAssoc(Preset, "_nameInner", "data.attributes.providerSettings.PresetName");
1576defineAssoc(Preset, "_nameOuter", "data.attributes.name");
1577defineAssoc(Preset, "id", "data.id");
1578defineAssoc(Preset, "attributes", "data.attributes");
1579defineAssoc(Preset, "relationships", "data.relationships");
1580defineAssoc(Preset, "remote", "meta.remote");
1581defineAssoc(Preset, "_code", "meta.code");
1582defineAssoc(Preset, "_path", "meta.path");
1583defineAssoc(Preset, "isGeneric", "meta.isGeneric");
1584defineAssoc(Preset, "ext", "meta.ext");
1585defineAssoc(Preset, "subproject", "meta.project");
1586defineAssoc(Preset, "metastring", "meta.metastring");
1587Preset.endpoint = "presets";
1588
1589class Notification extends RallyBase {
1590 constructor({
1591 data,
1592 remote
1593 }) {
1594 super();
1595 this.data = data;
1596 this.meta = {};
1597 this.remote = remote;
1598 }
1599
1600 static async getAllPreCollect(notifications) {
1601 return notifications.sort((a, b) => {
1602 return a.attributes.type.localeCompare(b.attributes.type) || a.attributes.name.localeCompare(b.attributes.name);
1603 });
1604 }
1605
1606 chalkPrint(pad = false) {
1607 let id = String("N-" + this.id);
1608 if (pad) id = id.padStart(4);
1609 return chalk`{green ${id}}: {blue ${this.type}} - {green ${this.name}}`;
1610 }
1611
1612}
1613
1614defineAssoc(Notification, "id", "data.id");
1615defineAssoc(Notification, "name", "data.attributes.name");
1616defineAssoc(Notification, "address", "data.attributes.address");
1617defineAssoc(Notification, "type", "data.attributes.type");
1618defineAssoc(Notification, "remote", "meta.remote");
1619Notification.endpoint = "notificationPresets";
1620
1621class Rule extends RallyBase {
1622 constructor({
1623 path: path$1,
1624 data,
1625 remote,
1626 subProject
1627 } = {}) {
1628 super();
1629
1630 if (path$1) {
1631 path$1 = path.resolve(path$1);
1632
1633 try {
1634 let f = readFileSync(path$1, "utf-8");
1635 data = JSON.parse(readFileSync(path$1, "utf-8"));
1636 } catch (e) {
1637 if (e.code === "ENOENT") {
1638 if (configObject.ignoreMissing) {
1639 this.missing = true;
1640 return undefined;
1641 } else {
1642 throw new AbortError("Could not load code of local file");
1643 }
1644 } else {
1645 throw new AbortError(`Unreadable JSON in ${path$1}. ${e}`);
1646 }
1647 }
1648 }
1649
1650 this.meta = {};
1651 this.subproject = subProject;
1652
1653 if (!data) {
1654 data = Rule.newShell();
1655 }
1656
1657 this.data = data;
1658 this.remote = remote;
1659 this.isGeneric = !this.remote;
1660 }
1661
1662 static newShell() {
1663 return {
1664 "attributes": {
1665 "description": "-",
1666 "priority": "PriorityNorm",
1667 "starred": false
1668 },
1669 "relationships": {},
1670 "type": "workflowRules"
1671 };
1672 }
1673
1674 async acclimatize(env) {
1675 this.remote = env;
1676 let preset = await this.resolveField(Preset, "preset", false, "specific");
1677 let pNext = await this.resolveField(Rule, "passNext", false, "specific");
1678 let eNext = await this.resolveField(Rule, "errorNext", false, "specific");
1679 let proType = await this.resolveField(Provider, "providerType", false, "specific");
1680 let dynamicNexts = await this.resolveField(Rule, "dynamicNexts", true, "specific");
1681 let enterNotif = await this.resolveField(Notification, "enterNotifications", true, "specific");
1682 let errorNotif = await this.resolveField(Notification, "errorNotifications", true, "specific");
1683 let passNotif = await this.resolveField(Notification, "passNotifications", true, "specific");
1684 }
1685
1686 async saveA(env) {
1687 if (lib.isLocalEnv(env)) return;
1688 return await this.createIfNotExist(env);
1689 }
1690
1691 async saveB(env) {
1692 if (!this.isGeneric) {
1693 await this.resolve();
1694 }
1695
1696 this.cleanup();
1697
1698 if (lib.isLocalEnv(env)) {
1699 log(chalk`Saving rule {green ${this.name}} to {blue ${lib.envName(env)}}.`);
1700 writeFileSync(this.localpath, JSON.stringify(this.data, null, 4));
1701 } else {
1702 await this.acclimatize(env);
1703 await this.uploadRemote(env);
1704 }
1705 }
1706
1707 get immutable() {
1708 return false;
1709 }
1710
1711 async createIfNotExist(env) {
1712 write(chalk`First pass rule {green ${this.name}} to {green ${env}}: `);
1713
1714 if (this.immutable) {
1715 log(chalk`{magenta IMMUTABLE}. Nothing to do.`);
1716 return;
1717 } //First query the api to see if this already exists.
1718
1719
1720 let remote = await Rule.getByName(env, this.name);
1721 this.idMap = this.idMap || {};
1722
1723 if (remote) {
1724 this.idMap[env] = remote.id;
1725 log(chalk`exists ${remote.chalkPrint(false)}`);
1726 return;
1727 } //If it exists we can replace it
1728
1729
1730 write("create, ");
1731 let res = await lib.makeAPIRequest({
1732 env,
1733 path: `/workflowRules`,
1734 method: "POST",
1735 payload: {
1736 data: {
1737 attributes: {
1738 name: this.name
1739 },
1740 type: "workflowRules"
1741 }
1742 }
1743 });
1744 this.idMap = this.idMap || {};
1745 this.idMap[env] = res.data.id;
1746 write("id ");
1747 log(this.idMap[env]);
1748 }
1749
1750 async patchStrip() {
1751 delete this.data.attributes.createdAt;
1752 delete this.data.attributes.starred;
1753 delete this.data.attributes.updatedAt; // TEMP FIX FOR BUG IN SDVI
1754
1755 if (this.relationships.passMetadata && this.relationships.passMetadata[0]) {
1756 log("HAS PASS");
1757 log(this.name);
1758 log("HAS PASS");
1759 }
1760
1761 delete this.relationships.passMetadata;
1762
1763 if (this.relationships.errorMetadata && this.relationships.errorMetadata[0]) {
1764 log("HAS PASS");
1765 log(this.name);
1766 log("HAS PASS");
1767 }
1768
1769 delete this.relationships.errorMetadata; // This is commented out because it was fixed.
1770 //for(let key in this.relationships){
1771 //let relationship = this.relationships[key];
1772 //if(!relationship.data || relationship.data instanceof Array && !relationship.data[0]){
1773 //delete this.relationships[key];
1774 //}
1775 //}
1776 }
1777
1778 async uploadRemote(env) {
1779 write(chalk`Uploading rule {green ${this.name}} to {green ${env}}: `);
1780
1781 if (this.immutable) {
1782 log(chalk`{magenta IMMUTABLE}. Nothing to do.`);
1783 return;
1784 }
1785
1786 if (this.idMap[env]) {
1787 this.remote = env;
1788 await this.patchStrip();
1789 this.data.id = this.idMap[env]; //If it exists we can replace it
1790
1791 write("replace, ");
1792 let res = await lib.makeAPIRequest({
1793 env,
1794 path: `/workflowRules/${this.idMap[env]}`,
1795 method: "PATCH",
1796 payload: {
1797 data: this.data
1798 },
1799 fullResponse: true
1800 });
1801 log(chalk`response {yellow ${res.statusCode}}`);
1802
1803 if (res.statusCode !== 200) {
1804 log(res.body);
1805 log(JSON.stringify(this.data, null, 4));
1806 }
1807 } else {
1808 throw Error("Bad idmap!");
1809 }
1810 }
1811
1812 get localpath() {
1813 return path.join(configObject.repodir, this.subproject || "", "silo-rules", this.name + ".json");
1814 }
1815
1816 async resolve() {
1817 let preset = await this.resolveField(Preset, "preset", false); //log(preset);
1818
1819 let pNext = await this.resolveField(Rule, "passNext", false);
1820 let eNext = await this.resolveField(Rule, "errorNext", false);
1821 let proType = await this.resolveField(Provider, "providerType", false); //log("Dynamic nexts")
1822
1823 let dynamicNexts = await this.resolveField(Rule, "dynamicNexts", true); //log(dynamicNexts);
1824
1825 let enterNotif = await this.resolveField(Notification, "enterNotifications", true);
1826 let errorNotif = await this.resolveField(Notification, "errorNotifications", true);
1827 let passNotif = await this.resolveField(Notification, "passNotifications", true); //TODO Unsupported
1828
1829 delete this.relationships["enterMetadata"];
1830 delete this.relationships["errorMetadata"];
1831 this.isGeneric = true;
1832 return {
1833 preset,
1834 proType,
1835 pNext,
1836 eNext,
1837 dynamicNexts,
1838 errorNotif,
1839 enterNotif,
1840 passNotif
1841 };
1842 }
1843
1844 chalkPrint(pad = true) {
1845 let id = String("R-" + (this.remote && this.remote + "-" + this.id || "LOCAL"));
1846 let sub = "";
1847
1848 if (this.subproject) {
1849 sub = chalk`{yellow ${this.subproject}}`;
1850 }
1851
1852 if (pad) id = id.padStart(10);
1853
1854 try {
1855 return chalk`{green ${id}}: ${sub}{blue ${this.name}}`;
1856 } catch (e) {
1857 return this.data;
1858 }
1859 }
1860
1861}
1862
1863defineAssoc(Rule, "name", "data.attributes.name");
1864defineAssoc(Rule, "description", "data.attributes.description");
1865defineAssoc(Rule, "id", "data.id");
1866defineAssoc(Rule, "relationships", "data.relationships");
1867defineAssoc(Rule, "isGeneric", "meta.isGeneric");
1868defineAssoc(Rule, "remote", "meta.remote");
1869defineAssoc(Rule, "subproject", "meta.project");
1870defineAssoc(Rule, "idMap", "meta.idMap");
1871Rule.endpoint = "workflowRules";
1872
1873//Move project into silo metadata
1874//move autotest into silo metadata
1875//
1876
1877class SupplyChain {
1878 constructor(startingRule, stopRule) {
1879 if (startingRule) {
1880 this.startingRule = startingRule;
1881 this.stopRule = stopRule;
1882 this.remote = startingRule.remote;
1883 }
1884 }
1885
1886 async downloadPresetCode(objs = this.allPresets) {
1887 log("Downloading code... ");
1888 await lib.keepalive(objs.arr.map(x => () => x.downloadCode()));
1889 }
1890
1891 async calculate() {
1892 log("Getting rules... ");
1893 this.allRules = await Rule.getAll(this.remote);
1894 log(this.allRules.length);
1895 log("Getting presets... ");
1896 this.allPresets = await Preset.getAll(this.remote);
1897 log(this.allPresets.length);
1898 log("Getting providers... ");
1899 this.allProviders = await Provider.getAll(this.remote);
1900 log(this.allProviders.length);
1901 log("Getting notifications... ");
1902 this.allNotifications = await Notification.getAll(this.remote);
1903 log(this.allNotifications.length);
1904
1905 if (!this.startingRule) {
1906 this.rules = this.allRules;
1907 this.presets = this.allPresets;
1908 this.notifications = new Collection([]);
1909 await this.downloadPresetCode();
1910 return;
1911 } else {
1912 await this.downloadPresetCode();
1913 }
1914
1915 log("Done!"); //Now we have everything we need to find a whole supply chain
1916
1917 write("Calculating Supply chain... ");
1918 log(this.startingRule.chalkPrint());
1919 let allRuleNames = this.allRules.arr.map(x => x.name).filter(x => x.length >= 4);
1920 let allPresetNames = this.allPresets.arr.map(x => x.name).filter(x => x.length >= 4);
1921 let allNotifNames = this.allNotifications.arr.map(x => x.name).filter(x => x.length >= 4);
1922 let requiredNotifications = new Set();
1923 let ruleQueue = [this.startingRule];
1924 let presetQueue = [];
1925
1926 for (let currentRule of ruleQueue) {
1927 if (currentRule === this.stopRule) continue;
1928 let {
1929 eNext,
1930 pNext,
1931 preset,
1932 passNotif,
1933 errorNotif,
1934 enterNotif
1935 } = await currentRule.resolve();
1936 passNotif.forEach(n => requiredNotifications.add(n));
1937 enterNotif.forEach(n => requiredNotifications.add(n));
1938 errorNotif.forEach(n => requiredNotifications.add(n));
1939 if (eNext && !ruleQueue.includes(eNext)) ruleQueue.push(eNext);
1940 if (pNext && !ruleQueue.includes(eNext)) ruleQueue.push(pNext);
1941 let neededPresets = preset.findStringsInCode(allPresetNames);
1942 neededPresets = neededPresets.map(x => this.allPresets.findByName(x));
1943 let neededRules = preset.findStringsInCode(allRuleNames);
1944 neededRules = neededRules.map(x => this.allRules.findByName(x));
1945 preset.findStringsInCode(allNotifNames).map(str => this.allNotifications.findByName(str)).forEach(notif => requiredNotifications.add(notif));
1946 neededPresets.push(preset);
1947
1948 for (let p of neededPresets) if (!presetQueue.includes(p)) presetQueue.push(p);
1949
1950 for (let p of neededRules) if (!ruleQueue.includes(p)) ruleQueue.push(p);
1951
1952 if (configObject.verbose) {
1953 write(currentRule.chalkPrint(false));
1954 log(":");
1955 write(" ");
1956 write(preset.chalkPrint(false));
1957 log(":");
1958 write(" Pass Next: ");
1959 if (pNext) write(pNext.chalkPrint(false));else write("None");
1960 log("");
1961 write(" Err Next: ");
1962 if (eNext) write(eNext.chalkPrint(false));else write("None");
1963 log("");
1964 log(" Rules:");
1965
1966 for (let p of neededRules) log(" " + p.chalkPrint(true));
1967
1968 log(" Presets:");
1969
1970 for (let p of neededPresets) log(" " + p.chalkPrint(true));
1971
1972 log("\n");
1973 }
1974 }
1975
1976 log("Done!");
1977 this.rules = new Collection(ruleQueue);
1978 this.presets = new Collection(presetQueue);
1979 requiredNotifications.delete(undefined);
1980 this.notifications = new Collection([...requiredNotifications]);
1981 }
1982
1983 async log() {
1984 if (this.notifications.arr.length > 0) {
1985 log("Required notifications: ");
1986 this.notifications.log();
1987 }
1988
1989 if (this.rules.arr.length > 0) {
1990 write("Required rules: ");
1991 log(this.rules.arr.length);
1992 this.rules.log();
1993 }
1994
1995 if (this.presets.arr.length > 0) {
1996 write("Required presets: ");
1997 log(this.presets.arr.length);
1998 this.presets.log();
1999 }
2000
2001 if (configObject.rawOutput) {
2002 return {
2003 presets: this.presets.arr,
2004 rules: this.rules.arr,
2005 notifications: this.notifications.arr
2006 };
2007 }
2008 }
2009
2010 async syncTo(env) {
2011 for (let preset of this.presets) {
2012 try {
2013 await preset.save(env);
2014 } catch (e) {
2015 log(e);
2016 }
2017 }
2018
2019 if (this.rules.arr[0]) {
2020 log("Starting create phase for rules");
2021
2022 for (let rule of this.rules) {
2023 try {
2024 await rule.saveA(env);
2025 } catch (e) {
2026 log(e);
2027 }
2028 }
2029
2030 log("OK");
2031 log("Starting link phase for rules");
2032 Rule.removeCache(env);
2033
2034 for (let rule of this.rules) {
2035 try {
2036 await rule.saveB(env);
2037 } catch (e) {
2038 log(e);
2039 }
2040 }
2041 }
2042 }
2043
2044}
2045
2046class User extends RallyBase {
2047 constructor({
2048 data,
2049 remote
2050 }) {
2051 super();
2052 this.data = data;
2053 this.meta = {};
2054 this.remote = remote;
2055 }
2056
2057 chalkPrint(pad = false) {
2058 let id = String("U-" + this.id);
2059 if (pad) id = id.padStart(7);
2060 return chalk`{green ${id}}: {blue ${this.name}}`;
2061 }
2062
2063}
2064
2065defineAssoc(User, "id", "data.id");
2066defineAssoc(User, "name", "data.attributes.name");
2067defineAssoc(User, "email", "data.attributes.email");
2068defineAssoc(User, "remote", "meta.remote");
2069User.endpoint = "users";
2070
2071class Tag extends RallyBase {
2072 constructor({
2073 data,
2074 remote
2075 } = {}) {
2076 super();
2077 this.meta = {};
2078 this.remote = remote;
2079 this.data = data; //this.data.attributes.rallyConfiguration = undefined;
2080 //this.data.attributes.systemManaged = undefined;
2081 }
2082
2083 chalkPrint(pad = true) {
2084 let id = String("T-" + this.remote + "-" + this.id);
2085 if (pad) id = id.padStart(10);
2086 let prefix = this.curated ? "blue +" : "red -";
2087 return chalk`{green ${id}}: {${prefix}${this.name}}`;
2088 }
2089
2090 static async create(env, name, {
2091 notCurated
2092 } = {}) {
2093 return new Tag({
2094 data: await lib.makeAPIRequest({
2095 env,
2096 path: `/${this.endpoint}`,
2097 method: "POST",
2098 payload: {
2099 data: {
2100 attributes: {
2101 name,
2102 curated: notCurated ? false : true
2103 },
2104 type: "tagNames"
2105 }
2106 }
2107 }),
2108 remote: env
2109 });
2110 }
2111
2112}
2113
2114defineAssoc(Tag, "id", "data.id");
2115defineAssoc(Tag, "attributes", "data.attributes");
2116defineAssoc(Tag, "relationships", "data.relationships");
2117defineAssoc(Tag, "name", "data.attributes.name");
2118defineAssoc(Tag, "curated", "data.attributes.curated");
2119defineAssoc(Tag, "remote", "meta.remote");
2120Tag.endpoint = "tagNames";
2121
2122require("source-map-support").install();
2123const rallyFunctions = {
2124 async bestPagintation() {
2125 global.silentAPI = true;
2126
2127 for (let i = 10; i <= 30; i += 5) {
2128 console.time("test with " + i);
2129 let dl = await lib.indexPathFast("DEV", `/workflowRules?page=1p${i}`);
2130 console.timeEnd("test with " + i);
2131 }
2132 },
2133
2134 async uploadPresets(env, presets, createFunc = () => false) {
2135 for (let preset of presets) {
2136 await preset.uploadCodeToEnv(env, createFunc);
2137 }
2138 },
2139
2140 //Dummy test access
2141 async testAccess(env) {
2142 if (lib.isLocalEnv(env)) {
2143 //TODO
2144 return true;
2145 }
2146
2147 let result = await lib.makeAPIRequest({
2148 env,
2149 path: "/providers?page=1p1",
2150 fullResponse: true,
2151 timeout: 1000
2152 });
2153 return result.statusCode;
2154 }
2155
2156};
2157
2158var allIndexBundle = /*#__PURE__*/Object.freeze({
2159 rallyFunctions: rallyFunctions,
2160 SupplyChain: SupplyChain,
2161 Preset: Preset,
2162 Rule: Rule,
2163 Provider: Provider,
2164 Notification: Notification,
2165 Asset: Asset,
2166 User: User,
2167 Tag: Tag,
2168 get configFile () { return configFile; },
2169 loadConfig: loadConfig,
2170 setConfig: setConfig,
2171 get configObject () { return configObject; },
2172 lib: lib,
2173 AbortError: AbortError,
2174 APIError: APIError,
2175 UnconfiguredEnvError: UnconfiguredEnvError,
2176 ProtectedEnvError: ProtectedEnvError,
2177 FileTooLargeError: FileTooLargeError,
2178 Collection: Collection,
2179 RallyBase: RallyBase
2180});
2181
2182var version = "1.14.0";
2183
2184async function findLineInFile(renderedPreset, lineNumber) {
2185 let trueFileLine = lineNumber;
2186 let linedRenderedPreset = renderedPreset.split("\n").slice(2, -2);
2187 renderedPreset = renderedPreset.split("\n").slice(2, -2).join("\n");
2188 let includeLocation = renderedPreset.split("\n").filter(x => x.includes("@include"));
2189 let endIncludeNumber = -1,
2190 addTabDepth = 2;
2191 let lineBeforeIncludeStatement = '';
2192 let withinInclude = true;
2193
2194 if (lineNumber > linedRenderedPreset.indexOf(includeLocation[includeLocation.length - 1])) {
2195 addTabDepth = 0;
2196 withinInclude = false;
2197 }
2198
2199 for (let index = includeLocation.length - 1; index >= 0; index--) {
2200 let currIncludeIndex = linedRenderedPreset.indexOf(includeLocation[index]);
2201 let tabDepth = includeLocation[index].split(" ").length;
2202
2203 if (lineNumber > currIncludeIndex) {
2204 if (includeLocation[index].split(" ").filter(Boolean)[1] != "ERROR:") {
2205 if (lineBeforeIncludeStatement.split(" ").length == tabDepth && withinInclude) {
2206 trueFileLine = trueFileLine - currIncludeIndex;
2207 break;
2208 } else if (lineBeforeIncludeStatement.split(" ").length + addTabDepth == tabDepth && endIncludeNumber == -1) {
2209 endIncludeNumber = currIncludeIndex;
2210 } else if (lineBeforeIncludeStatement.split(" ").length + addTabDepth == tabDepth) {
2211 trueFileLine = trueFileLine - (endIncludeNumber - currIncludeIndex);
2212 endIncludeNumber = -1;
2213 }
2214 }
2215 } else {
2216 lineBeforeIncludeStatement = includeLocation[index];
2217 }
2218 }
2219
2220 let funcLine = "";
2221
2222 for (let line of linedRenderedPreset.slice(0, lineNumber).reverse()) {
2223 let match = /def (\w+)/.exec(line);
2224
2225 if (match) {
2226 funcLine = match[1];
2227 break;
2228 }
2229 }
2230
2231 let includeFilename;
2232
2233 if (lineBeforeIncludeStatement != "") {
2234 includeFilename = lineBeforeIncludeStatement.slice(1).trim().slice(14, -1);
2235 } else {
2236 includeFilename = null;
2237 }
2238
2239 return {
2240 lineNumber: trueFileLine - 1,
2241 includeFilename,
2242 line: linedRenderedPreset[lineNumber - 1],
2243 funcLine
2244 };
2245}
2246
2247function printOutLine(eLine) {
2248 return log(chalk`{blue ${eLine.includeFilename || "Main"}}:{green ${eLine.lineNumber}} in ${eLine.funcLine}
2249${eLine.line}`);
2250}
2251async function parseTrace(env, jobid) {
2252 let trace = lib.makeAPIRequest({
2253 env,
2254 path: `/jobs/${jobid}/artifacts/trace`
2255 }).catch(x => null);
2256 let renderedPreset = lib.makeAPIRequest({
2257 env,
2258 path: `/jobs/${jobid}/artifacts/preset`
2259 }).catch(x => null);
2260 let result = lib.makeAPIRequest({
2261 env,
2262 path: `/jobs/${jobid}/artifacts/result`
2263 }).catch(x => null);
2264 let error = lib.makeAPIRequest({
2265 env,
2266 path: `/jobs/${jobid}/artifacts/error`
2267 }).catch(x => null);
2268 let output = lib.makeAPIRequest({
2269 env,
2270 path: `/jobs/${jobid}/artifacts/output`
2271 }).catch(x => null);
2272 [trace, renderedPreset, result, output, error] = await Promise.all([trace, renderedPreset, result, output, error]);
2273 let lineNumber = -1;
2274 let errorLines = [];
2275 let shouldBreak = 0;
2276
2277 for (let tr of trace.split("\n\n").reverse()) {
2278 errorLines.push(tr);
2279 shouldBreak--;
2280 if (tr.includes("Execption")) shouldBreak = 1;
2281 if (tr.includes("raised on")) shouldBreak = 1;
2282 if (!shouldBreak) break;
2283 }
2284
2285 let errorList = [];
2286
2287 for (let errLine of errorLines) {
2288 lineNumber = /\d+/g.exec(errLine);
2289
2290 if (lineNumber && lineNumber[0]) {
2291 errorList.push((await findLineInFile(renderedPreset, lineNumber)));
2292 } else {
2293 errorList.push(errLine);
2294 }
2295 }
2296
2297 return errorList;
2298}
2299
2300var baseCode = {
2301 SdviContentMover: `{
2302 "tasks": [
2303 {
2304 "operation": "move",
2305 "source": {
2306 "labels": [ "MyLabel" ],
2307 "tags": [ "segmented" ]
2308 "storageSet": [ "*", "-OtherStorageBucket" ],
2309
2310 },
2311 "destination": {
2312 "storage": "Archive",
2313 "newLabel": "myNewLabel",
2314 "overwrite": "always"
2315 }
2316 }
2317 ]
2318}`,
2319 SdviEvaluate: `'''
2320name: {name}
2321'''
2322
2323# code here`
2324};
2325
2326const inquirer = importLazy("inquirer");
2327const readdir = importLazy("recursive-readdir");
2328let hasAutoCompletePrompt = false;
2329function addAutoCompletePrompt() {
2330 if (hasAutoCompletePrompt) return;
2331 hasAutoCompletePrompt = true;
2332 inquirer.registerPrompt("autocomplete", require("inquirer-autocomplete-prompt"));
2333}
2334async function $api(propArray) {
2335 let q;
2336 q = await inquirer.prompt([{
2337 type: "input",
2338 name: "company",
2339 message: `What is your company?`,
2340 default: `discovery`
2341 }]);
2342 let company = q.company;
2343 const defaults = {
2344 DEV: `https://${company}-dev.sdvi.com/api/v2`,
2345 UAT: `https://${company}-uat.sdvi.com/api/v2`,
2346 QA: `https://${company}-qa.sdvi.com/api/v2`,
2347 PROD: `https://${company}.sdvi.com/api/v2`
2348 };
2349
2350 if (propArray && propArray[1]) {
2351 q = {
2352 envs: [propArray[1]]
2353 };
2354 } else {
2355 //Create a checkbox prompt to choose enviornments
2356 q = await inquirer.prompt([{
2357 type: "checkbox",
2358 name: "envs",
2359 message: `What enviornments would you like to configure?`,
2360 choices: Object.keys(defaults).map(name => ({
2361 name,
2362 checked: true
2363 }))
2364 }]);
2365 } //Each env should ask 2 for two things: The url and the key.
2366
2367
2368 let questions = q.envs.map(env => {
2369 let defaultKey = process.env[`rally_api_key_${env}`];
2370
2371 if (configObject && configObject.api && configObject.api[env]) {
2372 defaultKey = configObject.api[env].key;
2373 }
2374
2375 return [{
2376 type: "input",
2377 name: `api.${env}.url`,
2378 message: `What is the api endpoint for ${env}?`,
2379 default: defaults[env]
2380 }, {
2381 type: "input",
2382 name: `api.${env}.key`,
2383 message: `What is your api key for ${env}?`,
2384 default: defaultKey
2385 }];
2386 }); //flatten and ask
2387
2388 questions = [].concat(...questions);
2389 q = await inquirer.prompt(questions);
2390
2391 if (propArray) {
2392 q.api = { ...configObject.api,
2393 ...q.api
2394 };
2395 }
2396
2397 return q;
2398}
2399async function $chalk(propArray) {
2400 return {
2401 chalk: await askQuestion("Would you like chalk enabled (Adds coloring)?")
2402 };
2403}
2404async function $restrictUAT(propArray) {
2405 return {
2406 restrictUAT: await askQuestion("Would you like to protect UAT?")
2407 };
2408}
2409async function $repodir(propArray) {
2410 return await inquirer.prompt([{
2411 type: "input",
2412 name: `repodir`,
2413 message: `Where is your rally repository?`,
2414 default: process.env["rally_repo_path"]
2415 }]);
2416}
2417async function $appName(propArray) {
2418 let defaultAppName = "cmdline-" + (process.env.USERNAME || process.env.LOGNAME);
2419 let project = await askInput("Application name?", defaultAppName);
2420
2421 if (project === "none" || project === "-" || project === "" || !project) {
2422 project = null;
2423 }
2424
2425 return {
2426 appName: project
2427 };
2428}
2429async function $project(propArray) {
2430 let project = await askInput("Subproject directory?");
2431
2432 if (project === "none" || project === "-" || project === "" || !project) {
2433 project = null;
2434 }
2435
2436 return {
2437 project
2438 };
2439}
2440async function $defaultEnv(propArray) {
2441 return await inquirer.prompt([{
2442 type: "input",
2443 name: `defaultEnv`,
2444 message: `Default enviornment?`,
2445 default: "DEV"
2446 }]);
2447} //Internal usage/testing
2448
2449async function selectProvider(providers, autoDefault = false) {
2450 addAutoCompletePrompt();
2451 let defaultProvider = providers.findByName("SdviEvaluate");
2452
2453 if (autoDefault) {
2454 return defaultProvider;
2455 } else {
2456 let choices = providers.arr.map(x => ({
2457 name: x.chalkPrint(true),
2458 value: x
2459 }));
2460 let q = await inquirer.prompt([{
2461 type: "autocomplete",
2462 name: "provider",
2463 default: defaultProvider,
2464 source: async (sofar, input) => {
2465 return choices.filter(x => input ? x.value.name.toLowerCase().includes(input.toLowerCase()) : true);
2466 }
2467 }]);
2468 return q.provider;
2469 }
2470}
2471async function selectLocal(path, typeName, Class) {
2472 addAutoCompletePrompt();
2473 let basePath = configObject.repodir;
2474 let f = await readdir(basePath);
2475 let objs = f.filter(name => name.includes(path)).map(name => new Class({
2476 path: name
2477 }));
2478 let objsMap = objs.map(x => ({
2479 name: x.chalkPrint(true),
2480 value: x
2481 }));
2482 let none = {
2483 name: chalk` {red None}: {red None}`,
2484 value: null
2485 };
2486 objsMap.unshift(none);
2487 let q = await inquirer.prompt([{
2488 type: "autocomplete",
2489 name: "obj",
2490 message: `What ${typeName} do you want?`,
2491 source: async (sofar, input) => {
2492 return objsMap.filter(x => input ? x.name.toLowerCase().includes(input.toLowerCase()) : true);
2493 }
2494 }]);
2495 return q.obj;
2496}
2497async function selectPreset(purpose = "preset") {
2498 return selectLocal("silo-presets", purpose, Preset);
2499}
2500async function selectRule(purpose = "rule") {
2501 return selectLocal("silo-rules", purpose, Rule);
2502}
2503async function askInput(question, def) {
2504 return (await inquirer.prompt([{
2505 type: "input",
2506 name: "ok",
2507 message: question,
2508 default: def
2509 }])).ok;
2510}
2511async function askQuestion(question) {
2512 return (await inquirer.prompt([{
2513 type: "confirm",
2514 name: "ok",
2515 message: question
2516 }])).ok;
2517}
2518
2519var configHelpers = /*#__PURE__*/Object.freeze({
2520 inquirer: inquirer,
2521 addAutoCompletePrompt: addAutoCompletePrompt,
2522 $api: $api,
2523 $chalk: $chalk,
2524 $restrictUAT: $restrictUAT,
2525 $repodir: $repodir,
2526 $appName: $appName,
2527 $project: $project,
2528 $defaultEnv: $defaultEnv,
2529 selectProvider: selectProvider,
2530 selectLocal: selectLocal,
2531 selectPreset: selectPreset,
2532 selectRule: selectRule,
2533 askInput: askInput,
2534 askQuestion: askQuestion
2535});
2536
2537var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _dec25, _dec26, _dec27, _dec28, _dec29, _dec30, _dec31, _dec32, _dec33, _dec34, _dec35, _dec36, _dec37, _dec38, _dec39, _dec40, _dec41, _dec42, _dec43, _dec44, _dec45, _dec46, _dec47, _dec48, _obj;
2538
2539require("source-map-support").install();
2540let argv = argparse(process.argv.slice(2), {
2541 string: ["file", "env"],
2542 //boolean: ["no-protect"],
2543 boolean: ["anon"],
2544 default: {
2545 protect: true
2546 },
2547 alias: {
2548 f: "file",
2549 e: "env"
2550 }
2551}); //help menu helper
2552
2553function printHelp(help, short) {
2554 let helpText = chalk`
2555{white ${help.name}}: ${help.text}
2556 Usage: ${help.usage || "<unknown>"}
2557`; //Trim newlines
2558
2559 helpText = helpText.substring(1, helpText.length - 1);
2560
2561 if (!short) {
2562 for (let param of help.params || []) {
2563 helpText += chalk`\n {blue ${param.param}}: ${param.desc}`;
2564 }
2565
2566 for (let arg of help.args || []) {
2567 helpText += chalk`\n {blue ${arg.short}}, {blue ${arg.long}}: ${arg.desc}`;
2568 }
2569 }
2570
2571 return helpText;
2572}
2573
2574async function getFilesFromArgs(args) {
2575 let lastArg = args._.shift();
2576
2577 if (args.file) {
2578 let files = args.file;
2579 if (typeof files === "string") files = [files];
2580 return files;
2581 }
2582
2583 if (lastArg == "-") {
2584 log("Reading from stdin");
2585
2586 let getStdin = require("get-stdin");
2587
2588 let stdin = await getStdin();
2589 let files = stdin.split("\n");
2590 if (files[files.length - 1] === "") files.pop();
2591 return files;
2592 } else {
2593 args._.push(lastArg);
2594 }
2595}
2596
2597let presetsub = {
2598 async before(args) {
2599 this.env = args.env;
2600 if (!this.env) throw new AbortError("No env supplied");
2601 this.files = await getFilesFromArgs(args);
2602 },
2603
2604 async $grab(args) {
2605 if (!this.files) {
2606 throw new AbortError("No files provided to grab (use --file argument)");
2607 }
2608
2609 log(chalk`Grabbing {green ${this.files.length}} preset(s) metadata from {green ${this.env}}.`);
2610 let presets = this.files.map(path => new Preset({
2611 path,
2612 remote: false
2613 }));
2614
2615 for (let preset of presets) {
2616 //TODO small refactor
2617 await preset.grabMetadata(this.env);
2618 await preset.saveLocalMetadata();
2619
2620 if (args.full) {
2621 let remo = await Preset.getByName(this.env, preset.name);
2622 await remo.resolve();
2623 await remo.downloadCode();
2624 await remo.saveLocalFile();
2625 }
2626 }
2627 },
2628
2629 async $create(args) {
2630 let provider, name, ext;
2631
2632 if (args.provider) {
2633 provider = {
2634 name: args.provider
2635 };
2636 ext = args.ext;
2637 } else {
2638 provider = await selectProvider((await Provider.getAll(this.env)));
2639 ext = (await provider.getEditorConfig()).fileExt;
2640 }
2641
2642 if (args.name) {
2643 name = args.name;
2644 } else {
2645 name = await askInput("Preset Name", "What is the preset name?");
2646 }
2647
2648 let preset = new Preset({
2649 subProject: configObject.project
2650 });
2651 preset.providerType = {
2652 name: provider.name
2653 };
2654 preset.isGeneric = true;
2655 preset.name = name;
2656 preset.ext = ext;
2657
2658 if (baseCode[provider.name]) {
2659 preset._code = baseCode[provider.name].replace("{name}", name);
2660 } else {
2661 preset._code = " ";
2662 }
2663
2664 preset.saveLocalMetadata();
2665 preset.saveLocalFile();
2666 },
2667
2668 async $list(args) {
2669 log("Loading...");
2670 let presets = await Preset.getAll(this.env);
2671
2672 if (args.resolve) {
2673 Provider.getAll(this.env);
2674
2675 for (let preset of presets) {
2676 let resolve = await preset.resolve(this.env);
2677
2678 if (args.attach) {
2679 let {
2680 proType
2681 } = resolve;
2682 proType.editorConfig.helpText = "";
2683 preset.meta = { ...preset.meta,
2684 proType
2685 };
2686 }
2687 }
2688 }
2689
2690 if (configObject.rawOutput) return presets;
2691 log(chalk`{yellow ${presets.length}} presets on {green ${this.env}}.`);
2692 presets.arr.sort((a, b) => {
2693 return Number(a.attributes.updatedAt) - Number(b.attributes.updatedAt);
2694 });
2695
2696 for (let preset of presets) {
2697 log(preset.chalkPrint());
2698 }
2699 },
2700
2701 async $upload(args) {
2702 if (!this.files) {
2703 throw new AbortError("No files provided to upload (use --file argument)");
2704 }
2705
2706 log(chalk`Uploading {green ${this.files.length}} preset(s) to {green ${this.env}}.`);
2707 let presets = this.files.map(path => new Preset({
2708 path,
2709 remote: false
2710 }));
2711 await rallyFunctions.uploadPresets(this.env, presets);
2712 },
2713
2714 async $diff(args) {
2715 let file = this.files[0];
2716
2717 if (!this.files) {
2718 throw new AbortError("No files provided to diff (use --file argument)");
2719 }
2720
2721 let preset = new Preset({
2722 path: file,
2723 remote: false
2724 });
2725
2726 if (!preset.name) {
2727 throw new AbortError(chalk`No preset header found. Cannot get name.`);
2728 }
2729
2730 let preset2 = await Preset.getByName(this.env, preset.name);
2731
2732 if (!preset2) {
2733 throw new AbortError(chalk`No preset found with name {red ${preset.name}} on {blue ${this.env}}`);
2734 }
2735
2736 await preset2.downloadCode();
2737
2738 let tempfile = require("tempy").file;
2739
2740 let temp = tempfile({
2741 extension: `${this.env}.${preset.ext}`
2742 });
2743 fs.writeFileSync(temp, preset2.code);
2744 let ptr = `${file},${temp}`; //raw output returns "file1" "file2"
2745
2746 if (configObject.rawOutput) {
2747 if (args["only-new"]) return temp;else return ptr;
2748 } //standard diff
2749
2750
2751 argv.command = argv.command || "diff";
2752 await spawn(argv.command, [file, temp], {
2753 stdio: "inherit"
2754 });
2755 },
2756
2757 async unknown(arg, args) {
2758 log(chalk`Unknown action {red ${arg}} try '{white rally help preset}'`);
2759 }
2760
2761};
2762let rulesub = {
2763 async before(args) {
2764 this.env = args.env;
2765 if (!this.env) throw new AbortError("No env supplied");
2766 },
2767
2768 async $list(args) {
2769 log("Loading...");
2770 let rules = await Rule.getAll(this.env);
2771 if (configObject.rawOutput) return rules;
2772 log(chalk`{yellow ${rules.length}} rules on {green ${this.env}}.`);
2773 rules.arr.sort((a, b) => {
2774 return Number(a.data.attributes.updatedAt) - Number(b.data.attributes.updatedAt);
2775 });
2776
2777 for (let rule of rules) log(rule.chalkPrint());
2778 },
2779
2780 async $create(args) {
2781 let preset = await selectPreset();
2782 let passNext = await selectRule("'On Exit OK'");
2783 let errorNext = await selectRule("'On Exit Error'");
2784 let name = await askInput("Rule Name", "What is the rule name?");
2785 name = name.replace("@", preset.name);
2786 let desc = await askInput("Description", "Enter a description.");
2787 let dynamicNexts = [];
2788 let next;
2789
2790 while (next = await selectRule("dynamic next")) {
2791 let name = await askInput("Key", "Key name for dynamic next");
2792 dynamicNexts.push({
2793 meta: {
2794 transition: name
2795 },
2796 type: "workflowRules",
2797 name: next.name
2798 });
2799 }
2800
2801 let rule = new Rule({
2802 subProject: configObject.project
2803 });
2804 rule.name = name;
2805 rule.description = desc;
2806 rule.relationships.preset = {
2807 data: {
2808 name: preset.name,
2809 type: "presets"
2810 }
2811 };
2812 if (errorNext) rule.relationships.errorNext = {
2813 data: {
2814 name: errorNext.name,
2815 type: "workflowRules"
2816 }
2817 };
2818 if (passNext) rule.relationships.passNext = {
2819 data: {
2820 name: passNext.name,
2821 type: "workflowRules"
2822 }
2823 };
2824
2825 if (dynamicNexts[0]) {
2826 rule.relationships.dynamicNexts = {
2827 data: dynamicNexts
2828 };
2829 }
2830
2831 rule.saveB();
2832 },
2833
2834 async unknown(arg, args) {
2835 log(chalk`Unknown action {red ${arg}} try '{white rally help rule}'`);
2836 }
2837
2838};
2839let jupytersub = {
2840 async before(args) {
2841 this.input = args._.shift() || "main.ipynb";
2842 this.output = args._.shift() || "main.py";
2843 },
2844
2845 async $build(args) {
2846 let cmd = `jupyter nbconvert --to python ${this.input} --TagRemovePreprocessor.remove_cell_tags={\"remove_cell\"} --output ${this.output} --TemplateExporter.exclude_markdown=True --TemplateExporter.exclude_input_prompt=True --TemplateExporter.exclude_output_prompt=True`.split(" ");
2847 log(chalk`Compiling GCR file {green ${this.input}} into {green ${this.output}} using jupyter...`);
2848
2849 try {
2850 let {
2851 timestr
2852 } = await spawn(cmd[0], cmd.slice(1));
2853 log(chalk`Complete in ~{green.bold ${timestr}}.`);
2854 } catch (e) {
2855 if (e.code !== "ENOENT") throw e;
2856 log(chalk`Cannot run the build command. Make sure that you have jupyter notebook installed.\n{green pip install jupyter}`);
2857 return;
2858 }
2859 }
2860
2861};
2862
2863async function categorizeString(str, defaultSubproject = undefined) {
2864 str = str.trim();
2865
2866 if (str.startsWith('"')) {
2867 str = str.slice(1, -1);
2868 }
2869
2870 let match;
2871
2872 if (match = /^(\w)-(\w{1,10})-(\d{1,10}):/.exec(str)) {
2873 if (match[1] === "P") {
2874 let ret = await Preset.getById(match[2], match[3]); //TODO modify for subproject a bit
2875
2876 return ret;
2877 } else if (match[1] === "R") {
2878 return await Rule.getById(match[2], match[3]);
2879 } else {
2880 return null;
2881 }
2882 } else if (match = /^([\w \/\\\-_]*)[\/\\]?silo\-(\w+)[\/\\]/.exec(str)) {
2883 try {
2884 switch (match[2]) {
2885 case "presets":
2886 return new Preset({
2887 path: str,
2888 subProject: match[1]
2889 });
2890
2891 case "rules":
2892 return new Rule({
2893 path: str,
2894 subProject: match[1]
2895 });
2896
2897 case "metadata":
2898 return await Preset.fromMetadata(str, match[1]);
2899 }
2900 } catch (e) {
2901 log(e);
2902 }
2903 } else {
2904 return null;
2905 }
2906}
2907
2908let tagsub = {
2909 async before(args) {
2910 this.env = args.env;
2911 if (!this.env) throw new AbortError("No env supplied");
2912 },
2913
2914 async $list(args) {
2915 log("Loading...");
2916 let tags = await Tag.getAll(this.env);
2917 if (configObject.rawOutput) return tags;
2918 log(chalk`{yellow ${tags.length}} tags on {green ${this.env}}.`);
2919 tags.arr.sort((a, b) => {
2920 return Number(a.data.attributes.updatedAt) - Number(b.data.attributes.updatedAt);
2921 });
2922
2923 for (let tag of tags) log(tag.chalkPrint());
2924 },
2925
2926 async $create(args) {
2927 return Tag.create(this.env, "testTag");
2928 }
2929
2930};
2931let supplysub = {
2932 async before(args) {
2933 this.env = args.env;
2934 if (!this.env) throw new AbortError("No env supplied");
2935 this.files = await getFilesFromArgs(args);
2936 },
2937
2938 //Calculate a supply chain based on a starting rule at the top of the stack
2939 async $calc(args) {
2940 let name = args._.shift();
2941
2942 let stopName = args._.shift();
2943
2944 if (!name) {
2945 throw new AbortError("No starting rule or @ supplied");
2946 }
2947
2948 if (name === "@") {
2949 log(chalk`Silo clone started`);
2950 this.chain = new SupplyChain();
2951 this.chain.remote = args.env;
2952 } else {
2953 let rules = await Rule.getAll(this.env);
2954 let stop, start;
2955 start = rules.findByNameContains(name);
2956 if (stopName) stop = rules.findByNameContains(stopName);
2957
2958 if (!start) {
2959 throw new AbortError(chalk`No starting rule found by name {blue ${name}}`);
2960 }
2961
2962 log(chalk`Analzying supply chain: ${start.chalkPrint(false)} - ${stop ? stop.chalkPrint(false) : "(open)"}`);
2963 this.chain = new SupplyChain(start, stop);
2964 }
2965
2966 await this.chain.calculate();
2967 return await this.postAction(args);
2968 },
2969
2970 async postAction(args) {
2971 //Now that we ahve a supply chain object, do something with it
2972 if (args["to"]) {
2973 this.chain.log();
2974
2975 if (this.chain.presets.arr[0]) {
2976 await this.chain.downloadPresetCode(this.chain.presets);
2977 log("Done");
2978 }
2979
2980 if (Array.isArray(args["to"])) {
2981 for (let to of args["to"]) {
2982 await this.chain.syncTo(to);
2983 }
2984 } else {
2985 await this.chain.syncTo(args["to"]);
2986 }
2987 } else if (args["diff"]) {
2988 //Very basic diff
2989 let env = args["diff"];
2990 await Promise.all(this.chain.presets.arr.map(obj => obj.downloadCode()));
2991 await Promise.all(this.chain.presets.arr.map(obj => obj.resolve()));
2992 let otherPresets = await Promise.all(this.chain.presets.arr.map(obj => Preset.getByName(env, obj.name)));
2993 otherPresets = new Collection(otherPresets.filter(x => x));
2994 await Promise.all(otherPresets.arr.map(obj => obj.downloadCode()));
2995 await Promise.all(otherPresets.arr.map(obj => obj.resolve()));
2996
2997 const printPresets = (preset, otherPreset) => {
2998 log(preset.chalkPrint(true));
2999
3000 if (otherPreset.name) {
3001 log(otherPreset.chalkPrint(true));
3002 } else {
3003 log(chalk`{red (None)}`);
3004 }
3005 };
3006
3007 for (let preset of this.chain.presets) {
3008 let otherPreset = otherPresets.arr.find(x => x.name === preset.name) || {};
3009
3010 if (preset.code === otherPreset.code) {
3011 if (!args["ignore-same"]) {
3012 printPresets(preset, otherPreset);
3013 log("Code Same");
3014 }
3015 } else {
3016 printPresets(preset, otherPreset);
3017
3018 if (args["ignore-same"]) {
3019 log("-------");
3020 } else {
3021 log("Code Different");
3022 }
3023 }
3024 }
3025 } else {
3026 return await this.chain.log();
3027 }
3028 },
3029
3030 async $make(args) {
3031 let set = new Set();
3032 let hints = args.hint ? Array.isArray(args.hint) ? args.hint : [args.hint] : []; //TODO modify for better hinting, and add this elsewhere
3033
3034 for (let hint of hints) {
3035 if (hint === "presets-uat") {
3036 log("got hint");
3037 await Preset.getAll("UAT");
3038 }
3039 }
3040
3041 for (let file of this.files) {
3042 set.add((await categorizeString(file)));
3043 }
3044
3045 let files = [...set];
3046 files = files.filter(f => f && !f.missing);
3047 this.chain = new SupplyChain();
3048 this.chain.rules = new Collection(files.filter(f => f instanceof Rule));
3049 this.chain.presets = new Collection(files.filter(f => f instanceof Preset));
3050 this.chain.notifications = new Collection([]);
3051 return await this.postAction(args);
3052 },
3053
3054 async unknown(arg, args) {
3055 log(chalk`Unknown action {red ${arg}} try '{white rally help supply}'`);
3056 }
3057
3058};
3059
3060function subCommand(object) {
3061 object = {
3062 before() {},
3063
3064 after() {},
3065
3066 unknown() {},
3067
3068 ...object
3069 };
3070 return async function (args) {
3071 //Grab the next arg on the stack, find a function tied to it, and run
3072 let arg = args._.shift();
3073
3074 let key = "$" + arg;
3075 let ret;
3076
3077 if (object[key]) {
3078 await object.before(args);
3079 ret = await object[key](args);
3080 await object.after(args);
3081 } else {
3082 if (arg === undefined) arg = "(None)";
3083 object.unknown(arg, args);
3084 }
3085
3086 return ret;
3087 };
3088}
3089
3090let cli = (_dec = helpText(`Display the help menu`), _dec2 = usage(`rally help [subhelp]`), _dec3 = param("subhelp", "The name of the command to see help for"), _dec4 = helpText("Rally tools jupyter interface. Requires jupyter to be installed."), _dec5 = usage("rally jupyter build [in] [out]"), _dec6 = param("in/out", "input and output file for jupyter. By default main.ipyrb and main.py"), _dec7 = helpText(`Preset related actions`), _dec8 = usage(`rally preset [action] --env <enviornment> --file [file1] --file [file2] ...`), _dec9 = param("action", "The action to perform. Can be upload, diff, list"), _dec10 = arg("-e", "--env", "The enviornment you wish to perform the action on"), _dec11 = arg("-f", "--file", "A file to act on"), _dec12 = arg("~", "--command", "If the action is diff, this is the command to run instead of diff"), _dec13 = helpText(`Rule related actions`), _dec14 = usage(`rally rule [action] --env [enviornment]`), _dec15 = param("action", "The action to perform. Only list is supported right now"), _dec16 = arg("-e", "--env", "The enviornment you wish to perform the action on"), _dec17 = helpText(`supply chain related actions`), _dec18 = usage(`rally supply [action] [identifier] --env [enviornment]`), _dec19 = param("action", "The action to perform. Can be calc."), _dec20 = param("identifier", "If the action is calc, then this identifier should be the first rule in the chain."), _dec21 = arg("-e", "--env", "The enviornment you wish to perform the action on"), _dec22 = helpText(`tags stuff`), _dec23 = usage(`rally tags [action]`), _dec24 = param("action", "The action to perform. Can be list or create."), _dec25 = arg("-e", "--env", "The enviornment you wish to perform the action on"), _dec26 = helpText(`print out some trace info`), _dec27 = usage(`rally trace -e [env] [jobid]`), _dec28 = param("jobid", "a job id like b86d7d90-f0a5-4622-8754-486ca8e9ecbd"), _dec29 = arg("-e", "--env", "The enviornment you wish to perform the action on"), _dec30 = helpText(`List all available providers, or find one by name/id`), _dec31 = usage(`rally providers [identifier] --env [env] --raw`), _dec32 = param("identifier", "Either the name or id of the provider"), _dec33 = arg("-e", "--env", "The enviornment you wish to perform the action on"), _dec34 = arg("~", "--raw", "Raw output of command. If [identifier] is given, then print editorConfig too"), _dec35 = helpText(`Change config for rally tools`), _dec36 = usage("rally config [key] --set [value] --raw"), _dec37 = param("key", chalk`Key you want to edit. For example, {green chalk} or {green api.DEV}`), _dec38 = arg("~", "--set", "If this value is given, no interactive prompt will launch and the config option will change."), _dec39 = arg("~", "--raw", "Raw output of json config"), _dec40 = helpText(`create/modify asset`), _dec41 = usage("rally asset [action] [action...]"), _dec42 = param("action", chalk`Options are create, delete, launch, addfile. You can supply multiple actions to chain them`), _dec43 = arg(`-i`, `--id`, chalk`MOVIE_ID of asset to select`), _dec44 = arg(`-n`, `--name`, chalk`MOVIE_NAME of asset. with {white create}, '{white #}' will be replaced with a uuid. Default is '{white TEST_#}'`), _dec45 = arg(`-j`, `--job-name`, chalk`Job name to start (used with launch)`), _dec46 = arg(`~`, `--init-data`, chalk`Init data to use when launching job. can be string, or {white @path/to/file} for a file`), _dec47 = arg(`~`, `--file-label`, chalk`File label (used with addfile)`), _dec48 = arg(`~`, `--file-uri`, chalk`File s3 uri. Can use multiple uri's for the same label (used with addfile)`), (_obj = {
3091 async help(args) {
3092 let arg = args._.shift();
3093
3094 if (arg) {
3095 let help = helpEntries[arg];
3096
3097 if (!help) {
3098 log(chalk`No help found for '{red ${arg}}'`);
3099 } else {
3100 log(printHelp(helpEntries[arg]));
3101 }
3102 } else {
3103 for (let helpArg in helpEntries) {
3104 log(printHelp(helpEntries[helpArg], true));
3105 }
3106 }
3107 },
3108
3109 async jupyter(args) {
3110 return subCommand(jupytersub)(args);
3111 },
3112
3113 //@helpText(`Print input args, for debugging`)
3114 async printArgs(args) {
3115 log(args);
3116 },
3117
3118 async preset(args) {
3119 return subCommand(presetsub)(args);
3120 },
3121
3122 async rule(args) {
3123 return subCommand(rulesub)(args);
3124 },
3125
3126 async supply(args) {
3127 return subCommand(supplysub)(args);
3128 },
3129
3130 async tag(args) {
3131 return subCommand(tagsub)(args);
3132 },
3133
3134 async trace(args) {
3135 let jobId = args._.shift();
3136
3137 if (!jobId) throw new AbortError("No job id");
3138 if (!args.env) throw new AbortError("no env");
3139 let traceInfo = await parseTrace(args.env, jobId);
3140
3141 for (let line of traceInfo) {
3142 if (typeof line == "string") {
3143 log(chalk.red(line));
3144 } else {
3145 printOutLine(line);
3146 }
3147 }
3148
3149 return true;
3150 },
3151
3152 async providers(args) {
3153 let env = args.env;
3154 if (!env) return errorLog("No env supplied.");
3155
3156 let ident = args._.shift();
3157
3158 let providers = await Provider.getAll(env);
3159
3160 if (ident) {
3161 let pro = providers.arr.find(x => x.id == ident || x.name.includes(ident));
3162
3163 if (!pro) {
3164 log(chalk`Couldn't find provider by {green ${ident}}`);
3165 } else {
3166 log(pro.chalkPrint(false));
3167 let econfig = await pro.getEditorConfig();
3168
3169 if (args.raw) {
3170 return pro;
3171 } else {
3172 if (econfig.helpText.length > 100) {
3173 econfig.helpText = "<too long to display>";
3174 }
3175
3176 if (econfig.completions.length > 5) {
3177 econfig.completions = "<too long to display>";
3178 }
3179
3180 log(econfig);
3181 }
3182 }
3183 } else {
3184 if (args.raw) return providers;
3185
3186 for (let pro of providers) log(pro.chalkPrint());
3187 }
3188 },
3189
3190 async config(args) {
3191 let prop = args._.shift();
3192
3193 let propArray = prop && prop.split("."); //if(!await configHelpers.askQuestion(`Would you like to create a new config file in ${configFile}`)) return;
3194
3195 let newConfigObject;
3196
3197 if (!prop) {
3198 if (configObject.rawOutput) return configObject;
3199 log("Creating new config");
3200 newConfigObject = { ...configObject
3201 };
3202
3203 for (let helperName in configHelpers) {
3204 if (helperName.startsWith("$")) {
3205 newConfigObject = { ...newConfigObject,
3206 ...(await configHelpers[helperName](false))
3207 };
3208 }
3209 }
3210 } else {
3211 log(chalk`Editing option {green ${prop}}`);
3212
3213 if (args.set) {
3214 newConfigObject = { ...configObject,
3215 [prop]: args.set
3216 };
3217 } else {
3218 let ident = "$" + propArray[0];
3219
3220 if (configHelpers[ident]) {
3221 newConfigObject = { ...configObject,
3222 ...(await configHelpers[ident](propArray))
3223 };
3224 } else {
3225 log(chalk`No helper for {red ${ident}}`);
3226 return;
3227 }
3228 }
3229 }
3230
3231 newConfigObject.hasConfig = true; //Create readable json and make sure the user is ok with it
3232
3233 let newConfig = JSON.stringify(newConfigObject, null, 4);
3234 log(newConfig); //-y or --set will make this not prompt
3235
3236 if (!args.y && !args.set && !(await askQuestion("Write this config to disk?"))) return;
3237 fs.writeFileSync(configFile, newConfig, {
3238 mode: 0o600
3239 });
3240 log(chalk`Created file {green ${configFile}}.`);
3241 },
3242
3243 async asset(args) {
3244 function uuid(args) {
3245 const digits = 16;
3246 return String(Math.floor(Math.random() * Math.pow(10, digits))).padStart(digits, "0");
3247 }
3248
3249 let name = args.name || `TEST_#`;
3250 let env = args.env;
3251 let asset;
3252
3253 let arg = args._.shift();
3254
3255 if (!arg) {
3256 throw new AbortError(chalk`Missing arguments: see {white 'rally help asset'}`);
3257 }
3258
3259 if (args.anon) {
3260 args._.unshift(arg);
3261 } else if (arg == "create") {
3262 name = name.replace("#", uuid());
3263 asset = await Asset.createNew(name, env);
3264 } else {
3265 args._.unshift(arg);
3266
3267 if (args.id) {
3268 asset = Asset.lite(args.id, env);
3269 } else {
3270 asset = await Asset.getByName(env, args.name);
3271 }
3272 }
3273
3274 if (!asset && !args.anon) {
3275 throw new AbortError("No asset found/created");
3276 }
3277
3278 let launchArg = 0;
3279 let fileArg = 0;
3280
3281 let arrayify = (obj, i) => Array.isArray(obj) ? obj[i] : i == 0 ? obj : undefined;
3282
3283 while (arg = args._.shift()) {
3284 if (arg === "launch") {
3285 let initData = arrayify(args["init-data"], launchArg);
3286
3287 if (initData && initData.startsWith("@")) {
3288 log(chalk`Reading init data from {white ${initData.slice(1)}}`);
3289 initData = fs.readFileSync(initData.slice(1), "utf-8");
3290 }
3291
3292 let jobName = arrayify(args["job-name"], launchArg);
3293 let p = await Rule.getByName(env, jobName);
3294
3295 if (!p) {
3296 throw new AbortError(`Cannot launch job ${jobName}, does not exist (?)`);
3297 } else {
3298 log(chalk`Launching ${p.chalkPrint(false)} on ${asset ? asset.chalkPrint(false) : "(None)"}`);
3299 }
3300
3301 if (asset) {
3302 await asset.startWorkflow(jobName, initData);
3303 } else {
3304 await Asset.startAnonWorkflow(env, jobName, initData);
3305 }
3306
3307 launchArg++;
3308 } else if (arg === "addfile") {
3309 let label = arrayify(args["file-label"], fileArg);
3310 let uri = arrayify(args["file-uri"], fileArg);
3311
3312 if (label === undefined || !uri) {
3313 throw new AbortError("Number of file-label and file-uri does not match");
3314 }
3315
3316 await asset.addFile(label, uri);
3317 log(chalk`Added file ${label}`);
3318 fileArg++;
3319 } else if (arg === "delete") {
3320 await asset.delete();
3321 } else if (arg === "create") {
3322 throw new AbortError(`Cannot have more than 1 create/get per asset call`);
3323 } else if (arg === "show") {
3324 log(asset);
3325 }
3326 }
3327
3328 if (configObject.rawOutput) return asset;
3329 },
3330
3331 async checkSegments(args) {
3332 let asset = args._.shift();
3333
3334 let res = await lib.makeAPIRequest({
3335 env: args.env,
3336 path: `/movies/${asset}/metadata/Metadata`
3337 });
3338 let segments = res.data.attributes.metadata.userMetaData.segments.segments;
3339 let r = segments.reduce((lastSegment, val, ind) => {
3340 let curSegment = val.startTime;
3341
3342 if (curSegment < lastSegment) {
3343 log("bad segment " + (ind + 1));
3344 }
3345
3346 return val.endTime;
3347 }, "00:00:00:00");
3348 },
3349
3350 async listAssets(args, tag) {
3351 let req = await lib.indexPathFast({
3352 env: args.env,
3353 path: "/assets",
3354 qs: {
3355 noRelationships: true,
3356 sort: "id"
3357 },
3358 chunksize: 30
3359 });
3360
3361 for (let asset of req) {
3362 log(asset.id);
3363 }
3364
3365 return req;
3366 },
3367
3368 async listSegments(args) {
3369 let f = JSON.parse(fs.readFileSync(args.file, "utf-8"));
3370
3371 for (let asset of f) {
3372 var _r$data$attributes$me, _r$data$attributes$me2;
3373
3374 let r = await lib.makeAPIRequest({
3375 env: args.env,
3376 path: `/movies/${asset.id}/metadata/Metadata`
3377 });
3378 let segs = (_r$data$attributes$me = r.data.attributes.metadata.userMetaData) === null || _r$data$attributes$me === void 0 ? void 0 : (_r$data$attributes$me2 = _r$data$attributes$me.segments) === null || _r$data$attributes$me2 === void 0 ? void 0 : _r$data$attributes$me2.segments;
3379
3380 if (segs && segs.length > 1) {
3381 log(asset.id);
3382 log(asset.name);
3383 }
3384 }
3385 },
3386
3387 async test2(args) {
3388 let wfr = await lib.indexPath({
3389 env: args.env,
3390 path: "/workflowRuleMetadata"
3391 });
3392 log(wfr);
3393
3394 for (let wfrm of wfr) {
3395 try {
3396 wfrm.id = undefined;
3397 wfrm.links = undefined;
3398 log(wfrm);
3399 let req = await lib.makeAPIRequest({
3400 env: "DEV",
3401 path: "/workflowRuleMetadata",
3402 method: "POST",
3403 payload: {
3404 data: wfrm
3405 }
3406 });
3407 } catch (e) {
3408 log("caught");
3409 } //break;
3410
3411 }
3412 },
3413
3414 async test3(args) {
3415 let wfr = await lib.indexPath({
3416 env: args.env,
3417 path: "/workflowRuleMetadata"
3418 });
3419 log(wfr);
3420
3421 for (let wfrm of wfr) {
3422 try {
3423 wfrm.id = undefined;
3424 wfrm.links = undefined;
3425 log(wfrm);
3426 let req = await lib.makeAPIRequest({
3427 env: "DEV",
3428 path: "/workflowRuleMetadata",
3429 method: "POST",
3430 payload: {
3431 data: wfrm
3432 }
3433 });
3434 } catch (e) {
3435 log("caught");
3436 } //break;
3437
3438 }
3439 },
3440
3441 sleep(time = 1000) {
3442 return new Promise(resolve => setTimeout(resolve, time));
3443 },
3444
3445 async audit(args) {
3446 let supportedAudits = ["presets", "rule", "other"];
3447 await addAutoCompletePrompt();
3448 let q = await inquirer.prompt([{
3449 type: "autocomplete",
3450 name: "obj",
3451 message: `What audit do you want?`,
3452 source: async (sofar, input) => {
3453 return supportedAudits.filter(x => input ? x.includes(input.toLowerCase()) : true);
3454 }
3455 }]);
3456 let choice = q.obj;
3457 let resourceId = undefined;
3458
3459 let filterFunc = _ => _;
3460
3461 if (choice === "presets") {
3462 let preset = await selectPreset();
3463 let remote = await Preset.getByName(args.env, preset.name);
3464 if (!remote) throw new AbortError("Could not find this item on remote env");
3465
3466 filterFunc = ev => ev.resource == "Preset";
3467
3468 resourceId = remote.id;
3469 } else if (choice === "rule") {
3470 let preset = await selectRule();
3471 let remote = await Rule.getByName(args.env, preset.name);
3472 if (!remote) throw new AbortError("Could not find this item on remote env");
3473
3474 filterFunc = ev => ev.resource == "Rule";
3475
3476 resourceId = remote.id;
3477 } else {
3478 resourceId = await askInput(null, "What resourceID?");
3479 }
3480
3481 log(chalk`Resource ID on {blue ${args.env}} is {yellow ${resourceId}}`);
3482 log(`Loading audits (this might take a while)`);
3483 const numRows = 100;
3484 let r = await lib.makeAPIRequest({
3485 env: args.env,
3486 path: `/v1.0/audit?perPage=${numRows}&count=${numRows}&filter=%7B%22resourceId%22%3A%22${resourceId}%22%7D&autoload=false&pageNum=1&include=`,
3487 timeout: 180000
3488 });
3489 r.data = r.data.filter(filterFunc);
3490 log("Data recieved, parsing users");
3491
3492 for (let event of r.data) {
3493 var _event$correlation;
3494
3495 let uid = event === null || event === void 0 ? void 0 : (_event$correlation = event.correlation) === null || _event$correlation === void 0 ? void 0 : _event$correlation.userId;
3496 if (!uid) continue;
3497
3498 try {
3499 event.user = await User.getById(args.env, uid);
3500 } catch (e) {
3501 event.user = {
3502 name: "????"
3503 };
3504 }
3505 }
3506
3507 if (args.raw) return r.data;
3508 let evCounter = 0;
3509
3510 for (let event of r.data) {
3511 var _event$user;
3512
3513 let evtime = moment(event.createdAt);
3514 let date = evtime.format("ddd YYYY/MM/DD hh:mm:ssa");
3515 let timedist = evtime.fromNow();
3516 log(chalk`${date} {yellow ${timedist}} {green ${(_event$user = event.user) === null || _event$user === void 0 ? void 0 : _event$user.name}} ${event.event}`);
3517 if (++evCounter >= 30) break;
3518 }
3519 },
3520
3521 async audit2(args) {
3522 const numRows = 1000;
3523 let r = await lib.makeAPIRequest({
3524 env: args.env,
3525 //path: `/v1.0/audit?perPage=${numRows}&count=${numRows}&autoload=false&pageNum=1&include=`,
3526 path: `/v1.0/audit?perPage=${numRows}&count=${numRows}&filter=%7B%22correlation.userId%22%3A%5B%22164%22%5D%7D&autoload=false&pageNum=1&include=`,
3527 timeout: 60000
3528 });
3529
3530 for (let event of r.data) {
3531 log(event.event);
3532 }
3533 },
3534
3535 async findIDs(args) {
3536 let files = await getFilesFromArgs(args);
3537
3538 for (let file of files) {
3539 let preset = await Preset.getByName(args.env, file);
3540 await preset.resolve();
3541 log(`silo-presets/${file}.${preset.ext}`);
3542 }
3543 },
3544
3545 async getAssets(env, name) {
3546 if (!this.callid) this.callid = 0;
3547 this.callid++;
3548 let callid = this.callid;
3549 await this.sleep(500);
3550 if (callid != this.callid) return this.lastResult || [];
3551 let req = await lib.makeAPIRequest({
3552 env,
3553 path: `/assets`,
3554 qs: name ? {
3555 filter: `nameContains=${name}`
3556 } : undefined
3557 });
3558 this.lastCall = Date.now();
3559 return this.lastResult = req.data;
3560 },
3561
3562 async content(args) {
3563 addAutoCompletePrompt();
3564 let q = await inquirer.prompt([{
3565 type: "autocomplete",
3566 name: "what",
3567 message: `What asset do you want?`,
3568 source: async (sofar, input) => {
3569 let assets = await this.getAssets(args.env, input);
3570 assets = assets.map(x => new Asset({
3571 data: x,
3572 remote: args.env
3573 }));
3574 return assets.map(x => ({
3575 name: x.chalkPrint(true) + ": " + x.data.links.self.replace("/api/v2/assets/", "/content/"),
3576 value: x
3577 }));
3578 }
3579 }]);
3580 },
3581
3582 async ["@"](args) {
3583 args._.unshift("-");
3584
3585 args._.unshift("make");
3586
3587 return this.supply(args);
3588 },
3589
3590 async test(args) {
3591 let asset = await Asset.getByName("UAT", args.name);
3592 log(asset);
3593 },
3594
3595 //Used to test startup and teardown speed.
3596 noop() {
3597 return true;
3598 }
3599
3600}, (_applyDecoratedDescriptor(_obj, "help", [_dec, _dec2, _dec3], Object.getOwnPropertyDescriptor(_obj, "help"), _obj), _applyDecoratedDescriptor(_obj, "jupyter", [_dec4, _dec5, _dec6], Object.getOwnPropertyDescriptor(_obj, "jupyter"), _obj), _applyDecoratedDescriptor(_obj, "preset", [_dec7, _dec8, _dec9, _dec10, _dec11, _dec12], Object.getOwnPropertyDescriptor(_obj, "preset"), _obj), _applyDecoratedDescriptor(_obj, "rule", [_dec13, _dec14, _dec15, _dec16], Object.getOwnPropertyDescriptor(_obj, "rule"), _obj), _applyDecoratedDescriptor(_obj, "supply", [_dec17, _dec18, _dec19, _dec20, _dec21], Object.getOwnPropertyDescriptor(_obj, "supply"), _obj), _applyDecoratedDescriptor(_obj, "tag", [_dec22, _dec23, _dec24, _dec25], Object.getOwnPropertyDescriptor(_obj, "tag"), _obj), _applyDecoratedDescriptor(_obj, "trace", [_dec26, _dec27, _dec28, _dec29], Object.getOwnPropertyDescriptor(_obj, "trace"), _obj), _applyDecoratedDescriptor(_obj, "providers", [_dec30, _dec31, _dec32, _dec33, _dec34], Object.getOwnPropertyDescriptor(_obj, "providers"), _obj), _applyDecoratedDescriptor(_obj, "config", [_dec35, _dec36, _dec37, _dec38, _dec39], Object.getOwnPropertyDescriptor(_obj, "config"), _obj), _applyDecoratedDescriptor(_obj, "asset", [_dec40, _dec41, _dec42, _dec43, _dec44, _dec45, _dec46, _dec47, _dec48], Object.getOwnPropertyDescriptor(_obj, "asset"), _obj)), _obj));
3601
3602async function unknownCommand(cmd) {
3603 log(chalk`Unknown command {red ${cmd}}.`);
3604}
3605
3606async function noCommand() {
3607 write(chalk`
3608Rally Tools {yellow v${version} (alpha)} CLI
3609by John Schmidt <John_Schmidt@discovery.com>
3610`); //Prompt users to setup one time config.
3611
3612 if (!configObject.hasConfig) {
3613 write(chalk`
3614It looks like you haven't setup the config yet. Please run '{green rally config}'.
3615`);
3616 return;
3617 } //API Access tests
3618
3619
3620 for (let env of ["LOCAL", "DEV", "UAT", "QA", "PROD"]) {
3621 //Test access. Returns HTTP response code
3622 let resultStr;
3623
3624 try {
3625 let result = await rallyFunctions.testAccess(env); //Create a colored display and response
3626
3627 resultStr = chalk`{yellow ${result} <unknown>}`;
3628 if (result === 200) resultStr = chalk`{green 200 OK}`;else if (result === 401) resultStr = chalk`{red 401 No Access}`;else if (result >= 500) resultStr = chalk`{yellow ${result} API Down?}`;else if (result === true) resultStr = chalk`{green OK}`;else if (result === false) resultStr = chalk`{red BAD}`;
3629 } catch (e) {
3630 if (e instanceof UnconfiguredEnvError) {
3631 resultStr = chalk`{yellow Unconfigured}`;
3632 } else if (e instanceof APIError) {
3633 if (!e.response.body) {
3634 resultStr = chalk`{red Timeout (?)}`;
3635 }
3636 } else if (e.name == "RequestError") {
3637 resultStr = chalk`{red Low level error (check internet): ${e.error.errno}}`;
3638 } else {
3639 throw e;
3640 }
3641 }
3642
3643 log(chalk` ${env}: ${resultStr}`);
3644 }
3645}
3646
3647async function $main() {
3648 //Supply --config to load a different config file
3649 if (argv.config) loadConfig(argv.config); // First we need to decide if the user wants color or not. If they do want
3650 // color, we need to make sure we use the right mode
3651
3652 chalk.enabled = configObject.hasConfig ? configObject.chalk : true;
3653
3654 if (chalk.level === 0 || !chalk.enabled) {
3655 let force = argv["force-color"];
3656
3657 if (force) {
3658 chalk.enabled = true;
3659
3660 if (force === true && chalk.level === 0) {
3661 chalk.level = 1;
3662 } else if (Number(force)) {
3663 chalk.level = Number(force);
3664 }
3665 }
3666 } //This flag being true allows you to modify UAT and PROD
3667
3668
3669 if (!argv["protect"]) {
3670 configObject.dangerModify = true;
3671 } //This enables raw output for some functions
3672
3673
3674 if (argv["raw"]) {
3675 configObject.rawOutput = true;
3676
3677 global.log = () => {};
3678
3679 global.errorLog = () => {};
3680
3681 global.write = () => {};
3682 }
3683
3684 if (argv["ignore-missing"]) {
3685 configObject.ignoreMissing = true;
3686 } //Default enviornment should normally be from config, but it can be
3687 //overridden by the -e/--env flag
3688
3689
3690 if (configObject.defaultEnv) {
3691 argv.env = argv.env || configObject.defaultEnv;
3692 } //Enable verbose logging in some places.
3693
3694
3695 if (argv["vverbose"]) {
3696 configObject.verbose = argv["vverbose"];
3697 configObject.vverbose = true;
3698 } else if (argv["verbose"]) {
3699 configObject.verbose = argv["verbose"];
3700 } else if (argv["vvverbose"]) {
3701 configObject.verbose = true;
3702 configObject.vverbose = true;
3703 configObject.vvverbose = true;
3704 } //copy argument array to new object to allow modification
3705
3706
3707 argv._old = argv._.slice(); //Take first argument after `node bundle.js`
3708 //If there is no argument, display the default version info and API access.
3709
3710 let func = argv._.shift();
3711
3712 if (func) {
3713 if (!cli[func]) return await unknownCommand(func);
3714
3715 try {
3716 //Call the cli function
3717 let ret = await cli[func](argv);
3718
3719 if (ret) {
3720 write(chalk.white("CLI returned: "));
3721 if (ret instanceof Collection) ret = ret.arr; //Directly use console.log so that --raw works as intended.
3722
3723 if (typeof ret === "object") {
3724 console.log(JSON.stringify(ret, null, 4));
3725 } else {
3726 console.log(ret);
3727 }
3728 }
3729 } catch (e) {
3730 if (e instanceof AbortError) {
3731 log(chalk`{red CLI Aborted}: ${e.message}`);
3732 } else {
3733 throw e;
3734 }
3735 }
3736 } else {
3737 await noCommand();
3738 }
3739}
3740
3741async function main(...args) {
3742 //Catch all for errors to avoid ugly default node promise catcher
3743 try {
3744 await $main(...args);
3745 } catch (e) {
3746 errorLog(e.stack);
3747 }
3748} // If this is an imported module, then we should exec the cli interface.
3749// Oterwise just export everything.
3750
3751
3752if (require.main === module) {
3753 main();
3754} else {
3755 module.exports = allIndexBundle;
3756}
3757//# sourceMappingURL=bundle.js.map