@contrail/cli 2.0.6 → 2.0.8

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 CHANGED
@@ -15,7 +15,7 @@ $ npm install -g @contrail/cli
15
15
  $ contrail COMMAND
16
16
  running command...
17
17
  $ contrail (--version)
18
- @contrail/cli/2.0.6 darwin-arm64 node-v18.13.0
18
+ @contrail/cli/2.0.8 darwin-arm64 node-v18.13.0
19
19
  $ contrail --help [COMMAND]
20
20
  USAGE
21
21
  $ contrail COMMAND
@@ -331,11 +331,11 @@ Export all groups in the org.
331
331
 
332
332
  ```
333
333
  USAGE
334
- $ contrail groups export [-d <value>] [--overwrite]
334
+ $ contrail groups export [-f <value>] [--overwrite]
335
335
 
336
336
  FLAGS
337
- -d, --outDir=<value> [default: groups.yml] The location for the file to be saved
338
- --overwrite Replaces existing files if they exist
337
+ -f, --outFile=<value> [default: groups.yml] The location for the file to be saved
338
+ --overwrite Replaces existing files if they exist
339
339
 
340
340
  DESCRIPTION
341
341
  Export all groups in the org.
@@ -388,11 +388,12 @@ Exports an data of an instance including types and users
388
388
 
389
389
  ```
390
390
  USAGE
391
- $ contrail instance export [--outDir <value>] [--overwrite]
391
+ $ contrail instance export [-d <value>] [-f]
392
392
 
393
393
  FLAGS
394
- --outDir=<value> [default: .] The directory to which to export the instance data. Defaults to the current directory
395
- --overwrite Replaces any existing export files in outDir
394
+ -d, --outDir=<value> [default: .] The directory to which to export the instance data. Defaults to the current
395
+ directory
396
+ -f, --overwrite Replaces any existing export files in outDir
396
397
 
397
398
  DESCRIPTION
398
399
  Exports an data of an instance including types and users
@@ -407,10 +408,11 @@ Import the data to an instance, including types and users
407
408
 
408
409
  ```
409
410
  USAGE
410
- $ contrail instance import [--dir <value>]
411
+ $ contrail instance import [-d <value>]
411
412
 
412
413
  FLAGS
413
- --dir=<value> [default: .] The directory from which to export the instance data. Defaults to the current directory
414
+ -d, --dir=<value> [default: .] The directory from which to export the instance data. Defaults to the current
415
+ directory
414
416
 
415
417
  DESCRIPTION
416
418
  Import the data to an instance, including types and users
@@ -1088,11 +1090,11 @@ Export all users in the org.
1088
1090
 
1089
1091
  ```
1090
1092
  USAGE
1091
- $ contrail users export [-d <value>] [--overwrite]
1093
+ $ contrail users export [-f <value>] [--overwrite]
1092
1094
 
1093
1095
  FLAGS
1094
- -d, --outDir=<value> [default: users.yml] The location for the file to be saved
1095
- --overwrite Replaces existing files if they exist
1096
+ -f, --outFile=<value> [default: users.yml] The location for the file to be saved
1097
+ --overwrite Replaces existing files if they exist
1096
1098
 
1097
1099
  DESCRIPTION
1098
1100
  Export all users in the org.
package/dist/api/apps.js CHANGED
@@ -17,6 +17,7 @@ async function getAllAvailableApps(options) {
17
17
  criteria: {
18
18
  orgId: options.orgId,
19
19
  private: false,
20
+ hidden: true,
20
21
  },
21
22
  }));
22
23
  return apps;
