Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new flag to separate managed.idm.json into individual files #461

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/cli/config/config-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export default function setup() {
'Export sync.idm.json mappings separately in their own directory. Ignored with -a.'
)
)
.addOption(
new Option(
'-o, --separate-objects',
'Export managed.idm.json objects separately in their own directory. Ignored with -a.'
)
)
.addOption(
new Option(
'--include-active-values',
Expand Down Expand Up @@ -145,6 +151,7 @@ export default function setup() {
const outcome = await exportEverythingToFiles(
options.extract,
options.separateMappings,
options.separateObjects,
options.metadata,
{
useStringArrays: options.useStringArrays,
Expand Down
8 changes: 8 additions & 0 deletions src/cli/idm/idm-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ export default function setup() {
'Export sync.idm.json mappings separately in their own directory. Ignored with -a.'
)
)
.addOption(
new Option(
'-o, --separate-objects',
'Export managed.idm.json objects separately in their own directory. Ignored with -a.'
)
)
.addOption(
new Option(
'-N, --no-metadata',
Expand Down Expand Up @@ -95,6 +101,7 @@ export default function setup() {
options.file,
options.envFile,
options.separateMappings,
options.separateObjects,
options.metadata
);
if (!outcome) process.exitCode = 1;
Expand Down Expand Up @@ -136,6 +143,7 @@ export default function setup() {
options.entitiesFile,
options.envFile,
options.separateMappings,
options.separateObjects,
options.metadata
);
if (!outcome) process.exitCode = 1;
Expand Down
19 changes: 16 additions & 3 deletions src/ops/ConfigOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '../utils/Config';
import { cleanupProgressIndicators, printError } from '../utils/Console';
import { saveServersToFiles } from './classic/ServerOps';
import { ManagedSkeleton, writeManagedJsonToDirectory } from './IdmOps';
import { writeSyncJsonToDirectory } from './MappingOps';
import { extractScriptsToFiles } from './ScriptOps';

Expand Down Expand Up @@ -72,13 +73,15 @@ export async function exportEverythingToFile(
* Export everything to separate files
* @param {boolean} extract Extracts the scripts from the exports into separate files if true
* @param {boolean} separateMappings separate sync.idm.json mappings if true, otherwise keep them in a single file
* @param {boolean} separateObjects separate managed.idm.json objects if true, otherwise keep them in a single file
* @param {boolean} includeMeta true to include metadata, false otherwise. Default: true
* @param {FullExportOptions} options export options
* @return {Promise<boolean>} a promise that resolves to true if successful, false otherwise
*/
export async function exportEverythingToFiles(
extract: boolean = false,
separateMappings: boolean = false,
separateObjects: boolean = false,
includeMeta: boolean = true,
options: FullExportOptions = {
useStringArrays: true,
Expand Down Expand Up @@ -106,7 +109,8 @@ export async function exportEverythingToFiles(
`${baseDirectory}/global`,
includeMeta,
extract,
separateMappings
separateMappings,
separateObjects
)
);
Object.entries(exportData.realm).forEach(([realm, data]: [string, any]) =>
Expand All @@ -118,7 +122,8 @@ export async function exportEverythingToFiles(
`${baseDirectory}/realm/${realm}`,
includeMeta,
extract,
separateMappings
separateMappings,
separateObjects
)
)
);
Expand All @@ -141,6 +146,7 @@ export async function exportEverythingToFiles(
* @param {boolean} includeMeta true to include metadata, false otherwise. Default: true
* @param {boolean} extract Extracts the scripts from the exports into separate files if true
* @param {boolean} separateMappings separate sync.idm.json mappings if true, otherwise keep them in a single file
* @param {boolean} separateObjects separate managed.idm.json objects if true, otherwise keep them in a single file
*/
function exportItem(
exportData,
Expand All @@ -149,7 +155,8 @@ function exportItem(
baseDirectory,
includeMeta,
extract,
separateMappings = false
separateMappings = false,
separateObjects = false
) {
if (!obj || !Object.keys(obj).length) {
return;
Expand Down Expand Up @@ -253,6 +260,12 @@ function exportItem(
`${baseDirectory.substring(getWorkingDirectory(false).length + 1)}/${fileType}/sync`,
includeMeta
);
} else if (separateObjects && id === 'managed') {
writeManagedJsonToDirectory(
value as ManagedSkeleton,
`${baseDirectory.substring(getWorkingDirectory(false).length + 1)}/${fileType}/managed`,
includeMeta
);
} else {
const filename = `${id}.idm.json`;
if (filename.includes('/')) {
Expand Down
119 changes: 118 additions & 1 deletion src/ops/IdmOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import fs from 'fs';
import path from 'path';
import propertiesReader from 'properties-reader';

import { extractDataToFile, getExtractedJsonData } from '../utils/Config';
import {
createProgressIndicator,
printError,
Expand Down Expand Up @@ -77,12 +78,21 @@ export async function listAllConfigEntities(): Promise<boolean> {
return false;
}

type ObjectSkeleton = IdObjectSkeletonInterface & {
name: string;
};

export type ManagedSkeleton = IdObjectSkeletonInterface & {
objects: ObjectSkeleton[];
};

/**
* Export an IDM configuration object.
* @param {string} id the desired configuration object
* @param {string} file optional export file name (or directory name if exporting mappings separately)
* @param {string} envFile File that defines environment specific variables for replacement during configuration export/import
* @param {boolean} separateMappings separate sync.idm.json mappings if true (and id is "sync"), otherwise keep them in a single file
* @param {boolean} separateObjects separate managed.idm.json objects if true (and id is "managed"), otherwise keep them in a single file
* @param {boolean} includeMeta true to include metadata, false otherwise. Default: true
* @return {Promise<boolean>} a promise that resolves to true if successful, false otherwise
*/
Expand All @@ -91,6 +101,7 @@ export async function exportConfigEntityToFile(
file?: string,
envFile?: string,
separateMappings: boolean = false,
separateObjects: boolean = false,
includeMeta: boolean = true
): Promise<boolean> {
try {
Expand All @@ -107,6 +118,14 @@ export async function exportConfigEntityToFile(
);
return true;
}
if (separateObjects && id === 'managed') {
writeManagedJsonToDirectory(
exportData.idm[id] as ManagedSkeleton,
file,
includeMeta
);
return true;
}
let fileName = file;
if (!fileName) {
fileName = getTypedFilename(`${id}`, 'idm');
Expand Down Expand Up @@ -156,12 +175,14 @@ export async function exportAllConfigEntitiesToFile(
* @param {string} entitiesFile JSON file that specifies the config entities to export/import
* @param {string} envFile File that defines environment specific variables for replacement during configuration export/import
* @param {boolean} separateMappings separate sync.idm.json mappings if true, otherwise keep them in a single file
* @param {boolean} separateObjects separate managed.idm.json objects if true, otherwise keep them in a single file
* @return {Promise<boolean>} a promise that resolves to true if successful, false otherwise
*/
export async function exportAllConfigEntitiesToFiles(
entitiesFile?: string,
envFile?: string,
separateMappings: boolean = false,
separateObjects: boolean = false,
includeMeta: boolean = true
): Promise<boolean> {
const errors: Error[] = [];
Expand All @@ -177,6 +198,14 @@ export async function exportAllConfigEntitiesToFiles(
writeSyncJsonToDirectory(obj as SyncSkeleton, 'sync', includeMeta);
continue;
}
if (separateObjects && id === 'managed') {
writeManagedJsonToDirectory(
obj as ManagedSkeleton,
'managed',
includeMeta
);
continue;
}
saveToFile(
'idm',
obj,
Expand Down Expand Up @@ -232,6 +261,14 @@ export async function importConfigEntityByIdFromFile(
},
]);
importData = { idm: { sync: syncData } };
} else if (entityId === 'managed') {
const managedData = getManagedObjectsFromFiles([
{
content: fileData,
path: `${filePath.substring(0, filePath.lastIndexOf('/'))}/managed.idm.json`,
},
]);
importData = { idm: { managed: managedData } };
} else {
importData = JSON.parse(fileData);
}
Expand Down Expand Up @@ -292,6 +329,14 @@ export async function importFirstConfigEntityFromFile(
},
]);
}
if (entityId === 'managed') {
importData.idm.managed = getManagedObjectsFromFiles([
{
content: fileData,
path: `${filePath.substring(0, filePath.lastIndexOf('/'))}/managed.idm.json`,
},
]);
}

const options = getIdmImportExportOptions(undefined, envFile);

Expand Down Expand Up @@ -434,9 +479,13 @@ export async function getIdmImportDataFromIdmDirectory(
);
// Process sync mapping file(s)
importData.idm.sync = getLegacyMappingsFromFiles(idmConfigFiles);
importData.idm.managed = getManagedObjectsFromFiles(idmConfigFiles);
// Process other files
for (const f of idmConfigFiles.filter(
(f) => !f.path.endsWith('sync.idm.json') && f.path.endsWith('.idm.json')
(f) =>
!f.path.endsWith('sync.idm.json') &&
!f.path.endsWith('managed.idm.json') &&
f.path.endsWith('.idm.json')
)) {
const entities = Object.values(
JSON.parse(f.content).idm
Expand Down Expand Up @@ -483,3 +532,71 @@ function getIdmImportExportOptions(
envReplaceParams,
};
}

/**
* Helper that writes mappings in a managed.idm.json config entity to a directory
* @param managed The managed.idm.json config entity
* @param directory The directory to save the mappings
*/
export function writeManagedJsonToDirectory(
managed: ManagedSkeleton,
directory: string = 'managed',
includeMeta: boolean = true
) {
const objectPaths = [];
for (const object of managed.objects) {
const fileName = getTypedFilename(object.name, 'managed');
objectPaths.push(extractDataToFile(object, fileName, directory));
}
managed.objects = objectPaths;
saveToFile(
'idm',
managed,
'_id',
getFilePath(`${directory}/managed.idm.json`, true),
includeMeta
);
}

/**
* Helper that returns the managed.idm.json object containing all the mappings in it by looking through the files
*
* @param files the files to get managed.idm.json object from
* @returns the managed.idm.json object
*/
export function getManagedObjectsFromFiles(
files: { path: string; content: string }[]
): ManagedSkeleton {
const managedFiles = files.filter((f) =>
f.path.endsWith('/managed.idm.json')
);
if (managedFiles.length > 1) {
throw new FrodoError(
'Multiple managed.idm.json files found in idm directory'
);
}
const managed = {
_id: 'managed',
objects: [],
};
if (managedFiles.length === 1) {
const jsonData = JSON.parse(managedFiles[0].content);
const managedData = jsonData.managed
? jsonData.managed
: jsonData.idm.managed;
const managedJsonDir = managedFiles[0].path.substring(
0,
managedFiles[0].path.indexOf('/managed.idm.json')
);
if (managedData.objects) {
for (const object of managedData.objects) {
if (typeof object === 'string') {
managed.objects.push(getExtractedJsonData(object, managedJsonDir));
} else {
managed.objects.push(object);
}
}
}
}
return managed;
}
9 changes: 8 additions & 1 deletion src/utils/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import fs from 'fs';
import os from 'os';

import { readServersFromFiles } from '../ops/classic/ServerOps';
import { getManagedObjectsFromFiles } from '../ops/IdmOps';
import { getLegacyMappingsFromFiles } from '../ops/MappingOps';
import { getScriptExportByScriptFile } from '../ops/ScriptOps';
import { printMessage } from './Console';
Expand Down Expand Up @@ -159,7 +160,9 @@ export async function getConfig(
!f.path.endsWith('.script.json') &&
!f.path.endsWith('.server.json') &&
!f.path.endsWith('/sync.idm.json') &&
!f.path.endsWith('sync.json')
!f.path.endsWith('sync.json') &&
!f.path.endsWith('/managed.idm.json') &&
!f.path.endsWith('managed.json')
);
// Handle all other json files
for (const f of allOtherFiles) {
Expand All @@ -183,6 +186,10 @@ export async function getConfig(
if (sync.mappings.length > 0) {
(exportConfig as FullGlobalExportInterface).sync = sync;
}
const managed = await getManagedObjectsFromFiles(jsonFiles);
if (managed.objects.length > 0) {
(exportConfig as FullGlobalExportInterface).idm.managed = managed;
}
// Handle saml files
if (
samlFiles.length > 0 &&
Expand Down
3 changes: 3 additions & 0 deletions test/client_cli/en/__snapshots__/config-export.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ Options:
positions of the journey/tree nodes.
--no-decode Do not include decoded variable value in
variable export
-o, --separate-objects Export managed.idm.json objects
separately in their own directory.
Ignored with -a.
-s, --separate-mappings Export sync.idm.json mappings separately
in their own directory. Ignored with -a.
--sa-id <sa-id> Service account id.
Expand Down
3 changes: 3 additions & 0 deletions test/client_cli/en/__snapshots__/idm-export.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ Options:
-N, --no-metadata Does not include metadata in the export
file.
--no-cache Disable token cache for this operation.
-o, --separate-objects Export managed.idm.json objects
separately in their own directory.
Ignored with -a.
-s, --separate-mappings Export sync.idm.json mappings separately
in their own directory. Ignored with -a.
--sa-id <sa-id> Service account id.
Expand Down
Loading