@contrail/cli 3.5.4-alpha.ba45a5e → 3.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -23
- package/dist/api/org-config.d.ts +3 -0
- package/dist/api/org-config.js +28 -0
- package/dist/commands/groups/export.js +1 -13
- package/dist/commands/groups/import.js +2 -15
- package/dist/commands/instance/import.d.ts +14 -0
- package/dist/commands/instance/import.js +27 -5
- package/dist/commands/instance/import.spec.d.ts +1 -0
- package/dist/commands/instance/import.spec.js +57 -0
- package/dist/commands/org-config/export.d.ts +13 -0
- package/dist/commands/org-config/export.js +74 -0
- package/dist/commands/org-config/import.d.ts +12 -0
- package/dist/commands/org-config/import.js +63 -0
- package/dist/commands/types/getAll.js +2 -0
- package/dist/commands/types/loadAll.d.ts +13 -1
- package/dist/commands/types/loadAll.js +41 -4
- package/dist/commands/types/loadAll.spec.d.ts +1 -0
- package/dist/commands/types/loadAll.spec.js +77 -0
- package/dist/commands/types/mergeOptionSets.js +2 -9
- package/dist/commands/types/mergeTypeProperties.js +2 -6
- package/dist/commands/types/parseOptionSetsLocalFiles.js +3 -7
- package/dist/commands/types/uploadTypeOverrides.d.ts +10 -0
- package/dist/commands/types/uploadTypeOverrides.js +19 -8
- package/dist/commands/types/uploadTypeOverrides.spec.d.ts +1 -0
- package/dist/commands/types/uploadTypeOverrides.spec.js +60 -0
- package/dist/commands/users/export.js +1 -13
- package/dist/commands/users/import.js +2 -15
- package/dist/commands/workflow-templates/export.d.ts +2 -3
- package/dist/commands/workflow-templates/export.js +23 -122
- package/dist/commands/workflow-templates/load.d.ts +0 -2
- package/dist/commands/workflow-templates/load.js +6 -67
- package/dist/helpers/error-helpers.d.ts +9 -0
- package/dist/helpers/error-helpers.js +35 -0
- package/dist/helpers/file-helpers.d.ts +2 -0
- package/dist/helpers/file-helpers.js +25 -1
- package/dist/helpers/projects/helpers.js +2 -6
- package/dist/helpers/types/type-parser.js +9 -15
- package/dist/helpers/workflow-templates/helper.d.ts +1 -2
- package/dist/helpers/workflow-templates/helper.js +7 -38
- package/dist/interfaces.d.ts +1 -1
- package/oclif.manifest.json +126 -30
- package/package.json +4 -1
|
@@ -4,6 +4,7 @@ exports.loadAll = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const core_1 = require("@oclif/core");
|
|
6
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
7
8
|
const login_1 = require("../../auth/login");
|
|
8
9
|
const uploadOptionSets_1 = require("./uploadOptionSets");
|
|
9
10
|
const uploadOptionSetHierarchies_1 = require("./uploadOptionSetHierarchies");
|
|
@@ -12,8 +13,10 @@ const uploadTypePropertyPolicies_1 = require("./uploadTypePropertyPolicies");
|
|
|
12
13
|
const type_uploader_1 = require("../../helpers/types/type-uploader");
|
|
13
14
|
const uploadTypeOverrides_1 = require("./uploadTypeOverrides");
|
|
14
15
|
const uploadTypePolicies_1 = require("./uploadTypePolicies");
|
|
16
|
+
const import_1 = require("../org-config/import");
|
|
15
17
|
const basecommand_1 = require("../../basecommand");
|
|
16
18
|
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
19
|
+
const error_helpers_1 = require("../../helpers/error-helpers");
|
|
17
20
|
class LoadAllTypes extends basecommand_1.BaseCommand {
|
|
18
21
|
static description = 'loads all the type data that has been exported by getAll';
|
|
19
22
|
static examples = [
|
|
@@ -28,13 +31,30 @@ class LoadAllTypes extends basecommand_1.BaseCommand {
|
|
|
28
31
|
required: false,
|
|
29
32
|
default: '.',
|
|
30
33
|
}),
|
|
34
|
+
orgConfig: core_1.Flags.boolean({
|
|
35
|
+
description: 'Also load org config data from this directory. If not specified, you will be prompted.',
|
|
36
|
+
required: false,
|
|
37
|
+
default: undefined,
|
|
38
|
+
allowNo: true,
|
|
39
|
+
}),
|
|
31
40
|
};
|
|
32
41
|
async run() {
|
|
33
|
-
await loadAll(this.flags.dir);
|
|
42
|
+
await loadAll(this.flags.dir, false, this.flags.orgConfig);
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
45
|
exports.default = LoadAllTypes;
|
|
37
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Loads everything exported by `types getAll`.
|
|
48
|
+
*
|
|
49
|
+
* @param dir directory containing the export
|
|
50
|
+
* @param skipPolicies when true, type property policies, type policies AND type overrides are
|
|
51
|
+
* not uploaded. Policies need users/groups to exist first, and type overrides can reference
|
|
52
|
+
* policies (as well as rule sets and option set hierarchies), so the API rejects the whole
|
|
53
|
+
* override upload if a referenced policy is missing. Callers that pass `true` (e.g.
|
|
54
|
+
* `instance import`) must upload policies and then overrides themselves, in that order.
|
|
55
|
+
* @param loadOrgConfig whether to also load org-config.yml; prompts when undefined
|
|
56
|
+
*/
|
|
57
|
+
async function loadAll(dir, skipPolicies = false, loadOrgConfig) {
|
|
38
58
|
try {
|
|
39
59
|
await (0, login_1.authUser)();
|
|
40
60
|
await (0, uploadOptionSets_1.uploadOptionSets)(dir);
|
|
@@ -44,11 +64,28 @@ async function loadAll(dir, skipPolicies = false) {
|
|
|
44
64
|
if (!skipPolicies) {
|
|
45
65
|
await (0, uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)(dir);
|
|
46
66
|
await (0, uploadTypePolicies_1.uploadTypePolicies)(dir);
|
|
67
|
+
// must run after policies, rule sets and option set hierarchies: overrides reference them by slug
|
|
68
|
+
await (0, uploadTypeOverrides_1.uploadTypeOverrides)(dir);
|
|
69
|
+
}
|
|
70
|
+
let shouldLoadOrgConfig = loadOrgConfig;
|
|
71
|
+
if (shouldLoadOrgConfig === undefined) {
|
|
72
|
+
const answers = await inquirer_1.default.prompt([
|
|
73
|
+
{
|
|
74
|
+
type: 'confirm',
|
|
75
|
+
name: 'loadOrgConfig',
|
|
76
|
+
message: 'Also load org config details from this directory?',
|
|
77
|
+
default: false,
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
shouldLoadOrgConfig = answers.loadOrgConfig;
|
|
81
|
+
}
|
|
82
|
+
if (shouldLoadOrgConfig) {
|
|
83
|
+
await (0, import_1.uploadOrgConfig)(dir);
|
|
47
84
|
}
|
|
48
|
-
await (0, uploadTypeOverrides_1.uploadTypeOverrides)(dir);
|
|
49
85
|
}
|
|
50
86
|
catch (e) {
|
|
51
|
-
logger_1.default.
|
|
87
|
+
logger_1.default.error(`${chalk_1.default.red.bold('FAIL')} Types Upload Failed. ${(0, error_helpers_1.describeError)(e)}`);
|
|
88
|
+
process.exitCode = 1;
|
|
52
89
|
return;
|
|
53
90
|
}
|
|
54
91
|
logger_1.default.info(`${chalk_1.default.green.bold('COMPLETE')} Types Upload Complete.`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const loadAll_1 = require("./loadAll");
|
|
4
|
+
const login_1 = require("../../auth/login");
|
|
5
|
+
const uploadOptionSets_1 = require("./uploadOptionSets");
|
|
6
|
+
const uploadOptionSetHierarchies_1 = require("./uploadOptionSetHierarchies");
|
|
7
|
+
const uploadRuleSets_1 = require("./uploadRuleSets");
|
|
8
|
+
const uploadTypePropertyPolicies_1 = require("./uploadTypePropertyPolicies");
|
|
9
|
+
const uploadTypePolicies_1 = require("./uploadTypePolicies");
|
|
10
|
+
const uploadTypeOverrides_1 = require("./uploadTypeOverrides");
|
|
11
|
+
const type_uploader_1 = require("../../helpers/types/type-uploader");
|
|
12
|
+
const import_1 = require("../org-config/import");
|
|
13
|
+
jest.mock('../../auth/login');
|
|
14
|
+
jest.mock('./uploadOptionSets');
|
|
15
|
+
jest.mock('./uploadOptionSetHierarchies');
|
|
16
|
+
jest.mock('./uploadRuleSets');
|
|
17
|
+
jest.mock('./uploadTypePropertyPolicies');
|
|
18
|
+
jest.mock('./uploadTypePolicies');
|
|
19
|
+
jest.mock('./uploadTypeOverrides');
|
|
20
|
+
jest.mock('../../helpers/types/type-uploader');
|
|
21
|
+
jest.mock('../org-config/import');
|
|
22
|
+
const mocked = (fn) => fn;
|
|
23
|
+
describe('loadAll', () => {
|
|
24
|
+
const originalExitCode = process.exitCode;
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
jest.clearAllMocks();
|
|
27
|
+
process.exitCode = originalExitCode;
|
|
28
|
+
mocked(login_1.authUser).mockResolvedValue({ orgSlug: 'test-org' });
|
|
29
|
+
for (const fn of [
|
|
30
|
+
uploadOptionSets_1.uploadOptionSets,
|
|
31
|
+
type_uploader_1.uploadTypes,
|
|
32
|
+
uploadOptionSetHierarchies_1.uploadOptionSetHierarchies,
|
|
33
|
+
uploadRuleSets_1.uploadRuleSets,
|
|
34
|
+
uploadTypePropertyPolicies_1.uploadTypePropertyPolicies,
|
|
35
|
+
uploadTypePolicies_1.uploadTypePolicies,
|
|
36
|
+
uploadTypeOverrides_1.uploadTypeOverrides,
|
|
37
|
+
import_1.uploadOrgConfig,
|
|
38
|
+
]) {
|
|
39
|
+
mocked(fn).mockResolvedValue(undefined);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
afterAll(() => {
|
|
43
|
+
process.exitCode = originalExitCode;
|
|
44
|
+
});
|
|
45
|
+
it('uploads type overrides after policies, rule sets and option set hierarchies', async () => {
|
|
46
|
+
await (0, loadAll_1.loadAll)('dir', false, false);
|
|
47
|
+
expect(mocked(uploadTypeOverrides_1.uploadTypeOverrides)).toHaveBeenCalledWith('dir');
|
|
48
|
+
expect(mocked(import_1.uploadOrgConfig)).not.toHaveBeenCalled();
|
|
49
|
+
const overridesOrder = mocked(uploadTypeOverrides_1.uploadTypeOverrides).mock.invocationCallOrder[0];
|
|
50
|
+
for (const fn of [
|
|
51
|
+
uploadOptionSetHierarchies_1.uploadOptionSetHierarchies,
|
|
52
|
+
uploadRuleSets_1.uploadRuleSets,
|
|
53
|
+
uploadTypePropertyPolicies_1.uploadTypePropertyPolicies,
|
|
54
|
+
uploadTypePolicies_1.uploadTypePolicies,
|
|
55
|
+
]) {
|
|
56
|
+
expect(overridesOrder).toBeGreaterThan(mocked(fn).mock.invocationCallOrder[0]);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
it('skips policies and type overrides when skipPolicies is set', async () => {
|
|
60
|
+
await (0, loadAll_1.loadAll)('dir', true, false);
|
|
61
|
+
expect(mocked(type_uploader_1.uploadTypes)).toHaveBeenCalledWith('dir');
|
|
62
|
+
expect(mocked(uploadRuleSets_1.uploadRuleSets)).toHaveBeenCalledWith('dir');
|
|
63
|
+
expect(mocked(uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)).not.toHaveBeenCalled();
|
|
64
|
+
expect(mocked(uploadTypePolicies_1.uploadTypePolicies)).not.toHaveBeenCalled();
|
|
65
|
+
expect(mocked(uploadTypeOverrides_1.uploadTypeOverrides)).not.toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
it('sets a non-zero exit code when a step fails', async () => {
|
|
68
|
+
mocked(uploadTypeOverrides_1.uploadTypeOverrides).mockRejectedValue(new Error('boom'));
|
|
69
|
+
await (0, loadAll_1.loadAll)('dir', false, false);
|
|
70
|
+
expect(process.exitCode).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
it('loads org config when requested, after everything else', async () => {
|
|
73
|
+
await (0, loadAll_1.loadAll)('dir', false, true);
|
|
74
|
+
expect(mocked(import_1.uploadOrgConfig)).toHaveBeenCalledWith('dir');
|
|
75
|
+
expect(mocked(import_1.uploadOrgConfig).mock.invocationCallOrder[0]).toBeGreaterThan(mocked(uploadTypeOverrides_1.uploadTypeOverrides).mock.invocationCallOrder[0]);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
|
-
const path = tslib_1.__importStar(require("path"));
|
|
5
4
|
const sdk_1 = require("@contrail/sdk");
|
|
6
|
-
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
7
5
|
const login_1 = require("../../auth/login");
|
|
8
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
9
7
|
const core_1 = require("@oclif/core");
|
|
10
8
|
const basecommand_1 = require("../../basecommand");
|
|
11
9
|
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
12
|
-
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
13
10
|
const option_set_helper_1 = require("../../helpers/types/option-set-helper");
|
|
11
|
+
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
14
12
|
class MergeOptionSets extends basecommand_1.BaseCommand {
|
|
15
13
|
static description = 'Merge the OptionSets from an existing org and the OptionSets passed in. OptionSets File must be .yml or a directory with yml files. The files can contain a single option set or an array of option sets.';
|
|
16
14
|
static examples = [
|
|
@@ -88,7 +86,7 @@ async function mergeOptionSets(fileLocation) {
|
|
|
88
86
|
mergedOptionSets.push(currentOptionSet);
|
|
89
87
|
}
|
|
90
88
|
}
|
|
91
|
-
saveData(mergedOptionSets);
|
|
89
|
+
await (0, file_helpers_1.saveData)(mergedOptionSets, 'merged_optionSets.yml', true);
|
|
92
90
|
}
|
|
93
91
|
function mergeOptionSet(existingOptionSet, uploadedOptionSet) {
|
|
94
92
|
const merged = {
|
|
@@ -110,8 +108,3 @@ function mergeOptionSet(existingOptionSet, uploadedOptionSet) {
|
|
|
110
108
|
}
|
|
111
109
|
return merged;
|
|
112
110
|
}
|
|
113
|
-
function saveData(data) {
|
|
114
|
-
const yamlStr = js_yaml_1.default.dump(data);
|
|
115
|
-
const CURRENT_DIR = process.cwd();
|
|
116
|
-
fs_1.default.writeFileSync(path.resolve(path.join(CURRENT_DIR, 'merged_optionSets.yml')), yamlStr, 'utf8');
|
|
117
|
-
}
|
|
@@ -11,6 +11,7 @@ const types_1 = require("@contrail/org-config-migration/lib/types");
|
|
|
11
11
|
const types_2 = require("../../api/types");
|
|
12
12
|
const basecommand_1 = require("../../basecommand");
|
|
13
13
|
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
14
|
+
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
14
15
|
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
15
16
|
class MergeTypeProperties extends basecommand_1.BaseCommand {
|
|
16
17
|
static description = 'Merge the TypeProperties from an existing org and the TypeProperties passed in. Add to Types via TypePath File must be .yml';
|
|
@@ -74,7 +75,7 @@ async function mergeTypeProperties(fileLocation, typePaths) {
|
|
|
74
75
|
}
|
|
75
76
|
mergeType(foundType, uploadedTypeProperties);
|
|
76
77
|
}
|
|
77
|
-
saveData({ types: reducedData.reducedTypes });
|
|
78
|
+
await (0, file_helpers_1.saveData)({ types: reducedData.reducedTypes }, 'merged_types.yml', true);
|
|
78
79
|
}
|
|
79
80
|
function mergeType(reducedType, typePropertiesToAdd) {
|
|
80
81
|
for (const typeProperty of typePropertiesToAdd) {
|
|
@@ -84,8 +85,3 @@ function mergeType(reducedType, typePropertiesToAdd) {
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
|
-
function saveData(data) {
|
|
88
|
-
const yamlStr = js_yaml_1.default.dump(data);
|
|
89
|
-
const CURRENT_DIR = process.cwd();
|
|
90
|
-
fs_1.default.writeFileSync(path.resolve(path.join(CURRENT_DIR, 'merged_types.yml')), yamlStr, 'utf8');
|
|
91
|
-
}
|
|
@@ -12,6 +12,7 @@ const core_1 = require("@oclif/core");
|
|
|
12
12
|
const basecommand_1 = require("../../basecommand");
|
|
13
13
|
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
14
14
|
const common_1 = require("../../common");
|
|
15
|
+
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
15
16
|
class ParseOptionSetsLocalFiles extends basecommand_1.BaseCommand {
|
|
16
17
|
static description = 'Parse CSV files into OptionSets. Style denotes the format the csv is in. If the csv file has a column for option set' +
|
|
17
18
|
'name, option key, and option label, use "NameAndLabelAndKey". If the file has one column for each option set with ' +
|
|
@@ -109,22 +110,17 @@ async function parseOptionSetsLocalFiles(fileLocation, csvStyle) {
|
|
|
109
110
|
const uploadedOptionData = await loadOptionSetCSV(fileLocation);
|
|
110
111
|
if (csvStyle === 'NameAndLabelAndKey') {
|
|
111
112
|
const parsedOptionSets = parseNameAndLabelAndKey(uploadedOptionData);
|
|
112
|
-
saveData(parsedOptionSets);
|
|
113
|
+
await (0, file_helpers_1.saveData)(parsedOptionSets, 'parsed_local_optionSets.yml', true);
|
|
113
114
|
}
|
|
114
115
|
if (csvStyle === 'LabelsInColumn') {
|
|
115
116
|
const parsedOptionSets = parseLabelsInColumn(uploadedOptionData);
|
|
116
|
-
saveData(parsedOptionSets);
|
|
117
|
+
await (0, file_helpers_1.saveData)(parsedOptionSets, 'parsed_local_optionSets.yml', true);
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
async function parseOptionSetsToFolderStructure(fileLocation) {
|
|
120
121
|
const uploadedOptionData = await loadOptionSets(fileLocation);
|
|
121
122
|
saveDataInFolders(uploadedOptionData);
|
|
122
123
|
}
|
|
123
|
-
function saveData(data) {
|
|
124
|
-
const yamlStr = js_yaml_1.default.dump(data);
|
|
125
|
-
const CURRENT_DIR = process.cwd();
|
|
126
|
-
fs_1.default.writeFileSync(path.resolve(path.join(CURRENT_DIR, 'parsed_local_optionSets.yml')), yamlStr, 'utf8');
|
|
127
|
-
}
|
|
128
124
|
function saveDataInFolders(data) {
|
|
129
125
|
const folder = { filename: 'option-sets', split: true, fileLabel: 'name' };
|
|
130
126
|
const dir = 'merged_option_sets';
|
|
@@ -9,4 +9,14 @@ export default class UploadTypeOverrides extends BaseCommand {
|
|
|
9
9
|
};
|
|
10
10
|
run(): Promise<void>;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Uploads $dir/type-overrides.yml to the org.
|
|
14
|
+
*
|
|
15
|
+
* Type overrides can reference type policies, rule sets and option set hierarchies by
|
|
16
|
+
* slug, and the API rejects the whole upload (nothing is applied) if any referenced
|
|
17
|
+
* entity does not exist yet. Callers must therefore upload those entities first.
|
|
18
|
+
*
|
|
19
|
+
* Throws on failure so that callers such as `instance import` do not report success
|
|
20
|
+
* when the overrides were not applied.
|
|
21
|
+
*/
|
|
12
22
|
export declare function uploadTypeOverrides(dir: string): Promise<void>;
|
|
@@ -11,6 +11,7 @@ const basecommand_1 = require("../../basecommand");
|
|
|
11
11
|
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
12
12
|
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
13
13
|
const types_1 = require("../../api/types");
|
|
14
|
+
const error_helpers_1 = require("../../helpers/error-helpers");
|
|
14
15
|
class UploadTypeOverrides extends basecommand_1.BaseCommand {
|
|
15
16
|
static description = 'Upload type overrides';
|
|
16
17
|
static examples = [
|
|
@@ -31,21 +32,31 @@ class UploadTypeOverrides extends basecommand_1.BaseCommand {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
exports.default = UploadTypeOverrides;
|
|
35
|
+
/**
|
|
36
|
+
* Uploads $dir/type-overrides.yml to the org.
|
|
37
|
+
*
|
|
38
|
+
* Type overrides can reference type policies, rule sets and option set hierarchies by
|
|
39
|
+
* slug, and the API rejects the whole upload (nothing is applied) if any referenced
|
|
40
|
+
* entity does not exist yet. Callers must therefore upload those entities first.
|
|
41
|
+
*
|
|
42
|
+
* Throws on failure so that callers such as `instance import` do not report success
|
|
43
|
+
* when the overrides were not applied.
|
|
44
|
+
*/
|
|
34
45
|
async function uploadTypeOverrides(dir) {
|
|
46
|
+
await (0, login_1.authUser)();
|
|
47
|
+
const reducedOverrides = readTypeOverridesFromFile(dir);
|
|
48
|
+
if (!reducedOverrides || reducedOverrides.length === 0) {
|
|
49
|
+
logger_1.default.info(`${chalk_1.default.yellow.bold('TYPE OVERRIDE')} No overrides found in type-overrides.yml — skipping upload to avoid wiping org overrides`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
35
52
|
try {
|
|
36
|
-
await (0, login_1.authUser)();
|
|
37
|
-
const reducedOverrides = readTypeOverridesFromFile(dir);
|
|
38
|
-
if (!reducedOverrides || reducedOverrides.length === 0) {
|
|
39
|
-
logger_1.default.info(`${chalk_1.default.yellow.bold('TYPE OVERRIDE')} No overrides found in type-overrides.yml — skipping upload to avoid wiping org overrides`);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
53
|
logger_1.default.info(`${chalk_1.default.yellow.bold('TYPE OVERRIDE')} Uploading`);
|
|
43
54
|
await (0, types_1.uploadTypeOverrideObjects)(reducedOverrides);
|
|
44
55
|
logger_1.default.info(`${chalk_1.default.yellow.bold('TYPE OVERRIDE')} Uploaded`);
|
|
45
56
|
}
|
|
46
57
|
catch (e) {
|
|
47
|
-
logger_1.default.
|
|
48
|
-
|
|
58
|
+
logger_1.default.error(`${chalk_1.default.red.bold('FAIL')} Type Override Upload Failed. None of the ${reducedOverrides.length} overrides were applied: ${(0, error_helpers_1.describeError)(e)}`);
|
|
59
|
+
throw e;
|
|
49
60
|
}
|
|
50
61
|
logger_1.default.info(`${chalk_1.default.green.bold('COMPLETE')} Type Override Upload Complete.`);
|
|
51
62
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
5
|
+
const uploadTypeOverrides_1 = require("./uploadTypeOverrides");
|
|
6
|
+
const login_1 = require("../../auth/login");
|
|
7
|
+
const types_1 = require("../../api/types");
|
|
8
|
+
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
9
|
+
jest.mock('../../auth/login');
|
|
10
|
+
jest.mock('../../api/types');
|
|
11
|
+
const OVERRIDES_YML = `
|
|
12
|
+
- typePath: item
|
|
13
|
+
attribute:
|
|
14
|
+
type: policy
|
|
15
|
+
overrideType: add
|
|
16
|
+
referenceSlug: Limit-Create
|
|
17
|
+
`;
|
|
18
|
+
describe('uploadTypeOverrides', () => {
|
|
19
|
+
let errorSpy;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
login_1.authUser.mockResolvedValue({
|
|
23
|
+
orgSlug: 'test-org',
|
|
24
|
+
});
|
|
25
|
+
jest.spyOn(fs_1.default, 'existsSync').mockReturnValue(true);
|
|
26
|
+
jest.spyOn(fs_1.default, 'readFileSync').mockReturnValue(OVERRIDES_YML);
|
|
27
|
+
errorSpy = jest.spyOn(logger_1.default, 'error').mockImplementation(() => logger_1.default);
|
|
28
|
+
jest.spyOn(logger_1.default, 'info').mockImplementation(() => logger_1.default);
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
jest.restoreAllMocks();
|
|
32
|
+
});
|
|
33
|
+
it('uploads the overrides from type-overrides.yml', async () => {
|
|
34
|
+
types_1.uploadTypeOverrideObjects.mockResolvedValue(undefined);
|
|
35
|
+
await (0, uploadTypeOverrides_1.uploadTypeOverrides)('dir');
|
|
36
|
+
expect(types_1.uploadTypeOverrideObjects).toHaveBeenCalledWith([
|
|
37
|
+
{
|
|
38
|
+
typePath: 'item',
|
|
39
|
+
attribute: { type: 'policy', overrideType: 'add', referenceSlug: 'Limit-Create' },
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
it('skips the upload when the file has no overrides', async () => {
|
|
44
|
+
jest.spyOn(fs_1.default, 'readFileSync').mockReturnValue('[]');
|
|
45
|
+
await (0, uploadTypeOverrides_1.uploadTypeOverrides)('dir');
|
|
46
|
+
expect(types_1.uploadTypeOverrideObjects).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
it('rethrows API failures and logs the API message', async () => {
|
|
49
|
+
// shape of the SDK ApiError: undefined message, raw response body in `details`
|
|
50
|
+
const apiError = new Error();
|
|
51
|
+
apiError.details = JSON.stringify({
|
|
52
|
+
message: 'policy with identifier Limit-Create not found',
|
|
53
|
+
error: 'Bad Request',
|
|
54
|
+
statusCode: 400,
|
|
55
|
+
});
|
|
56
|
+
types_1.uploadTypeOverrideObjects.mockRejectedValue(apiError);
|
|
57
|
+
await expect((0, uploadTypeOverrides_1.uploadTypeOverrides)('dir')).rejects.toBe(apiError);
|
|
58
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('policy with identifier Limit-Create not found'));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.exportUsers = exports.DEFAULT_FILE_LOCATION = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const path = tslib_1.__importStar(require("path"));
|
|
6
5
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
-
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
8
6
|
const login_1 = require("../../auth/login");
|
|
9
|
-
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
10
7
|
const core_1 = require("@oclif/core");
|
|
11
8
|
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
12
9
|
const users_1 = require("../../api/users");
|
|
@@ -63,15 +60,6 @@ async function exportUsers(fileLocation, overwrite) {
|
|
|
63
60
|
});
|
|
64
61
|
}
|
|
65
62
|
users = users.sort((one, two) => (one.email < two.email ? -1 : 1));
|
|
66
|
-
await saveData(users, fileLocation, overwrite);
|
|
63
|
+
await (0, file_helpers_1.saveData)(users, fileLocation, overwrite);
|
|
67
64
|
}
|
|
68
65
|
exports.exportUsers = exportUsers;
|
|
69
|
-
async function saveData(data, fileLocation, overwrite) {
|
|
70
|
-
const yamlStr = js_yaml_1.default.dump(data);
|
|
71
|
-
const CURRENT_DIR = process.cwd();
|
|
72
|
-
const pathToWrite = path.resolve(path.join(CURRENT_DIR, fileLocation));
|
|
73
|
-
if (fs_1.default.existsSync(pathToWrite)) {
|
|
74
|
-
await (0, file_helpers_1.safeFileOverwrite)(pathToWrite, overwrite);
|
|
75
|
-
}
|
|
76
|
-
fs_1.default.writeFileSync(pathToWrite, yamlStr, 'utf8');
|
|
77
|
-
}
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.uploadUsers = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const path = tslib_1.__importStar(require("path"));
|
|
6
5
|
const sdk_1 = require("@contrail/sdk");
|
|
7
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
8
|
-
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
9
7
|
const login_1 = require("../../auth/login");
|
|
10
|
-
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
11
8
|
const core_1 = require("@oclif/core");
|
|
9
|
+
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
12
10
|
const basecommand_1 = require("../../basecommand");
|
|
13
11
|
class ImportUsers extends basecommand_1.BaseCommand {
|
|
14
12
|
static description = 'Import all users in the users.yml file';
|
|
@@ -38,19 +36,8 @@ class ImportUsers extends basecommand_1.BaseCommand {
|
|
|
38
36
|
}
|
|
39
37
|
}
|
|
40
38
|
exports.default = ImportUsers;
|
|
41
|
-
function getUsersFile(fileLocation) {
|
|
42
|
-
const CURRENT_DIR = process.cwd();
|
|
43
|
-
try {
|
|
44
|
-
const fullPath = path.join(CURRENT_DIR, fileLocation);
|
|
45
|
-
const doc = js_yaml_1.default.load(fs_1.default.readFileSync(fullPath, 'utf8'));
|
|
46
|
-
return doc;
|
|
47
|
-
}
|
|
48
|
-
catch (e) {
|
|
49
|
-
throw new Error(`ERROR PARSING YAML: ${e}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
39
|
async function uploadUsers(fileLocation) {
|
|
53
|
-
const users =
|
|
40
|
+
const users = (0, file_helpers_1.loadData)(fileLocation);
|
|
54
41
|
for (const user of users) {
|
|
55
42
|
const role = user.role;
|
|
56
43
|
const email = user.email;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { BaseCommand } from '../../basecommand';
|
|
2
|
+
export declare const DEFAULT_FILE_LOCATION = "workflows.yml";
|
|
2
3
|
export default class ExportWorkflows extends BaseCommand {
|
|
3
4
|
static description: string;
|
|
4
5
|
static examples: string[];
|
|
5
6
|
static flags: {
|
|
6
7
|
outFile: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
7
|
-
dir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
8
8
|
overwrite: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
9
|
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
10
|
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
11
|
};
|
|
12
12
|
run(): Promise<void>;
|
|
13
13
|
}
|
|
14
|
-
export declare function
|
|
15
|
-
export declare function exportWorkflowsToFile(fileLocation: string, overwrite: boolean): Promise<void>;
|
|
14
|
+
export declare function exportWorkflows(fileLocation: string, overwrite: boolean): Promise<void>;
|
|
@@ -1,39 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.exportWorkflows = exports.DEFAULT_FILE_LOCATION = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const path = tslib_1.__importStar(require("path"));
|
|
6
5
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
-
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
8
6
|
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
9
7
|
const login_1 = require("../../auth/login");
|
|
10
|
-
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
11
8
|
const core_1 = require("@oclif/core");
|
|
12
9
|
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
13
10
|
const event_workflow_templates_1 = require("../../api/event-workflow-templates");
|
|
14
|
-
const schedules_1 = require("../../api/schedules");
|
|
15
|
-
const apps_1 = require("../../api/apps");
|
|
16
11
|
const helper_1 = require("../../helpers/workflow-templates/helper");
|
|
17
12
|
const basecommand_1 = require("../../basecommand");
|
|
13
|
+
exports.DEFAULT_FILE_LOCATION = 'workflows.yml';
|
|
18
14
|
class ExportWorkflows extends basecommand_1.BaseCommand {
|
|
19
|
-
static description = 'Export all workflows in the org.
|
|
15
|
+
static description = 'Export all workflows in the org.';
|
|
20
16
|
static examples = [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'$ contrail workflow-templates export --outFile workflows.yml',
|
|
17
|
+
`$ contrail workflow-templates export
|
|
18
|
+
`,
|
|
24
19
|
];
|
|
25
20
|
static flags = {
|
|
26
21
|
...basecommand_1.BaseCommand.baseFlags,
|
|
27
22
|
outFile: core_1.Flags.string({
|
|
28
23
|
char: 'f',
|
|
29
|
-
description: '
|
|
24
|
+
description: 'The location for the file to be saved',
|
|
30
25
|
required: false,
|
|
31
|
-
|
|
32
|
-
dir: core_1.Flags.string({
|
|
33
|
-
char: 'd',
|
|
34
|
-
description: 'Directory to write per-app workflow files',
|
|
35
|
-
required: false,
|
|
36
|
-
default: '.',
|
|
26
|
+
default: exports.DEFAULT_FILE_LOCATION,
|
|
37
27
|
}),
|
|
38
28
|
overwrite: core_1.Flags.boolean({
|
|
39
29
|
description: 'Replaces existing files if they exist',
|
|
@@ -45,12 +35,7 @@ class ExportWorkflows extends basecommand_1.BaseCommand {
|
|
|
45
35
|
try {
|
|
46
36
|
await (0, login_1.authUser)();
|
|
47
37
|
this.log(`${chalk_1.default.blue.bold('START')} Getting Workflows.`);
|
|
48
|
-
|
|
49
|
-
await exportWorkflowsToFile(this.flags.outFile, this.flags.overwrite);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
await exportWorkflowsByApp(this.flags.dir ?? '.', this.flags.overwrite);
|
|
53
|
-
}
|
|
38
|
+
await exportWorkflows(this.flags.outFile ?? exports.DEFAULT_FILE_LOCATION, this.flags.overwrite);
|
|
54
39
|
this.log(`${chalk_1.default.yellow.bold('WORKFLOWS')} Done`);
|
|
55
40
|
}
|
|
56
41
|
catch (e) {
|
|
@@ -61,106 +46,22 @@ class ExportWorkflows extends basecommand_1.BaseCommand {
|
|
|
61
46
|
}
|
|
62
47
|
}
|
|
63
48
|
exports.default = ExportWorkflows;
|
|
64
|
-
async function
|
|
65
|
-
const
|
|
66
|
-
(0, event_workflow_templates_1.getExistingWorkflowTemplates)(),
|
|
67
|
-
(0, schedules_1.getExistingSchedules)(),
|
|
68
|
-
(0, apps_1.getInstalledApps)(),
|
|
69
|
-
]);
|
|
70
|
-
const appIdToIdentifier = new Map(installedApps
|
|
71
|
-
.filter((appOrg) => appOrg.appId && appOrg.app?.identifier)
|
|
72
|
-
.map((appOrg) => [appOrg.appId, appOrg.app.identifier]));
|
|
73
|
-
const workflowsByApp = new Map();
|
|
74
|
-
const unassigned = [];
|
|
75
|
-
for (const workflow of allWorkflowsInOrg) {
|
|
76
|
-
let appIdentifier = workflow.supportingAppId
|
|
77
|
-
? appIdToIdentifier.get(workflow.supportingAppId)
|
|
78
|
-
: undefined;
|
|
79
|
-
// If no supportingAppId, try to extract app identifier from workflow identifier
|
|
80
|
-
// Format: @{appIdentifier}@{randomId}:{workflowName}
|
|
81
|
-
if (!appIdentifier && workflow.identifier) {
|
|
82
|
-
const match = workflow.identifier.match(/^@([^@]+)@/);
|
|
83
|
-
if (match) {
|
|
84
|
-
// Keep the @ prefix in the appIdentifier
|
|
85
|
-
appIdentifier = `@${match[1]}`;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (appIdentifier) {
|
|
89
|
-
if (!workflowsByApp.has(appIdentifier)) {
|
|
90
|
-
workflowsByApp.set(appIdentifier, []);
|
|
91
|
-
}
|
|
92
|
-
const workflows = workflowsByApp.get(appIdentifier);
|
|
93
|
-
if (workflows) {
|
|
94
|
-
workflows.push(workflow);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
unassigned.push(workflow);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const resolvedDir = path.resolve(process.cwd(), outputDir);
|
|
102
|
-
for (const [appIdentifier, workflows] of workflowsByApp.entries()) {
|
|
103
|
-
const exportObj = { appIdentifier, workflows: [] };
|
|
104
|
-
for (const workflow of workflows) {
|
|
105
|
-
exportObj.workflows.push(await buildExportWorkflow(workflow, allSchedules, appIdentifier));
|
|
106
|
-
}
|
|
107
|
-
const safeAppIdentifier = appIdentifier.replace(/[\\/]/g, '-');
|
|
108
|
-
const filePath = path.join(resolvedDir, `workflows-${safeAppIdentifier}.yml`);
|
|
109
|
-
await writeYamlFile(exportObj, filePath, overwrite);
|
|
110
|
-
console.log(`${chalk_1.default.green('EXPORTED')} ${filePath}`);
|
|
111
|
-
}
|
|
112
|
-
if (unassigned.length > 0) {
|
|
113
|
-
const unassignedObj = { workflows: [] };
|
|
114
|
-
for (const workflow of unassigned) {
|
|
115
|
-
unassignedObj.workflows.push(await buildExportWorkflow(workflow, allSchedules));
|
|
116
|
-
}
|
|
117
|
-
const unassignedFilePath = path.join(resolvedDir, 'workflows-unassigned.yml');
|
|
118
|
-
await writeYamlFile(unassignedObj, unassignedFilePath, overwrite);
|
|
119
|
-
console.log(`${chalk_1.default.yellow('EXPORTED')} ${unassignedFilePath} (unassigned workflows)`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
exports.exportWorkflowsByApp = exportWorkflowsByApp;
|
|
123
|
-
async function exportWorkflowsToFile(fileLocation, overwrite) {
|
|
124
|
-
const [allWorkflowsInOrg, allSchedules] = await Promise.all([
|
|
125
|
-
(0, event_workflow_templates_1.getExistingWorkflowTemplates)(),
|
|
126
|
-
(0, schedules_1.getExistingSchedules)(),
|
|
127
|
-
]);
|
|
49
|
+
async function exportWorkflows(fileLocation, overwrite) {
|
|
50
|
+
const allWorkflowsInOrg = await (0, event_workflow_templates_1.getExistingWorkflowTemplates)();
|
|
128
51
|
const workflowsObj = { workflows: [] };
|
|
129
52
|
for (const workflow of allWorkflowsInOrg) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
'isActive',
|
|
142
|
-
'messageGroup',
|
|
143
|
-
'dynamicMessageGroupId',
|
|
144
|
-
'parallelWorkerCount',
|
|
145
|
-
'workflowDefinition',
|
|
146
|
-
]);
|
|
147
|
-
const schedules = allSchedules.filter((s) => s.ownerReference === `event-workflow-template:${workflow.id}`);
|
|
148
|
-
if (schedules.length > 0) {
|
|
149
|
-
exportWorkflow.crons = schedules.map((s) => ({ name: s.name, cron: s.cron }));
|
|
150
|
-
}
|
|
151
|
-
await (0, helper_1.populateActionIdentifiers)(exportWorkflow);
|
|
152
|
-
// Shortened keys can only be resolved back to a full action identifier on
|
|
153
|
-
// import if an appIdentifier is known. Without one (e.g. unassigned
|
|
154
|
-
// workflows), keep the full identifier so the workflow stays re-importable.
|
|
155
|
-
if (appIdentifier) {
|
|
156
|
-
(0, helper_1.extractShortActionKeys)(exportWorkflow);
|
|
157
|
-
}
|
|
158
|
-
return exportWorkflow;
|
|
159
|
-
}
|
|
160
|
-
async function writeYamlFile(data, absolutePath, overwrite) {
|
|
161
|
-
if (fs_1.default.existsSync(absolutePath)) {
|
|
162
|
-
await (0, file_helpers_1.safeFileOverwrite)(absolutePath, overwrite);
|
|
53
|
+
const exportWorkflow = lodash_1.default.pick(workflow, [
|
|
54
|
+
'triggerKey',
|
|
55
|
+
'workflowDefinition',
|
|
56
|
+
'name',
|
|
57
|
+
'isActive',
|
|
58
|
+
'identifier',
|
|
59
|
+
'messageGroup',
|
|
60
|
+
'dynamicMessageGroupId',
|
|
61
|
+
]);
|
|
62
|
+
await (0, helper_1.populateActionIdentifiers)(exportWorkflow);
|
|
63
|
+
workflowsObj.workflows.push(exportWorkflow);
|
|
163
64
|
}
|
|
164
|
-
|
|
165
|
-
fs_1.default.writeFileSync(absolutePath, js_yaml_1.default.dump(data), 'utf8');
|
|
65
|
+
await (0, file_helpers_1.saveData)(workflowsObj, fileLocation, overwrite);
|
|
166
66
|
}
|
|
67
|
+
exports.exportWorkflows = exportWorkflows;
|