@@ -0,0 +1,2 @@
1
+ export declare function getUserOrgs(options?: any): Promise<any[]>;
2
+ export declare function getGroups(options?: any): Promise<any[]>;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getGroups = exports.getUserOrgs = void 0;
4
+ const sdk_1 = require("@contrail/sdk");
5
+ async function getUserOrgs(options) {
6
+ return new sdk_1.Entities().get({
7
+ entityName: 'user-org', ...options
8
+ });
9
+ }
10
+ exports.getUserOrgs = getUserOrgs;
11
+ async function getGroups(options) {
12
+ return new sdk_1.Entities().get({
13
+ entityName: 'group', ...options
14
+ });
15
+ }
16
+ exports.getGroups = getGroups;
@@ -4,7 +4,7 @@ export default class ExportGroups extends Command {
4
4
  static description: string;
5
5
  static examples: string[];
6
6
  static flags: {
7
- outDir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
7
+ outFile: 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
  };
10
10
  run(): Promise<void>;
@@ -3,13 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.exportGroups = exports.DEFAULT_FILE_LOCATION = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const path = tslib_1.__importStar(require("path"));
6
- const sdk_1 = require("@contrail/sdk");
7
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
8
7
  const fs_1 = tslib_1.__importDefault(require("fs"));
9
8
  const login_1 = require("../../auth/login");
10
9
  const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
11
10
  const core_1 = require("@oclif/core");
12
11
  const file_helpers_1 = require("../../helpers/file-helpers");
12
+ const users_1 = require("../../api/users");
13
13
  exports.DEFAULT_FILE_LOCATION = 'groups.yml';
14
14
  class ExportGroups extends core_1.Command {
15
15
  async run() {
@@ -18,7 +18,7 @@ class ExportGroups extends core_1.Command {
18
18
  try {
19
19
  await (0, login_1.authUser)();
20
20
  this.log('%s Getting Groups.', chalk_1.default.blue.bold('START'));
21
- await exportGroups((_a = flags.outDir) !== null && _a !== void 0 ? _a : exports.DEFAULT_FILE_LOCATION, flags.overwrite);
21
+ await exportGroups((_a = flags.outFile) !== null && _a !== void 0 ? _a : exports.DEFAULT_FILE_LOCATION, flags.overwrite);
22
22
  this.log('%s Done', chalk_1.default.yellow.bold('GROUPS'));
23
23
  }
24
24
  catch (e) {
@@ -35,16 +35,12 @@ ExportGroups.examples = [
35
35
  `,
36
36
  ];
37
37
  ExportGroups.flags = {
38
- outDir: core_1.Flags.string({ char: 'd', description: 'The location for the file to be saved', required: false, default: exports.DEFAULT_FILE_LOCATION }),
38
+ outFile: core_1.Flags.string({ char: 'f', description: 'The location for the file to be saved', required: false, default: exports.DEFAULT_FILE_LOCATION }),
39
39
  overwrite: core_1.Flags.boolean({ description: 'Replaces existing files if they exist', required: false, default: false })
40
40
  };
41
41
  async function exportGroups(fileLocation, overwrite) {
42
- const allGroups = await new sdk_1.Entities().get({
43
- entityName: 'group', relations: ['groupPrincipals']
44
- });
45
- const allUsersInOrg = await new sdk_1.Entities().get({
46
- entityName: 'user-org',
47
- });
42
+ const allGroups = await (0, users_1.getGroups)({ relations: ['groupPrincipals'] });
43
+ const allUsersInOrg = await (0, users_1.getUserOrgs)();
48
44
  const groups = [];
49
45
  for (const group of allGroups) {
50
46
  const groupPrincipals = getGroupPrincipals(group, allGroups, allUsersInOrg);
@@ -53,19 +49,26 @@ async function exportGroups(fileLocation, overwrite) {
53
49
  groupPrincipals,
54
50
  });
55
51
  }
52
+ console.log("saving groups:", JSON.stringify(groups));
56
53
  await saveData(groups, fileLocation, overwrite);
57
54
  }
58
55
  exports.exportGroups = exportGroups;
59
56
  async function saveData(data, fileLocation, overwrite) {
60
57
  const yamlStr = js_yaml_1.default.dump(data);
58
+ console.log("file location:", fileLocation);
59
+ console.log("final yml string:", yamlStr);
61
60
  const CURRENT_DIR = process.cwd();
62
- const pathToWrite = path.resolve(path.join(CURRENT_DIR, fileLocation));
61
+ const pathToWrite = path.join(CURRENT_DIR, fileLocation);
63
62
  if (fs_1.default.existsSync(pathToWrite)) {
64
63
  await (0, file_helpers_1.safeFileOverwrite)(pathToWrite, overwrite);
65
64
  }
66
65
  fs_1.default.writeFileSync(pathToWrite, yamlStr, 'utf8');
67
66
  }
68
67
  function getGroupPrincipals(group, allGroups, orgUsers) {
68
+ if (!(group === null || group === void 0 ? void 0 : group.groupPrincipals)) {
69
+ return [];
70
+ }
71
+ console.log("all groups:", allGroups.map(x => x.id));
69
72
  const principals = [];
70
73
  for (const principal of group.groupPrincipals) {
71
74
  const [type, id] = principal.principalReference.split(':');
@@ -83,6 +86,7 @@ function getGroupPrincipals(group, allGroups, orgUsers) {
83
86
  }
84
87
  else if (type === 'group') {
85
88
  const nestedGroup = allGroups.find(x => x.id === id);
89
+ console.log("looking for group", id);
86
90
  principals.push({
87
91
  type: 'group',
88
92
  name: nestedGroup.name
@@ -56,12 +56,12 @@ async function uploadGroups(fileLocation) {
56
56
  });
57
57
  const toDelete = existingGroups.filter(x => !groupsToImport.map(y => y.name).includes(x.name));
58
58
  const toCreate = groupsToImport.filter(x => !existingGroups.map(y => y.name).includes(x.name));
59
- const toUpdate = existingGroups.filter(x => groupsToImport.map(y => y.name).includes(x.name));
59
+ const toUpdate = groupsToImport.filter(x => existingGroups.map(y => y.name).includes(x.name));
60
60
  const createdGroups = [];
61
61
  // it's important to create all the groups first, to resolve any downstream principal dependencies
62
62
  for (const group of toCreate) {
63
63
  if (!group.groupPrincipals || !group.name) {
64
- console.log('skipping invalid group');
64
+ console.log('skipping create of invalid group');
65
65
  }
66
66
  createdGroups.push(await new sdk_1.Entities().create({
67
67
  entityName: 'group', object: { name: group.name }
@@ -77,90 +77,67 @@ async function uploadGroups(fileLocation) {
77
77
  }
78
78
  for (const group of toUpdate) {
79
79
  if (!group.groupPrincipals || !group.name) {
80
- console.log('skipping invalid group');
80
+ console.log('skipping update invalid group');
81
81
  }
82
- await updateGroup(group.groupPrincipals, orgUsers, group, allGroupsAfterCreate);
82
+ const existingGroup = existingGroups.find(x => x.name === group.name);
83
+ await updateGroup(group.groupPrincipals, orgUsers, existingGroup, allGroupsAfterCreate);
83
84
  }
84
85
  for (const group of toDelete) {
85
86
  if (!group.groupPrincipals || !group.name) {
86
- console.log('skipping invalid group');
87
+ console.log('skipping delete of invalid group');
87
88
  }
88
89
  await new sdk_1.Entities().delete({ entityName: 'group', id: group.id });
89
90
  }
90
91
  }
91
92
  exports.uploadGroups = uploadGroups;
92
93
  async function updateGroup(principals, orgUsers, existingGroup, allGroupsAfterCreate) {
93
- const existingPrincipals = existingGroup.groupPrincipals;
94
- for (const principal of principals) {
95
- if (principal.type === 'user') {
96
- const user = orgUsers.map(x => x.user).find(x => x.email === (principal === null || principal === void 0 ? void 0 : principal.email));
97
- if (!user) {
98
- console.log(`user ${principal.email} is not in this org's user list`);
99
- }
100
- const principalId = `user:${user.id}`;
101
- if (existingPrincipals.map(x => x.principalReference).includes(principalId)) {
102
- continue;
103
- }
104
- await new sdk_1.Entities().update({
105
- entityName: 'group-principal', object: {
106
- principalReference: `user:${user.id}`,
107
- groupId: existingGroup.id
108
- }
109
- });
94
+ var _a;
95
+ const principalsToUpdate = principals
96
+ .map(x => groupPrincipalNameToId(x, allGroupsAfterCreate, orgUsers))
97
+ .filter(x => !!x)
98
+ .map(x => { return { principalReference: `${x.type}:${x.identifier}`, groupId: existingGroup.id }; });
99
+ await new sdk_1.Entities().batchCreate({
100
+ entityName: 'group-principal', objects: {
101
+ principalReferences: principalsToUpdate.map(x => x.principalReference),
102
+ groupIds: [existingGroup.id]
110
103
  }
111
- else if (principal.type === 'group') {
112
- const group = allGroupsAfterCreate.find(x => x.name === principal.name);
113
- if (!group) {
114
- console.log(`group ${principal.name} is not in this org's group list`);
115
- }
116
- const principalId = `group:${principal.name}`;
117
- if (existingPrincipals.map(x => x.principalReference).includes(principalId)) {
118
- continue;
119
- }
120
- await new sdk_1.Entities().update({
121
- entityName: 'group-principal', object: {
122
- principalReference: `group:${group.id}`,
123
- groupId: existingGroup.id
124
- }
125
- });
126
- }
127
- }
104
+ });
105
+ // remove principals
106
+ const principalsToKeep = principalsToUpdate.map(principal => `${principal.type}:${principal.identifier}`);
107
+ const principalsToDelete = ((_a = existingGroup === null || existingGroup === void 0 ? void 0 : existingGroup.groupPrincipals) !== null && _a !== void 0 ? _a : [])
108
+ .map(x => groupPrincipalNameToId(x, allGroupsAfterCreate, orgUsers))
109
+ .filter(x => !!x)
110
+ .filter(x => !principalsToKeep.includes(`${x.type}:${x.identifier}`));
111
+ await principalsToDelete.forEach(async (principal) => {
112
+ await new sdk_1.Entities().delete({
113
+ entityName: 'group-principal', id: principal.cloudId
114
+ });
115
+ });
128
116
  }
129
117
  async function createGroupPrincipals(group, principals, orgUsers, allGroupsAfterCreate) {
130
- for (const principal of principals) {
131
- if (principal.type === 'user') {
132
- try {
133
- const user = orgUsers.map(x => x.user).find(x => x.email === (principal === null || principal === void 0 ? void 0 : principal.email));
134
- if (!user) {
135
- console.log(`user ${principal.email} is not in this org's user list`);
136
- }
137
- await new sdk_1.Entities().create({
138
- entityName: 'group-principal', object: {
139
- principalReference: `user:${user.id}`,
140
- groupId: group.id
141
- }
142
- });
143
- }
144
- catch (e) {
145
- console.log('could not create group principal');
146
- }
118
+ const principalsToCreate = principals
119
+ .map(x => groupPrincipalNameToId(x, allGroupsAfterCreate, orgUsers))
120
+ .filter(x => !!x)
121
+ .map(x => { return { principalReference: `${x.type}:${x.identifier}`, groupId: group.id }; });
122
+ await new sdk_1.Entities().batchCreate({
123
+ entityName: 'group-principal', objects: principalsToCreate
124
+ });
125
+ }
126
+ function groupPrincipalNameToId(principal, groups, orgUsers) {
127
+ if (principal.type === 'user') {
128
+ const user = orgUsers.map(x => x.user).find(x => x.email === (principal === null || principal === void 0 ? void 0 : principal.email));
129
+ if (!user) {
130
+ console.log(`user ${principal.email} is not in this org's user list`);
131
+ return undefined;
147
132
  }
148
- else if (principal.type === 'group') {
149
- try {
150
- const nestedGroup = allGroupsAfterCreate.find(x => x.name === principal.name);
151
- if (!nestedGroup) {
152
- console.log(`group ${principal.name} is not in this org's group list`);
153
- }
154
- await new sdk_1.Entities().create({
155
- entityName: 'group-principal', object: {
156
- principalReference: `group:${nestedGroup.id}`,
157
- groupId: group.id
158
- }
159
- });
160
- }
161
- catch (e) {
162
- console.log('could not create nested group');
163
- }
133
+ return { type: 'user', identifier: user.id, cloudId: principal.id };
134
+ }
135
+ else if (principal.type === 'group') {
136
+ const group = groups.find(x => x.name === principal.name);
137
+ if (!group) {
138
+ console.log(`group ${principal.name} is not in this org's group list`);
139
+ return undefined;
164
140
  }
141
+ return { type: 'group', identifier: group.id, cloudId: principal.id };
165
142
  }
166
143
  }
@@ -5,11 +5,13 @@ const path = tslib_1.__importStar(require("path"));
5
5
  const core_1 = require("@oclif/core");
6
6
  const getAll_1 = require("../types/getAll");
7
7
  const export_1 = require("../users/export");
8
+ const export_2 = require("../groups/export");
8
9
  class ExportInstance extends core_1.Command {
9
10
  async run() {
10
11
  const { flags } = await this.parse(ExportInstance);
11
12
  await (0, getAll_1.getAll)(flags.outDir, flags.overwrite);
12
13
  await (0, export_1.exportUsers)(path.join(flags.outDir, 'users.yml'), flags.overwrite);
14
+ await (0, export_2.exportGroups)(path.join(flags.outDir, 'groups.yml'), flags.overwrite);
13
15
  }
14
16
  }
15
17
  exports.default = ExportInstance;
@@ -19,6 +21,6 @@ ExportInstance.examples = [
19
21
  `,
20
22
  ];
21
23
  ExportInstance.flags = {
22
- outDir: core_1.Flags.string({ description: 'The directory to which to export the instance data. Defaults to the current directory', required: false, default: '.' }),
23
- overwrite: core_1.Flags.boolean({ description: 'Replaces any existing export files in outDir', required: false, default: false })
24
+ outDir: core_1.Flags.string({ char: 'd', description: 'The directory to which to export the instance data. Defaults to the current directory', required: false, default: '.' }),
25
+ overwrite: core_1.Flags.boolean({ char: 'f', description: 'Replaces any existing export files in outDir', required: false, default: false })
24
26
  };
@@ -5,11 +5,15 @@ const path = tslib_1.__importStar(require("path"));
5
5
  const core_1 = require("@oclif/core");
6
6
  const loadAll_1 = require("../types/loadAll");
7
7
  const upload_1 = require("../users/upload");
8
+ const upload_2 = require("../groups/upload");
9
+ const uploadTypePropertyPolicies_1 = require("../types/uploadTypePropertyPolicies");
8
10
  class ImportInstance extends core_1.Command {
9
11
  async run() {
10
12
  const { flags } = await this.parse(ImportInstance);
11
- await (0, loadAll_1.loadAll)(flags.dir);
13
+ await (0, loadAll_1.loadAll)(flags.dir, true);
12
14
  await (0, upload_1.uploadUsers)(path.join(flags.dir, 'users.yml'));
15
+ await (0, upload_2.uploadGroups)(path.join(flags.dir, 'groups.yml'));
16
+ await (0, uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)(flags.dir);
13
17
  }
14
18
  }
15
19
  exports.default = ImportInstance;
@@ -19,5 +23,5 @@ ImportInstance.examples = [
19
23
  `,
20
24
  ];
21
25
  ImportInstance.flags = {
22
- dir: core_1.Flags.string({ description: 'The directory from which to export the instance data. Defaults to the current directory', required: false, default: '.' }),
26
+ dir: core_1.Flags.string({ char: 'd', description: 'The directory from which to export the instance data. Defaults to the current directory', required: false, default: '.' }),
23
27
  };
@@ -30,4 +30,4 @@ export default class GetAllTypes extends Command {
30
30
  }
31
31
  export declare function getAll(outDir: string, overwrite: boolean): Promise<void>;
32
32
  export declare function reduceTypes(dataHolder: DataHolder): any[];
33
- export declare function reduceData(data: DataHolder): ReducedDataHolder;
33
+ export declare function reduceData(data: DataHolder): Promise<ReducedDataHolder>;
@@ -10,6 +10,7 @@ const path = tslib_1.__importStar(require("path"));
10
10
  const type_extractor_1 = require("../../helpers/types/type-extractor");
11
11
  const types_1 = require("../../api/types");
12
12
  const file_helpers_1 = require("../../helpers/file-helpers");
13
+ const users_1 = require("../../api/users");
13
14
  const yaml = require('js-yaml');
14
15
  class GetAllTypes extends core_1.Command {
15
16
  async run() {
@@ -45,7 +46,7 @@ async function getAll(outDir, overwrite) {
45
46
  typePropertyPolicies: await (0, types_1.getTypePropertyPolicies)(),
46
47
  typePropertyPolicyLinks: await (0, types_1.getTypePropertyPolicyLinks)(),
47
48
  };
48
- const reducedData = reduceData(data);
49
+ const reducedData = await reduceData(data);
49
50
  await saveData(reducedData, outDir, overwrite);
50
51
  console.log('%s Retrieved', chalk_1.default.yellow.bold('TYPES'));
51
52
  }
@@ -56,14 +57,43 @@ async function getAll(outDir, overwrite) {
56
57
  console.log('%s Types Retrieval Complete.', chalk_1.default.green.bold('COMPLETE'));
57
58
  }
58
59
  exports.getAll = getAll;
59
- function reduceTypePropertyPolicies(data) {
60
+ async function reduceTypePropertyPolicies(data) {
60
61
  var _a;
62
+ const orgUsers = await (0, users_1.getUserOrgs)();
63
+ const groups = await (0, users_1.getGroups)();
64
+ function reducePrincipal(statement) {
65
+ var _a, _b;
66
+ if (!(statement === null || statement === void 0 ? void 0 : statement.principal)) {
67
+ return statement;
68
+ }
69
+ const [type, id] = statement.principal.split(':');
70
+ if (!type || !id) {
71
+ return statement;
72
+ }
73
+ if (type === 'group') {
74
+ const groupName = (_a = groups.find(x => x.id === id)) === null || _a === void 0 ? void 0 : _a.name;
75
+ if (!groupName) {
76
+ console.log(`Could not find group for statement. skipping invalid policy conversion ${id}`);
77
+ return statement;
78
+ }
79
+ statement.principal = `group:${groupName}`;
80
+ }
81
+ else if (type === 'user') {
82
+ const userEmail = (_b = orgUsers.find(x => x.user.id === id)) === null || _b === void 0 ? void 0 : _b.email;
83
+ if (!userEmail) {
84
+ console.log(`Could not find group for statement. skipping invalid policy conversion ${id}`);
85
+ return statement;
86
+ }
87
+ statement.principal = `user:${userEmail}`;
88
+ }
89
+ return statement;
90
+ }
61
91
  return (_a = data === null || data === void 0 ? void 0 : data.typePropertyPolicies) === null || _a === void 0 ? void 0 : _a.map(x => {
62
92
  return {
63
93
  label: x.label,
64
94
  policy: {
65
95
  version: x.policy.version,
66
- statements: x.policy.statements,
96
+ statements: x.policy.statements.map(x => reducePrincipal(x)),
67
97
  }
68
98
  };
69
99
  });
@@ -195,7 +225,7 @@ function reduceTypes(dataHolder) {
195
225
  return reducedTypes;
196
226
  }
197
227
  exports.reduceTypes = reduceTypes;
198
- function reduceData(data) {
228
+ async function reduceData(data) {
199
229
  return {
200
230
  reducedTypes: reduceTypes(data),
201
231
  reducedOptionSets: reduceOptionSets(data),
@@ -203,7 +233,7 @@ function reduceData(data) {
203
233
  reducedRuleSets: reduceRuleSets(data),
204
234
  reducedRuleSetLinks: reduceRuleSetLinks(data),
205
235
  reducedUsageInfo: reduceUsageInfo(data),
206
- reducedTypePropertyPolicies: reduceTypePropertyPolicies(data),
236
+ reducedTypePropertyPolicies: await reduceTypePropertyPolicies(data),
207
237
  reducedTypePropertyPolicyLinks: reduceTypePropertyPolicyLinks(data),
208
238
  };
209
239
  }
@@ -7,4 +7,4 @@ export default class LoadAllTypes extends Command {
7
7
  };
8
8
  run(): Promise<void>;
9
9
  }
10
- export declare function loadAll(dir: string): Promise<void>;
10
+ export declare function loadAll(dir: string, skipPolicies?: boolean): Promise<void>;
@@ -26,7 +26,7 @@ LoadAllTypes.examples = [
26
26
  LoadAllTypes.flags = {
27
27
  dir: core_1.Flags.string({ char: 'd', description: 'The directory from which to load the types data. Defaults to the current directory.', required: false, default: '.' }),
28
28
  };
29
- async function loadAll(dir) {
29
+ async function loadAll(dir, skipPolicies = false) {
30
30
  try {
31
31
  await (0, login_1.authUser)();
32
32
  await (0, uploadOptionSets_1.uploadOptionSets)(dir);
@@ -34,7 +34,9 @@ async function loadAll(dir) {
34
34
  await (0, type_uploader_1.uploadTypes)(dir);
35
35
  await (0, uploadUsageInfo_1.uploadUsageInfo)(dir);
36
36
  await (0, uploadRuleSets_1.uploadRuleSets)(dir);
37
- await (0, uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)(dir);
37
+ if (!skipPolicies) {
38
+ await (0, uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)(dir);
39
+ }
38
40
  }
39
41
  catch (e) {
40
42
  console.log(`%s Types Upload Failed. ${e}`, chalk_1.default.red.bold('FAIL'));
@@ -9,6 +9,7 @@ const login_1 = require("../../auth/login");
9
9
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
10
10
  const core_1 = require("@oclif/core");
11
11
  const type_extractor_1 = require("../../helpers/types/type-extractor");
12
+ const users_1 = require("../../api/users");
12
13
  const yaml = require('js-yaml');
13
14
  class UploadTypePropertyPolicies extends core_1.Command {
14
15
  async run() {
@@ -58,6 +59,42 @@ function loadTPPolicies(fromDir) {
58
59
  }
59
60
  }
60
61
  async function uploadTPPolicyObjects(reducedTypePropertyPolicies) {
62
+ const orgUsers = await (0, users_1.getUserOrgs)();
63
+ const groups = await (0, users_1.getGroups)();
64
+ function fillPrincipal(statement) {
65
+ const principal = statement === null || statement === void 0 ? void 0 : statement.principal;
66
+ if (!principal) {
67
+ console.warn(`skipping bad statement principal ${statement === null || statement === void 0 ? void 0 : statement.principal}`);
68
+ return statement;
69
+ }
70
+ const [type, identifier] = principal.split(':');
71
+ if (!type || !identifier) {
72
+ console.warn(`skipping bad statement principal ${statement === null || statement === void 0 ? void 0 : statement.principal}`);
73
+ return statement;
74
+ }
75
+ if (type === 'group') {
76
+ const group = groups.find(x => x.name === identifier);
77
+ if (!group) {
78
+ console.log(`Could not find group ${identifier}`);
79
+ }
80
+ else {
81
+ statement.principal = `group:${group.id}`;
82
+ }
83
+ }
84
+ else if (type === 'user') {
85
+ const orgUser = orgUsers.find(x => x.user.email === identifier);
86
+ if (!orgUser) {
87
+ console.log(`Could not find user ${identifier}`);
88
+ }
89
+ else {
90
+ statement.principal = `user:${orgUser.user.id}`;
91
+ }
92
+ }
93
+ return statement;
94
+ }
95
+ reducedTypePropertyPolicies.forEach(policy => {
96
+ policy.policy.statements = policy.policy.statements.map(x => fillPrincipal(x));
97
+ });
61
98
  const currentTPPolicies = await new sdk_1.Entities().get({
62
99
  entityName: 'type-property-policy',
63
100
  });
@@ -69,8 +106,6 @@ async function uploadTPPolicyObjects(reducedTypePropertyPolicies) {
69
106
  for (const loadedTPP of reducedTypePropertyPolicies) {
70
107
  loadedTPPByName[loadedTPP.label] = loadedTPP;
71
108
  }
72
- // console.debug('current tp policy:', currentTPPolicies);
73
- // console.debug('reduced tp policy:', reducedTypePropertyPolicies);
74
109
  for (const [label, loadedTPP] of Object.entries(loadedTPPByName)) {
75
110
  try {
76
111
  const found = currentTPPByLabel[label];
@@ -137,8 +172,6 @@ async function uploadTypePropertyPolicyLinks(reducedTypePropertyPolicyLinks) {
137
172
  const hash = `${loadedLink.typePropertySlug}|${loadedLink.typePropertyPolicyLabel}`;
138
173
  reducedLinksByHashes[hash] = loadedLink;
139
174
  }
140
- console.debug('current type property policy links:', currentLinksHashes);
141
- console.debug('reduced type property policy links:', reducedLinksByHashes);
142
175
  for (const loadedTPPLink of reducedTypePropertyPolicyLinks) {
143
176
  try {
144
177
  const found = currentLinksHashes[`${loadedTPPLink.typePropertySlug}|${loadedTPPLink.typePropertyPolicyLabel}`];
@@ -146,7 +179,6 @@ async function uploadTypePropertyPolicyLinks(reducedTypePropertyPolicyLinks) {
146
179
  if (!transformReducedLink(loadedTPPLink, typeProperties, currentTPPs)) {
147
180
  continue;
148
181
  }
149
- console.log('Creating: ', loadedTPPLink);
150
182
  await new sdk_1.Entities().create({
151
183
  entityName: 'type-property-policy-link',
152
184
  object: {
@@ -159,7 +191,6 @@ async function uploadTypePropertyPolicyLinks(reducedTypePropertyPolicyLinks) {
159
191
  if (!transformReducedLink(loadedTPPLink, typeProperties, currentTPPs)) {
160
192
  continue;
161
193
  }
162
- console.log('skipping existing link: ', loadedTPPLink);
163
194
  }
164
195
  }
165
196
  catch (err) {
@@ -4,7 +4,7 @@ export default class ExportUsers extends Command {
4
4
  static description: string;
5
5
  static examples: string[];
6
6
  static flags: {
7
- outDir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
7
+ outFile: 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
  };
10
10
  run(): Promise<void>;
@@ -3,13 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.exportUsers = exports.DEFAULT_FILE_LOCATION = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const path = tslib_1.__importStar(require("path"));
6
- const sdk_1 = require("@contrail/sdk");
7
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
8
7
  const fs_1 = tslib_1.__importDefault(require("fs"));
9
8
  const login_1 = require("../../auth/login");
10
9
  const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
11
10
  const core_1 = require("@oclif/core");
12
11
  const file_helpers_1 = require("../../helpers/file-helpers");
12
+ const users_1 = require("../../api/users");
13
13
  exports.DEFAULT_FILE_LOCATION = 'users.yml';
14
14
  class ExportUsers extends core_1.Command {
15
15
  async run() {
@@ -18,7 +18,7 @@ class ExportUsers extends core_1.Command {
18
18
  try {
19
19
  await (0, login_1.authUser)();
20
20
  this.log('%s Getting Users.', chalk_1.default.blue.bold('START'));
21
- await exportUsers((_a = flags.outDir) !== null && _a !== void 0 ? _a : exports.DEFAULT_FILE_LOCATION, flags.overwrite);
21
+ await exportUsers((_a = flags.outFile) !== null && _a !== void 0 ? _a : exports.DEFAULT_FILE_LOCATION, flags.overwrite);
22
22
  this.log('%s Done', chalk_1.default.yellow.bold('USERS'));
23
23
  }
24
24
  catch (e) {
@@ -35,13 +35,11 @@ ExportUsers.examples = [
35
35
  `,
36
36
  ];
37
37
  ExportUsers.flags = {
38
- outDir: core_1.Flags.string({ char: 'd', description: 'The location for the file to be saved', required: false, default: exports.DEFAULT_FILE_LOCATION }),
38
+ outFile: core_1.Flags.string({ char: 'f', description: 'The location for the file to be saved', required: false, default: exports.DEFAULT_FILE_LOCATION }),
39
39
  overwrite: core_1.Flags.boolean({ description: 'Replaces existing files if they exist', required: false, default: false })
40
40
  };
41
41
  async function exportUsers(fileLocation, overwrite) {
42
- const allUsersInOrg = await new sdk_1.Entities().get({
43
- entityName: 'user-org',
44
- });
42
+ const allUsersInOrg = await (0, users_1.getUserOrgs)();
45
43
  const users = [];
46
44
  for (const userInOrg of allUsersInOrg) {
47
45
  delete userInOrg.user.id;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.safeFileOverwrite = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
6
- const PASS_FORCE_TO_OVERWRITE = 'Pass --force to overwrite without prompting';
6
+ const PASS_FORCE_TO_OVERWRITE = 'Pass --overwrite to overwrite without prompting';
7
7
  //throws an error if the user declines to overwrite the file
8
8
  async function safeFileOverwrite(fileLocation, overwrite) {
9
9
  if (overwrite) {
@@ -8,6 +8,7 @@ const apps_1 = require("../../api/apps");
8
8
  const event_workflow_templates_1 = require("../../api/event-workflow-templates");
9
9
  const event_workflow_template_definition_1 = require("../../api/event-workflow-template-definition");
10
10
  const actions_deployer_1 = require("../app/actions-deployer");
11
+ const interface_converters_1 = require("../../interface-converters");
11
12
  class WorkflowTemplateDefinitionDeployer {
12
13
  async cliInstallWorkflowTemplateDefinitions() {
13
14
  const installedApps = await (0, apps_1.getInstalledApps)();
@@ -94,6 +95,7 @@ class WorkflowTemplateDefinitionDeployer {
94
95
  console.log(`%s \tDeploying workflow definitions: ${workflowKey}`, chalk_1.default.yellow.bold('WORKFLOW DEFINITION'));
95
96
  const identifier = this.getWorkflowDeclarationIdentifier(workflowKey, app);
96
97
  const existingDefinition = existingWorkflows.find(ad => ad.identifier === identifier);
98
+ (0, interface_converters_1.convertWorkflow)(workflow);
97
99
  if (existingDefinition) {
98
100
  return await this.updateWorkflowTemplateDefinition(existingDefinition, workflow, app);
99
101
  }
@@ -9,6 +9,7 @@ const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
9
9
  const event_workflow_action_definition_1 = require("../../api/event-workflow-action-definition");
10
10
  const event_workflow_templates_1 = require("../../api/event-workflow-templates");
11
11
  const login_1 = require("../../auth/login");
12
+ const interface_converters_1 = require("../../interface-converters");
12
13
  const yaml = require('js-yaml');
13
14
  class LocalWorkflowTemplateDeployer {
14
15
  async deployWorkflows(wtDeclaration) {
@@ -24,6 +25,7 @@ class LocalWorkflowTemplateDeployer {
24
25
  console.log(`%s \tDeploying workflow: ${workflowKey}`, chalk_1.default.yellow.bold('WORKFLOW TEMPLATES'));
25
26
  const identifier = getWorkflowTemplateIdentifier(workflowKey);
26
27
  const existingTemplate = existingWorkflows.find(ad => ad.identifier === identifier);
28
+ (0, interface_converters_1.convertWorkflow)(workflow);
27
29
  if (existingTemplate) {
28
30
  return await this.updateWorkflowTemplate(workflowKey, existingTemplate, workflow);
29
31
  }
@@ -47,6 +49,7 @@ class LocalWorkflowTemplateDeployer {
47
49
  }
48
50
  async populateActionIds(workflowTemplate, workflowKey) {
49
51
  var _a;
52
+ console.log('workflow tempalate:', JSON.stringify(workflowTemplate, null, 2));
50
53
  if (!((_a = workflowTemplate === null || workflowTemplate === void 0 ? void 0 : workflowTemplate.workflowDefinition) === null || _a === void 0 ? void 0 : _a.paths)) {
51
54
  return;
52
55
  }
@@ -70,11 +73,14 @@ class LocalWorkflowTemplateDeployer {
70
73
  }
71
74
  }
72
75
  async updateWorkflowTemplate(workflowKey, existingWorkflowTemplate, workflowTemplate) {
76
+ var _a, _b;
73
77
  await this.populateActionIds(workflowTemplate, workflowKey);
74
78
  const updates = Object.assign(new Object(), {
75
79
  name: workflowTemplate.name,
76
80
  triggerKey: workflowTemplate.triggerKey,
77
- workflowDefinition: workflowTemplate.workflowDefinition
81
+ workflowDefinition: workflowTemplate.workflowDefinition,
82
+ messageGroupId: (_a = workflowTemplate.messageGroupId) !== null && _a !== void 0 ? _a : existingWorkflowTemplate.messageGroupId,
83
+ parallelWorkerCount: (_b = workflowTemplate.parallelWorkerCount) !== null && _b !== void 0 ? _b : existingWorkflowTemplate.parallelWorkerCount,
78
84
  });
79
85
  const updatedWorkflowTemplate = await (0, event_workflow_templates_1.updateWorkflowTemplate)({ id: existingWorkflowTemplate.id, updates });
80
86
  console.log(`%s Updated existing workflow template: ${updatedWorkflowTemplate.identifier}`, chalk_1.default.yellow.bold('WORKFLOW TEMPLATES'));
@@ -157,7 +163,6 @@ function loadTemplateConfig(saveFile) {
157
163
  const TARGET_DIR = process.cwd();
158
164
  try {
159
165
  const doc = yaml.load(fs_1.default.readFileSync(path.resolve(path.join(TARGET_DIR, saveFile)), 'utf8'));
160
- console.log(doc);
161
166
  return doc;
162
167
  }
163
168
  catch (e) {
@@ -0,0 +1,3 @@
1
+ import { WorkflowDefinition, WorkflowTemplate } from './interfaces';
2
+ export declare const DEFAULT_WORKFLOW_PARALLELISM = 1;
3
+ export declare function convertWorkflow(workflow: WorkflowDefinition | WorkflowTemplate): void;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertWorkflow = exports.DEFAULT_WORKFLOW_PARALLELISM = void 0;
4
+ exports.DEFAULT_WORKFLOW_PARALLELISM = 1;
5
+ // parses the user-facing interface of the workflow and converts it to the cloud-facing format in-place.
6
+ function convertWorkflow(workflow) {
7
+ var _a;
8
+ workflow.parallelWorkerCount = (_a = workflow.parallelism) !== null && _a !== void 0 ? _a : exports.DEFAULT_WORKFLOW_PARALLELISM;
9
+ workflow.messageGroupId = workflow.groupId;
10
+ }
11
+ exports.convertWorkflow = convertWorkflow;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const interface_converters_1 = require("./interface-converters");
4
+ describe('Workflow Template converter tests', () => {
5
+ it('should use default parallelism', () => {
6
+ const workflow = {
7
+ identifier: 'identifier',
8
+ name: 'name1',
9
+ triggerKey: 'foo|bar',
10
+ workflowDefinition: {
11
+ paths: [
12
+ {
13
+ conditional: 'true',
14
+ actionDefinitionConfigs: [
15
+ {
16
+ action: 'logItem',
17
+ name: 'Log Item'
18
+ }
19
+ ]
20
+ }
21
+ ]
22
+ },
23
+ ownerKey: 'app:123',
24
+ isActive: true,
25
+ public: true,
26
+ // parallelism: 3,
27
+ };
28
+ (0, interface_converters_1.convertWorkflow)(workflow);
29
+ expect(workflow.parallelWorkerCount).toEqual(interface_converters_1.DEFAULT_WORKFLOW_PARALLELISM);
30
+ expect(workflow['concurrency']).toBeUndefined();
31
+ expect(workflow.messageGroupId).toBeUndefined();
32
+ });
33
+ it('should pass user concurrency as parallel count', () => {
34
+ const workflow = {
35
+ identifier: 'identifier',
36
+ name: 'name1',
37
+ triggerKey: 'foo|bar',
38
+ workflowDefinition: {
39
+ paths: [
40
+ {
41
+ conditional: 'true',
42
+ actionDefinitionConfigs: [
43
+ {
44
+ action: 'logItem',
45
+ name: 'Log Item'
46
+ }
47
+ ]
48
+ }
49
+ ]
50
+ },
51
+ ownerKey: 'app:123',
52
+ isActive: true,
53
+ public: true,
54
+ parallelism: 3,
55
+ };
56
+ (0, interface_converters_1.convertWorkflow)(workflow);
57
+ expect(workflow.parallelWorkerCount).toEqual(3);
58
+ expect(workflow['concurrency']).toBeUndefined();
59
+ expect(workflow.messageGroupId).toBeUndefined();
60
+ });
61
+ it('should ignore concurrency if we pass group ID or ', () => {
62
+ const workflow = {
63
+ identifier: 'identifier',
64
+ name: 'name1',
65
+ triggerKey: 'foo|bar',
66
+ workflowDefinition: {
67
+ paths: [
68
+ {
69
+ conditional: 'true',
70
+ actionDefinitionConfigs: [
71
+ {
72
+ action: 'logItem',
73
+ name: 'Log Item'
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ },
79
+ ownerKey: 'app:123',
80
+ isActive: true,
81
+ public: true,
82
+ parallelism: 3,
83
+ };
84
+ (0, interface_converters_1.convertWorkflow)(workflow);
85
+ expect(workflow.parallelWorkerCount).toEqual(3);
86
+ expect(workflow['concurrency']).toBeUndefined();
87
+ expect(workflow.messageGroupId).toBeUndefined();
88
+ });
89
+ });
@@ -57,6 +57,8 @@ export interface WorkflowDefinition {
57
57
  isActive: boolean;
58
58
  public: boolean;
59
59
  supportingAppId?: string;
60
+ parallelism?: number;
61
+ groupId?: string;
60
62
  messageGroupId?: string;
61
63
  parallelWorkerCount?: number;
62
64
  }
@@ -74,6 +76,8 @@ export interface WorkflowTemplate {
74
76
  isActive: boolean;
75
77
  ownerKey?: string;
76
78
  supportingAppId?: string;
79
+ parallelism?: number;
80
+ groupId?: string;
77
81
  messageGroupId?: string;
78
82
  parallelWorkerCount?: number;
79
83
  }
@@ -34,6 +34,7 @@ workflows:
34
34
  triggerKey: item|update
35
35
  isActive: true
36
36
  public: true
37
+ concurrency: 3 # The maximum number of concurrent jobs to run for this workflow. Defaults to 5
37
38
  workflowDefinition:
38
39
  paths:
39
40
  - conditional: "true"
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.0.6",
2
+ "version": "2.0.8",
3
3
  "commands": {
4
4
  "app:create": {
5
5
  "id": "app:create",
@@ -307,10 +307,10 @@
307
307
  "$ contrail groups export\n"
308
308
  ],
309
309
  "flags": {
310
- "outDir": {
311
- "name": "outDir",
310
+ "outFile": {
311
+ "name": "outFile",
312
312
  "type": "option",
313
- "char": "d",
313
+ "char": "f",
314
314
  "description": "The location for the file to be saved",
315
315
  "required": false,
316
316
  "multiple": false,
@@ -361,6 +361,7 @@
361
361
  "outDir": {
362
362
  "name": "outDir",
363
363
  "type": "option",
364
+ "char": "d",
364
365
  "description": "The directory to which to export the instance data. Defaults to the current directory",
365
366
  "required": false,
366
367
  "multiple": false,
@@ -369,6 +370,7 @@
369
370
  "overwrite": {
370
371
  "name": "overwrite",
371
372
  "type": "boolean",
373
+ "char": "f",
372
374
  "description": "Replaces any existing export files in outDir",
373
375
  "required": false,
374
376
  "allowNo": false
@@ -391,6 +393,7 @@
391
393
  "dir": {
392
394
  "name": "dir",
393
395
  "type": "option",
396
+ "char": "d",
394
397
  "description": "The directory from which to export the instance data. Defaults to the current directory",
395
398
  "required": false,
396
399
  "multiple": false,
@@ -930,10 +933,10 @@
930
933
  "$ contrail users export\n"
931
934
  ],
932
935
  "flags": {
933
- "outDir": {
934
- "name": "outDir",
936
+ "outFile": {
937
+ "name": "outFile",
935
938
  "type": "option",
936
- "char": "d",
939
+ "char": "f",
937
940
  "description": "The location for the file to be saved",
938
941
  "required": false,
939
942
  "multiple": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/cli",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "description": "VibeIQ's Contrail Platform CLI.",
5
5
  "bin": {
6
6
  "contrail-cli": "./bin/run",
@@ -50,16 +50,17 @@
50
50
  "devDependencies": {
51
51
  "@oclif/test": "^2.3.8",
52
52
  "@types/chai": "^4",
53
- "@types/mocha": "^9.0.0",
53
+ "@types/jest": "^29.5.0",
54
54
  "@types/node": "^16.18.14",
55
55
  "chai": "^4",
56
56
  "eslint": "^8.35.0",
57
57
  "eslint-config-oclif": "^4",
58
58
  "eslint-config-oclif-typescript": "^1.0.3",
59
59
  "eslint-plugin-unused-imports": "^2.0.0",
60
- "mocha": "^9",
60
+ "jest": "^29.5.0",
61
61
  "oclif": "^3",
62
62
  "shx": "^0.3.3",
63
+ "ts-jest": "^29.0.5",
63
64
  "ts-node": "^10.9.1",
64
65
  "tslib": "^2.5.0",
65
66
  "typescript": "^4.9.5"
@@ -106,16 +107,29 @@
106
107
  },
107
108
  "scripts": {
108
109
  "build": "shx rm -rf dist && tsc -b && npm run copy-templates",
109
- "lint": "eslint . --ext .ts --config .eslintrc",
110
+ "lint": "eslint . --ext .ts --config .eslintrc.js",
110
111
  "copy-templates": "cp -r templates/ dist/templates",
111
112
  "postpack": "shx rm -f oclif.manifest.json",
112
- "posttest": "yarn lint",
113
113
  "prepack": "yarn build && oclif manifest && oclif readme",
114
- "test": "mocha --forbid-only \"test/**/*.test.ts\"",
114
+ "test": "jest",
115
115
  "version": "oclif readme && git add README.md"
116
116
  },
117
117
  "engines": {
118
118
  "node": ">=12.0.0"
119
119
  },
120
- "types": "dist/index.d.ts"
120
+ "types": "dist/index.d.ts",
121
+ "jest": {
122
+ "moduleFileExtensions": [
123
+ "js",
124
+ "json",
125
+ "ts"
126
+ ],
127
+ "rootDir": "src",
128
+ "testRegex": ".spec.ts$",
129
+ "transform": {
130
+ "^.+\\.(t|j)s$": "ts-jest"
131
+ },
132
+ "coverageDirectory": "../coverage",
133
+ "testEnvironment": "node"
134
+ }
121
135
  }