@contrail/cli 3.5.4-alpha.103cd15 → 3.5.4-alpha.3c3e1b7
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 +57 -12
- package/dist/api/org-config.d.ts +3 -0
- package/dist/api/org-config.js +28 -0
- package/dist/auth/login.js +28 -18
- package/dist/auth/login.spec.js +77 -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 -117
- 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 +0 -1
- package/dist/helpers/workflow-templates/helper.js +3 -28
- package/oclif.manifest.json +122 -15
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ $ npm install -g @contrail/cli
|
|
|
17
17
|
$ contrail COMMAND
|
|
18
18
|
running command...
|
|
19
19
|
$ contrail (--version)
|
|
20
|
-
@contrail/cli/3.5.4-alpha.
|
|
20
|
+
@contrail/cli/3.5.4-alpha.3c3e1b7 linux-x64 node-v24.20.0
|
|
21
21
|
$ contrail --help [COMMAND]
|
|
22
22
|
USAGE
|
|
23
23
|
$ contrail COMMAND
|
|
@@ -61,6 +61,8 @@ To get instructions on how to set up autocomplete for your shell, run `contrail
|
|
|
61
61
|
* [`contrail load custom-entity DATAFILE CONFIGFILE`](#contrail-load-custom-entity-datafile-configfile)
|
|
62
62
|
* [`contrail load line-sheet DATAFILE CONFIGFILE`](#contrail-load-line-sheet-datafile-configfile)
|
|
63
63
|
* [`contrail load upload-and-load INCOMINGFILE CONFIGFILENAME`](#contrail-load-upload-and-load-incomingfile-configfilename)
|
|
64
|
+
* [`contrail org-config export`](#contrail-org-config-export)
|
|
65
|
+
* [`contrail org-config import`](#contrail-org-config-import)
|
|
64
66
|
* [`contrail projects export PROJECTNAME`](#contrail-projects-export-projectname)
|
|
65
67
|
* [`contrail projects list`](#contrail-projects-list)
|
|
66
68
|
* [`contrail projects load TEMPLATEFILELOCATION`](#contrail-projects-load-templatefilelocation)
|
|
@@ -777,6 +779,53 @@ EXAMPLES
|
|
|
777
779
|
$ contrail load-processor upload-and-load <File Path> <Config Path>
|
|
778
780
|
```
|
|
779
781
|
|
|
782
|
+
## `contrail org-config export`
|
|
783
|
+
|
|
784
|
+
Export the org config for your Org
|
|
785
|
+
|
|
786
|
+
```
|
|
787
|
+
USAGE
|
|
788
|
+
$ contrail org-config export [-l error|warn|info|debug] [-e true|false] [-d <value>] [-f]
|
|
789
|
+
|
|
790
|
+
FLAGS
|
|
791
|
+
-d, --outDir=<value> [default: .] The directory to which to export the org config data. Defaults to the
|
|
792
|
+
current directory
|
|
793
|
+
-e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
|
|
794
|
+
<options: true|false>
|
|
795
|
+
-f, --overwrite Replaces any existing export files in outDir
|
|
796
|
+
-l, --logLevel=<option> The Log Level
|
|
797
|
+
<options: error|warn|info|debug>
|
|
798
|
+
|
|
799
|
+
DESCRIPTION
|
|
800
|
+
Export the org config for your Org
|
|
801
|
+
|
|
802
|
+
EXAMPLES
|
|
803
|
+
$ contrail org-config export
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
## `contrail org-config import`
|
|
807
|
+
|
|
808
|
+
Upload an extracted org config
|
|
809
|
+
|
|
810
|
+
```
|
|
811
|
+
USAGE
|
|
812
|
+
$ contrail org-config import [-l error|warn|info|debug] [-e true|false] [-d <value>]
|
|
813
|
+
|
|
814
|
+
FLAGS
|
|
815
|
+
-d, --dir=<value> [default: .] The directory from which to import the org config data. Defaults to the
|
|
816
|
+
current directory. The org config data will be loaded from $dir/org-config.yml
|
|
817
|
+
-e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
|
|
818
|
+
<options: true|false>
|
|
819
|
+
-l, --logLevel=<option> The Log Level
|
|
820
|
+
<options: error|warn|info|debug>
|
|
821
|
+
|
|
822
|
+
DESCRIPTION
|
|
823
|
+
Upload an extracted org config
|
|
824
|
+
|
|
825
|
+
EXAMPLES
|
|
826
|
+
$ contrail org-config import
|
|
827
|
+
```
|
|
828
|
+
|
|
780
829
|
## `contrail projects export PROJECTNAME`
|
|
781
830
|
|
|
782
831
|
Export a project.
|
|
@@ -919,7 +968,7 @@ loads all the type data that has been exported by getAll
|
|
|
919
968
|
|
|
920
969
|
```
|
|
921
970
|
USAGE
|
|
922
|
-
$ contrail types loadAll [-l error|warn|info|debug] [-e true|false] [-d <value>]
|
|
971
|
+
$ contrail types loadAll [-l error|warn|info|debug] [-e true|false] [-d <value>] [--orgConfig]
|
|
923
972
|
|
|
924
973
|
FLAGS
|
|
925
974
|
-d, --dir=<value> [default: .] The directory from which to load the types data. Defaults to the current
|
|
@@ -928,6 +977,8 @@ FLAGS
|
|
|
928
977
|
<options: true|false>
|
|
929
978
|
-l, --logLevel=<option> The Log Level
|
|
930
979
|
<options: error|warn|info|debug>
|
|
980
|
+
--[no-]orgConfig Also load org config data from this directory. If not specified, you will be
|
|
981
|
+
prompted.
|
|
931
982
|
|
|
932
983
|
DESCRIPTION
|
|
933
984
|
loads all the type data that has been exported by getAll
|
|
@@ -1611,31 +1662,25 @@ EXAMPLES
|
|
|
1611
1662
|
|
|
1612
1663
|
## `contrail workflow-templates export`
|
|
1613
1664
|
|
|
1614
|
-
Export all workflows in the org.
|
|
1665
|
+
Export all workflows in the org.
|
|
1615
1666
|
|
|
1616
1667
|
```
|
|
1617
1668
|
USAGE
|
|
1618
|
-
$ contrail workflow-templates export [-l error|warn|info|debug] [-e true|false] [-f <value>] [
|
|
1619
|
-
[--overwrite]
|
|
1669
|
+
$ contrail workflow-templates export [-l error|warn|info|debug] [-e true|false] [-f <value>] [--overwrite]
|
|
1620
1670
|
|
|
1621
1671
|
FLAGS
|
|
1622
|
-
-d, --dir=<value> [default: .] Directory to write per-app workflow files
|
|
1623
1672
|
-e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
|
|
1624
1673
|
<options: true|false>
|
|
1625
|
-
-f, --outFile=<value>
|
|
1674
|
+
-f, --outFile=<value> [default: workflows.yml] The location for the file to be saved
|
|
1626
1675
|
-l, --logLevel=<option> The Log Level
|
|
1627
1676
|
<options: error|warn|info|debug>
|
|
1628
1677
|
--overwrite Replaces existing files if they exist
|
|
1629
1678
|
|
|
1630
1679
|
DESCRIPTION
|
|
1631
|
-
Export all workflows in the org.
|
|
1680
|
+
Export all workflows in the org.
|
|
1632
1681
|
|
|
1633
1682
|
EXAMPLES
|
|
1634
1683
|
$ contrail workflow-templates export
|
|
1635
|
-
|
|
1636
|
-
$ contrail workflow-templates export --dir ./workflows
|
|
1637
|
-
|
|
1638
|
-
$ contrail workflow-templates export --outFile workflows.yml
|
|
1639
1684
|
```
|
|
1640
1685
|
|
|
1641
1686
|
## `contrail workflow-templates list`
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOrgConfig = exports.updateOrgConfig = exports.getOrgConfigs = void 0;
|
|
4
|
+
const sdk_1 = require("@contrail/sdk");
|
|
5
|
+
// every org has exactly one org-config record (created at provisioning),
|
|
6
|
+
// so callers can safely use the first element of the returned list
|
|
7
|
+
async function getOrgConfigs(options) {
|
|
8
|
+
return new sdk_1.Entities().get({
|
|
9
|
+
entityName: 'org-config',
|
|
10
|
+
...options,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
exports.getOrgConfigs = getOrgConfigs;
|
|
14
|
+
async function updateOrgConfig(id, changes) {
|
|
15
|
+
return new sdk_1.Entities().update({
|
|
16
|
+
entityName: 'org-config',
|
|
17
|
+
id,
|
|
18
|
+
object: changes,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
exports.updateOrgConfig = updateOrgConfig;
|
|
22
|
+
async function createOrgConfig(orgConfig) {
|
|
23
|
+
return new sdk_1.Entities().create({
|
|
24
|
+
entityName: 'org-config',
|
|
25
|
+
object: orgConfig,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
exports.createOrgConfig = createOrgConfig;
|
package/dist/auth/login.js
CHANGED
|
@@ -149,28 +149,33 @@ async function setAuthContext(user, params) {
|
|
|
149
149
|
}
|
|
150
150
|
async function refreshCurrentUserOrg() {
|
|
151
151
|
logger_1.default.debug('refreshing current user org...');
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
apiUserToken: newVibeAccessToken,
|
|
159
|
-
apiRefreshToken: config.apiRefreshToken,
|
|
160
|
-
apiGateway: config.apiGateway,
|
|
161
|
-
orgSlug: config.orgSlug,
|
|
162
|
-
};
|
|
163
|
-
console.log('fetched new access token from refresh token', newVibeAccessToken);
|
|
164
|
-
}
|
|
165
|
-
catch (e) {
|
|
166
|
-
throw Error('Could not get refresh token');
|
|
152
|
+
try {
|
|
153
|
+
await (0, sdk_1.getCurrentUser)();
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
if (isNetworkConnectionError(e)) {
|
|
157
|
+
throw e;
|
|
167
158
|
}
|
|
159
|
+
logger_1.default.debug('access token rejected, refreshing it...');
|
|
160
|
+
await refreshAccessToken();
|
|
161
|
+
await (0, sdk_1.getCurrentUser)();
|
|
168
162
|
}
|
|
169
163
|
// TODO: To uncomment when telemetry backend is developed.
|
|
170
164
|
// const current_Version = getVersionFromPackageJson();
|
|
171
165
|
// await checkTelemetry(current_Version);
|
|
172
166
|
return returnAndStoreConfig(sdk_1.Auth.config);
|
|
173
167
|
}
|
|
168
|
+
async function refreshAccessToken() {
|
|
169
|
+
const config = await (0, config_1.getConfig)();
|
|
170
|
+
const newVibeAccessToken = await requestRefreshedAccessToken(config.apiRefreshToken, config.apiGateway);
|
|
171
|
+
sdk_1.Auth.config = {
|
|
172
|
+
apiUserToken: newVibeAccessToken,
|
|
173
|
+
apiRefreshToken: config.apiRefreshToken,
|
|
174
|
+
apiGateway: config.apiGateway,
|
|
175
|
+
orgSlug: config.orgSlug,
|
|
176
|
+
};
|
|
177
|
+
(0, sdk_1.setConfig)(sdk_1.Auth.config);
|
|
178
|
+
}
|
|
174
179
|
async function getVibeAccessTokenFromCognitoAccessToken(cognitoAccessToken, apiGateway) {
|
|
175
180
|
const url = `${apiGateway}/auth/cognito/`;
|
|
176
181
|
const response = await fetch(url, {
|
|
@@ -195,9 +200,14 @@ async function requestRefreshedAccessToken(vibeRefreshToken, apiGateway) {
|
|
|
195
200
|
},
|
|
196
201
|
body: JSON.stringify({ refreshToken: vibeRefreshToken }),
|
|
197
202
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
203
|
+
if (!response.ok) {
|
|
204
|
+
throw new Error(`Token refresh failed with status ${response.status}`);
|
|
205
|
+
}
|
|
206
|
+
const data = await response.json();
|
|
207
|
+
if (!data.accessToken) {
|
|
208
|
+
throw new Error('Token refresh response did not contain an access token');
|
|
209
|
+
}
|
|
210
|
+
return data.accessToken;
|
|
201
211
|
}
|
|
202
212
|
function getVersionFromPackageJson() {
|
|
203
213
|
// Get the path to the package.json file
|
package/dist/auth/login.spec.js
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
3
4
|
const login_1 = require("./login");
|
|
5
|
+
const sdk_1 = require("@contrail/sdk");
|
|
6
|
+
const config_1 = require("../helpers/config/config");
|
|
7
|
+
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
8
|
+
jest.mock('@contrail/sdk', () => ({
|
|
9
|
+
Auth: { config: undefined },
|
|
10
|
+
getCurrentUser: jest.fn(),
|
|
11
|
+
setConfig: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
jest.mock('../helpers/config/config', () => ({
|
|
14
|
+
getConfig: jest.fn(),
|
|
15
|
+
storeConfig: jest.fn(),
|
|
16
|
+
checkTelemetry: jest.fn(),
|
|
17
|
+
}));
|
|
18
|
+
jest.mock('inquirer', () => ({ prompt: jest.fn() }));
|
|
19
|
+
jest.mock('amazon-cognito-identity-js', () => ({
|
|
20
|
+
CognitoUserPool: jest.fn(),
|
|
21
|
+
AuthenticationDetails: jest.fn(),
|
|
22
|
+
CognitoUser: jest.fn().mockImplementation(() => ({
|
|
23
|
+
authenticateUser: (_details, callbacks) => callbacks.onFailure(new Error('Incorrect password')),
|
|
24
|
+
})),
|
|
25
|
+
}));
|
|
26
|
+
jest.mock('../logger', () => ({
|
|
27
|
+
__esModule: true,
|
|
28
|
+
default: { debug: jest.fn(), info: jest.fn(), error: jest.fn() },
|
|
29
|
+
}));
|
|
30
|
+
const STORED_CONFIG = {
|
|
31
|
+
apiUserToken: 'expired-access-token',
|
|
32
|
+
apiRefreshToken: 'valid-refresh-token',
|
|
33
|
+
apiGateway: 'https://api.vibeiq.com/prod/api',
|
|
34
|
+
orgSlug: 'test-zone-1',
|
|
35
|
+
};
|
|
4
36
|
describe('getVersionFromPackageJson', () => {
|
|
5
37
|
it('should return the version value from package.json', () => {
|
|
6
38
|
const version = (0, login_1.getVersionFromPackageJson)();
|
|
@@ -8,3 +40,48 @@ describe('getVersionFromPackageJson', () => {
|
|
|
8
40
|
expect(typeof version).toBe('string');
|
|
9
41
|
});
|
|
10
42
|
});
|
|
43
|
+
describe('authUser', () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
jest.clearAllMocks();
|
|
46
|
+
delete process.env.CONTRAIL_CLI_API_KEY;
|
|
47
|
+
config_1.getConfig.mockResolvedValue({ ...STORED_CONFIG });
|
|
48
|
+
config_1.storeConfig.mockResolvedValue(undefined);
|
|
49
|
+
sdk_1.Auth.config = undefined;
|
|
50
|
+
});
|
|
51
|
+
it('should refresh an expired access token without asking for credentials', async () => {
|
|
52
|
+
sdk_1.getCurrentUser
|
|
53
|
+
.mockRejectedValueOnce(new Error('Failed to fetch current user.'))
|
|
54
|
+
.mockResolvedValueOnce({ email: 'adam@vibeiq.com' });
|
|
55
|
+
global.fetch = jest.fn().mockResolvedValue({
|
|
56
|
+
ok: true,
|
|
57
|
+
json: async () => ({ accessToken: 'refreshed-access-token' }),
|
|
58
|
+
});
|
|
59
|
+
const config = await (0, login_1.authUser)();
|
|
60
|
+
expect(global.fetch).toHaveBeenCalledWith(`${STORED_CONFIG.apiGateway}/auth/jwt_refresh/`, expect.objectContaining({ method: 'POST' }));
|
|
61
|
+
expect(config.apiUserToken).toBe('refreshed-access-token');
|
|
62
|
+
expect(config.apiRefreshToken).toBe(STORED_CONFIG.apiRefreshToken);
|
|
63
|
+
expect(inquirer_1.default.prompt).not.toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
it('should ask for credentials when the refresh token is rejected', async () => {
|
|
66
|
+
sdk_1.getCurrentUser.mockRejectedValue(new Error('Failed to fetch current user.'));
|
|
67
|
+
global.fetch = jest.fn().mockResolvedValue({ ok: false, status: 401 });
|
|
68
|
+
inquirer_1.default.prompt.mockResolvedValue({
|
|
69
|
+
email: 'adam@vibeiq.com',
|
|
70
|
+
password: 'hunter2',
|
|
71
|
+
});
|
|
72
|
+
await expect((0, login_1.authUser)()).rejects.toThrow('Incorrect password');
|
|
73
|
+
expect(inquirer_1.default.prompt).toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
it('should exit on a network failure rather than asking for credentials', async () => {
|
|
76
|
+
const networkError = new Error('fetch failed');
|
|
77
|
+
networkError.cause = { code: 'ENOTFOUND' };
|
|
78
|
+
sdk_1.getCurrentUser.mockRejectedValue(networkError);
|
|
79
|
+
const exit = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
80
|
+
throw new Error('process.exit called');
|
|
81
|
+
});
|
|
82
|
+
await expect((0, login_1.authUser)()).rejects.toThrow('process.exit called');
|
|
83
|
+
expect(exit).toHaveBeenCalledWith(1);
|
|
84
|
+
expect(inquirer_1.default.prompt).not.toHaveBeenCalled();
|
|
85
|
+
exit.mockRestore();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.exportGroups = 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");
|
|
@@ -61,18 +58,9 @@ async function exportGroups(fileLocation, overwrite) {
|
|
|
61
58
|
});
|
|
62
59
|
}
|
|
63
60
|
groups = groups.sort((one, two) => (one.name < two.name ? -1 : 1));
|
|
64
|
-
await saveData(groups, fileLocation, overwrite);
|
|
61
|
+
await (0, file_helpers_1.saveData)(groups, fileLocation, overwrite);
|
|
65
62
|
}
|
|
66
63
|
exports.exportGroups = exportGroups;
|
|
67
|
-
async function saveData(data, fileLocation, overwrite) {
|
|
68
|
-
const yamlStr = js_yaml_1.default.dump(data);
|
|
69
|
-
const CURRENT_DIR = process.cwd();
|
|
70
|
-
const pathToWrite = path.join(CURRENT_DIR, fileLocation);
|
|
71
|
-
if (fs_1.default.existsSync(pathToWrite)) {
|
|
72
|
-
await (0, file_helpers_1.safeFileOverwrite)(pathToWrite, overwrite);
|
|
73
|
-
}
|
|
74
|
-
fs_1.default.writeFileSync(pathToWrite, yamlStr, 'utf8');
|
|
75
|
-
}
|
|
76
64
|
function getGroupPrincipals(group, allGroups, orgUsers) {
|
|
77
65
|
if (!group?.groupPrincipals) {
|
|
78
66
|
return [];
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.uploadGroups = 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
|
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
14
12
|
class ImportGroups extends basecommand_1.BaseCommand {
|
|
@@ -39,23 +37,12 @@ class ImportGroups extends basecommand_1.BaseCommand {
|
|
|
39
37
|
}
|
|
40
38
|
}
|
|
41
39
|
exports.default = ImportGroups;
|
|
42
|
-
function getGroupsFile(fileLocation) {
|
|
43
|
-
const CURRENT_DIR = process.cwd();
|
|
44
|
-
try {
|
|
45
|
-
const fullPath = path.join(CURRENT_DIR, fileLocation);
|
|
46
|
-
const doc = js_yaml_1.default.load(fs_1.default.readFileSync(fullPath, 'utf8'));
|
|
47
|
-
return doc;
|
|
48
|
-
}
|
|
49
|
-
catch (e) {
|
|
50
|
-
throw new Error(`ERROR PARSING YAML: ${e}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
40
|
async function uploadGroups(fileLocation) {
|
|
54
41
|
const existingGroups = await new sdk_1.Entities().get({
|
|
55
42
|
entityName: 'group',
|
|
56
43
|
relations: ['groupPrincipals'],
|
|
57
44
|
});
|
|
58
|
-
const groupsToImport =
|
|
45
|
+
const groupsToImport = (0, file_helpers_1.loadData)(fileLocation);
|
|
59
46
|
const orgUsers = await new sdk_1.Entities().get({
|
|
60
47
|
entityName: 'user-org',
|
|
61
48
|
});
|
|
@@ -9,3 +9,17 @@ export default class ImportInstance extends BaseCommand {
|
|
|
9
9
|
};
|
|
10
10
|
run(): Promise<void>;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Imports an instance export in dependency order:
|
|
14
|
+
* 1. option sets, types, option set hierarchies, rule sets
|
|
15
|
+
* 2. users and groups (policy principals)
|
|
16
|
+
* 3. type property policies and type policies
|
|
17
|
+
* 4. type overrides — after policies, because they reference policies, rule sets and option
|
|
18
|
+
* set hierarchies by slug and the API rejects the whole upload if any of them is missing
|
|
19
|
+
* 5. org config
|
|
20
|
+
*
|
|
21
|
+
* Fails (rejects) if the type overrides could not be applied, so the import does not
|
|
22
|
+
* report success while the org is missing customizations carried by overrides
|
|
23
|
+
* (labels/formulas/editability of core properties, subtype-level property changes).
|
|
24
|
+
*/
|
|
25
|
+
export declare function importInstance(dir: string): Promise<void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.importInstance = void 0;
|
|
3
4
|
const tslib_1 = require("tslib");
|
|
4
5
|
const path = tslib_1.__importStar(require("path"));
|
|
5
6
|
const core_1 = require("@oclif/core");
|
|
@@ -8,6 +9,8 @@ const import_1 = require("../users/import");
|
|
|
8
9
|
const import_2 = require("../groups/import");
|
|
9
10
|
const uploadTypePropertyPolicies_1 = require("../types/uploadTypePropertyPolicies");
|
|
10
11
|
const uploadTypePolicies_1 = require("../types/uploadTypePolicies");
|
|
12
|
+
const uploadTypeOverrides_1 = require("../types/uploadTypeOverrides");
|
|
13
|
+
const import_3 = require("../org-config/import");
|
|
11
14
|
const basecommand_1 = require("../../basecommand");
|
|
12
15
|
class ImportInstance extends basecommand_1.BaseCommand {
|
|
13
16
|
static description = 'Import the data to an instance, including types and users';
|
|
@@ -25,11 +28,30 @@ class ImportInstance extends basecommand_1.BaseCommand {
|
|
|
25
28
|
}),
|
|
26
29
|
};
|
|
27
30
|
async run() {
|
|
28
|
-
await (
|
|
29
|
-
await (0, import_1.uploadUsers)(path.join(this.flags.dir, 'users.yml'));
|
|
30
|
-
await (0, import_2.uploadGroups)(path.join(this.flags.dir, 'groups.yml'));
|
|
31
|
-
await (0, uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)(this.flags.dir);
|
|
32
|
-
await (0, uploadTypePolicies_1.uploadTypePolicies)(this.flags.dir);
|
|
31
|
+
await importInstance(this.flags.dir);
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
34
|
exports.default = ImportInstance;
|
|
35
|
+
/**
|
|
36
|
+
* Imports an instance export in dependency order:
|
|
37
|
+
* 1. option sets, types, option set hierarchies, rule sets
|
|
38
|
+
* 2. users and groups (policy principals)
|
|
39
|
+
* 3. type property policies and type policies
|
|
40
|
+
* 4. type overrides — after policies, because they reference policies, rule sets and option
|
|
41
|
+
* set hierarchies by slug and the API rejects the whole upload if any of them is missing
|
|
42
|
+
* 5. org config
|
|
43
|
+
*
|
|
44
|
+
* Fails (rejects) if the type overrides could not be applied, so the import does not
|
|
45
|
+
* report success while the org is missing customizations carried by overrides
|
|
46
|
+
* (labels/formulas/editability of core properties, subtype-level property changes).
|
|
47
|
+
*/
|
|
48
|
+
async function importInstance(dir) {
|
|
49
|
+
await (0, loadAll_1.loadAll)(dir, true, false);
|
|
50
|
+
await (0, import_1.uploadUsers)(path.join(dir, 'users.yml'));
|
|
51
|
+
await (0, import_2.uploadGroups)(path.join(dir, 'groups.yml'));
|
|
52
|
+
await (0, uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)(dir);
|
|
53
|
+
await (0, uploadTypePolicies_1.uploadTypePolicies)(dir);
|
|
54
|
+
await (0, uploadTypeOverrides_1.uploadTypeOverrides)(dir);
|
|
55
|
+
await (0, import_3.uploadOrgConfig)(dir);
|
|
56
|
+
}
|
|
57
|
+
exports.importInstance = importInstance;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const import_1 = require("./import");
|
|
4
|
+
const loadAll_1 = require("../types/loadAll");
|
|
5
|
+
const import_2 = require("../users/import");
|
|
6
|
+
const import_3 = require("../groups/import");
|
|
7
|
+
const uploadTypePropertyPolicies_1 = require("../types/uploadTypePropertyPolicies");
|
|
8
|
+
const uploadTypePolicies_1 = require("../types/uploadTypePolicies");
|
|
9
|
+
const uploadTypeOverrides_1 = require("../types/uploadTypeOverrides");
|
|
10
|
+
const import_4 = require("../org-config/import");
|
|
11
|
+
jest.mock('../types/loadAll');
|
|
12
|
+
jest.mock('../users/import');
|
|
13
|
+
jest.mock('../groups/import');
|
|
14
|
+
jest.mock('../types/uploadTypePropertyPolicies');
|
|
15
|
+
jest.mock('../types/uploadTypePolicies');
|
|
16
|
+
jest.mock('../types/uploadTypeOverrides');
|
|
17
|
+
jest.mock('../org-config/import');
|
|
18
|
+
const mocked = (fn) => fn;
|
|
19
|
+
const STEPS = [
|
|
20
|
+
loadAll_1.loadAll,
|
|
21
|
+
import_2.uploadUsers,
|
|
22
|
+
import_3.uploadGroups,
|
|
23
|
+
uploadTypePropertyPolicies_1.uploadTypePropertyPolicies,
|
|
24
|
+
uploadTypePolicies_1.uploadTypePolicies,
|
|
25
|
+
uploadTypeOverrides_1.uploadTypeOverrides,
|
|
26
|
+
import_4.uploadOrgConfig,
|
|
27
|
+
];
|
|
28
|
+
describe('importInstance', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
for (const fn of STEPS) {
|
|
32
|
+
mocked(fn).mockResolvedValue(undefined);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
it('skips policies/overrides/org-config in loadAll and uploads them after users and groups', async () => {
|
|
36
|
+
await (0, import_1.importInstance)('export-dir');
|
|
37
|
+
expect(mocked(loadAll_1.loadAll)).toHaveBeenCalledWith('export-dir', true, false);
|
|
38
|
+
expect(mocked(uploadTypeOverrides_1.uploadTypeOverrides)).toHaveBeenCalledWith('export-dir');
|
|
39
|
+
expect(mocked(import_4.uploadOrgConfig)).toHaveBeenCalledWith('export-dir');
|
|
40
|
+
const order = STEPS.map((fn) => mocked(fn).mock.invocationCallOrder[0]);
|
|
41
|
+
// strictly increasing => called in exactly this order
|
|
42
|
+
for (let i = 1; i < order.length; i++) {
|
|
43
|
+
expect(order[i]).toBeGreaterThan(order[i - 1]);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
it('uploads type overrides after type policies (CR-2095)', async () => {
|
|
47
|
+
await (0, import_1.importInstance)('export-dir');
|
|
48
|
+
const policiesOrder = mocked(uploadTypePolicies_1.uploadTypePolicies).mock.invocationCallOrder[0];
|
|
49
|
+
const overridesOrder = mocked(uploadTypeOverrides_1.uploadTypeOverrides).mock.invocationCallOrder[0];
|
|
50
|
+
expect(overridesOrder).toBeGreaterThan(policiesOrder);
|
|
51
|
+
});
|
|
52
|
+
it('rejects when the type override upload fails instead of reporting success', async () => {
|
|
53
|
+
mocked(uploadTypeOverrides_1.uploadTypeOverrides).mockRejectedValue(new Error('policy with identifier X not found'));
|
|
54
|
+
await expect((0, import_1.importInstance)('export-dir')).rejects.toThrow('policy with identifier X not found');
|
|
55
|
+
expect(mocked(import_4.uploadOrgConfig)).not.toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from '../../basecommand';
|
|
2
|
+
export default class ExportOrgConfig extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
outDir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
7
|
+
overwrite: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
9
|
+
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function exportOrgConfig(outDir: string, overwrite: boolean): Promise<void>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exportOrgConfig = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const path = tslib_1.__importStar(require("path"));
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
8
|
+
const login_1 = require("../../auth/login");
|
|
9
|
+
const core_1 = require("@oclif/core");
|
|
10
|
+
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
11
|
+
const org_config_1 = require("../../api/org-config");
|
|
12
|
+
const basecommand_1 = require("../../basecommand");
|
|
13
|
+
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
14
|
+
const ORG_SPECIFIC_FIELDS = [
|
|
15
|
+
'id',
|
|
16
|
+
'orgId',
|
|
17
|
+
'createdOn',
|
|
18
|
+
'updatedOn',
|
|
19
|
+
'createdById',
|
|
20
|
+
'updatedById',
|
|
21
|
+
'createdBy',
|
|
22
|
+
'updatedBy',
|
|
23
|
+
];
|
|
24
|
+
class ExportOrgConfig extends basecommand_1.BaseCommand {
|
|
25
|
+
static description = 'Export the org config for your Org';
|
|
26
|
+
static examples = [
|
|
27
|
+
`$ contrail org-config export
|
|
28
|
+
`,
|
|
29
|
+
];
|
|
30
|
+
static flags = {
|
|
31
|
+
...basecommand_1.BaseCommand.baseFlags,
|
|
32
|
+
outDir: core_1.Flags.string({
|
|
33
|
+
char: 'd',
|
|
34
|
+
description: 'The directory to which to export the org config data. Defaults to the current directory',
|
|
35
|
+
required: false,
|
|
36
|
+
default: '.',
|
|
37
|
+
}),
|
|
38
|
+
overwrite: core_1.Flags.boolean({
|
|
39
|
+
char: 'f',
|
|
40
|
+
description: 'Replaces any existing export files in outDir',
|
|
41
|
+
required: false,
|
|
42
|
+
default: false,
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
async run() {
|
|
46
|
+
try {
|
|
47
|
+
await (0, login_1.authUser)();
|
|
48
|
+
this.log(`${chalk_1.default.blue.bold('START')} Getting Org Config.`);
|
|
49
|
+
this.log(`${chalk_1.default.yellow.bold('ORG CONFIG')} Exporting`);
|
|
50
|
+
await exportOrgConfig(this.flags.outDir, this.flags.overwrite);
|
|
51
|
+
this.log(`${chalk_1.default.yellow.bold('ORG CONFIG')} Exported`);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
this.log(`${chalk_1.default.red.bold('FAIL')} Org Config Export Failed. ${e}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.log(`${chalk_1.default.green.bold('COMPLETE')} Org Config Export Complete.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.default = ExportOrgConfig;
|
|
61
|
+
async function exportOrgConfig(outDir, overwrite) {
|
|
62
|
+
const orgConfigs = await (0, org_config_1.getOrgConfigs)();
|
|
63
|
+
if (!orgConfigs?.length) {
|
|
64
|
+
logger_1.default.info(`${chalk_1.default.yellow.bold('ORG CONFIG')} No org config found. Skipping.`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const orgConfig = { ...orgConfigs[0] };
|
|
68
|
+
for (const field of ORG_SPECIFIC_FIELDS) {
|
|
69
|
+
delete orgConfig[field];
|
|
70
|
+
}
|
|
71
|
+
fs_1.default.mkdirSync(path.resolve(outDir), { recursive: true });
|
|
72
|
+
await (0, file_helpers_1.saveData)(orgConfig, path.join(outDir, 'org-config.yml'), overwrite);
|
|
73
|
+
}
|
|
74
|
+
exports.exportOrgConfig = exportOrgConfig;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../basecommand';
|
|
2
|
+
export default class ImportOrgConfig extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
dir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
7
|
+
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
8
|
+
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function uploadOrgConfig(dir: string): Promise<void>;
|