diff --git a/src/cli/config-manager/config-manager-pull/config-manager-pull-authz-policies.ts b/src/cli/config-manager/config-manager-pull/config-manager-pull-authz-policies.ts index 639c70cf8..14a422636 100644 --- a/src/cli/config-manager/config-manager-pull/config-manager-pull-authz-policies.ts +++ b/src/cli/config-manager/config-manager-pull/config-manager-pull-authz-policies.ts @@ -157,7 +157,7 @@ export default function setup() { if (!outcome) { printMessage( - `Failed to export one or more authorization policy sets. ${options.verbose ? '' : 'Check --verbose for me details.'}` + `Failed to export one or more authorization policy sets. ${options.verbose ? '' : 'Check --verbose for more details.'}` ); process.exitCode = 1; } diff --git a/src/configManagerOps/FrConfigAccessConfigOps.ts b/src/configManagerOps/FrConfigAccessConfigOps.ts index 5dbcb81ed..aa37abae5 100644 --- a/src/configManagerOps/FrConfigAccessConfigOps.ts +++ b/src/configManagerOps/FrConfigAccessConfigOps.ts @@ -2,7 +2,11 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; import { getIdmImportExportOptions } from '../ops/IdmOps'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { exportConfigEntity, importConfigEntities } = frodo.idm.config; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -15,8 +19,14 @@ const { getFilePath, saveJsonToFile } = frodo.utils; export async function configManagerExportAccessConfig( envFile?: string ): Promise { + let indicatorId: string | undefined; try { const options = getIdmImportExportOptions(undefined, envFile); + indicatorId = createProgressIndicator( + 'indeterminate', + undefined, + 'Exporting access config' + ); const exportData = ( await exportConfigEntity('access', { envReplaceParams: options.envReplaceParams, @@ -29,8 +39,17 @@ export async function configManagerExportAccessConfig( getFilePath('access-config/access.json', true), false ); + + stopProgressIndicator(indicatorId, 'Exported access config'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting access config', + 'fail' + ); + } printError(error, `Error exporting config entity access`); } return false; diff --git a/src/configManagerOps/FrConfigAuditOps.ts b/src/configManagerOps/FrConfigAuditOps.ts index 3d794f007..0d25d7574 100644 --- a/src/configManagerOps/FrConfigAuditOps.ts +++ b/src/configManagerOps/FrConfigAuditOps.ts @@ -2,7 +2,11 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; import { getIdmImportExportOptions } from '../ops/IdmOps'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { exportConfigEntity, importConfigEntities } = frodo.idm.config; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -15,8 +19,14 @@ const { getFilePath, saveJsonToFile } = frodo.utils; export async function configManagerExportAudit( envFile?: string ): Promise { + let indicatorId: string | undefined; try { const options = getIdmImportExportOptions(undefined, envFile); + indicatorId = createProgressIndicator( + 'indeterminate', + undefined, + 'Exporting audit' + ); const exportData = ( await exportConfigEntity('audit', { envReplaceParams: options.envReplaceParams, @@ -25,8 +35,13 @@ export async function configManagerExportAudit( ).idm['audit']; saveJsonToFile(exportData, getFilePath('audit/audit.json', true), false); + + stopProgressIndicator(indicatorId, 'Exported audit'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting audit', 'fail'); + } printError(error, `Error exporting config entity audit`); } return false; diff --git a/src/configManagerOps/FrConfigAuthenticationOps.ts b/src/configManagerOps/FrConfigAuthenticationOps.ts index 10733613b..51f5870d4 100644 --- a/src/configManagerOps/FrConfigAuthenticationOps.ts +++ b/src/configManagerOps/FrConfigAuthenticationOps.ts @@ -2,7 +2,12 @@ import { frodo, state } from '@rockcarver/frodo-lib'; import { AuthenticationSettingsExportInterface } from '@rockcarver/frodo-lib/types/ops/AuthenticationSettingsOps'; import fs from 'fs'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; import { realmList } from '../utils/FrConfig'; const { @@ -19,19 +24,40 @@ const { getFilePath, saveJsonToFile } = frodo.utils; export async function configManagerExportAuthentication( realm?: string ): Promise { + let indicatorId: string | undefined; try { if (realm && realm !== '__default__realm__') { + indicatorId = createProgressIndicator( + 'indeterminate', + 0, + 'Exporting authentication settings' + ); const exportData = await _readAuthenticationSettings(false); const fileName = `realms/${state.getRealm()}/realm-config/authentication.json`; saveJsonToFile(exportData, getFilePath(`${fileName}`, true), false, true); + stopProgressIndicator(indicatorId, 'Exported authentication settings'); } else { - for (const realmName of await realmList()) { + const realmNames = (await realmList()).filter((realmName) => { if ( realmName === '/' && state.getDeploymentType() === frodo.utils.constants.CLOUD_DEPLOYMENT_TYPE_KEY ) - continue; + return false; + return true; + }); + + indicatorId = createProgressIndicator( + 'determinate', + realmNames.length, + 'Exporting authentication settings' + ); + + for (const realmName of realmNames) { + updateProgressIndicator( + indicatorId, + `Exporting authentication settings (${realmName})` + ); state.setRealm(realmName); const exportData = await _readAuthenticationSettings(false); @@ -43,11 +69,19 @@ export async function configManagerExportAuthentication( true ); } + stopProgressIndicator(indicatorId, 'Exported authentication settings'); } return true; } catch (error) { - printError(error, `Error exporting config entity ui-configuration`); + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting authentication settings', + 'fail' + ); + } + printError(error, `Error exporting config entity authentication`); } return false; } diff --git a/src/configManagerOps/FrConfigAuthzPoliciesOps.ts b/src/configManagerOps/FrConfigAuthzPoliciesOps.ts index d0793c4c9..5ccf22bce 100644 --- a/src/configManagerOps/FrConfigAuthzPoliciesOps.ts +++ b/src/configManagerOps/FrConfigAuthzPoliciesOps.ts @@ -4,7 +4,13 @@ import { PolicySetSkeleton } from '@rockcarver/frodo-lib/types/api/PolicySetApi' import { ResourceTypeSkeleton } from '@rockcarver/frodo-lib/types/api/ResourceTypesApi'; import { readFile } from 'fs/promises'; -import { printError, verboseMessage } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, + verboseMessage, +} from '../utils/Console'; const { getFilePath, saveJsonToFile } = frodo.utils; const { policySet, policy, resourceType } = frodo.authz; @@ -127,9 +133,16 @@ export async function configManagerExportAuthzPolicySet( const allPoliciesOfThis: PolicySkeleton[] = await policy.readPoliciesByPolicySet(ps.name); if (allPoliciesOfThis.length !== 0) { + const indicatorId = createProgressIndicator( + 'determinate', + allPoliciesOfThis.length, + `Exporting policies (${ps.name})` + ); for (const p of allPoliciesOfThis) { + updateProgressIndicator(indicatorId, `Exporting policy ${p.name}`); await exportPolicy(p); } + stopProgressIndicator(indicatorId, 'Exported authorization policies'); } else { verboseMessage( ` There are no policies in the policy-set "${ps.name}"` @@ -216,6 +229,12 @@ export async function configManagerExportAuthzPolicySetsRealm(): Promise { try { for (const realm of await readRealms()) { + if ( + realm.name === '/' && + state.getDeploymentType() === + frodo.utils.constants.CLOUD_DEPLOYMENT_TYPE_KEY + ) + continue; // set realm of state because policySet.readPolicySets() uses state to check realm state.setRealm(realm.name); if (!(await configManagerExportAuthzPolicySetsRealm())) { diff --git a/src/configManagerOps/FrConfigConnectorDefinitionsOps.ts b/src/configManagerOps/FrConfigConnectorDefinitionsOps.ts index fa45e73f3..1e7e5fff3 100644 --- a/src/configManagerOps/FrConfigConnectorDefinitionsOps.ts +++ b/src/configManagerOps/FrConfigConnectorDefinitionsOps.ts @@ -2,7 +2,13 @@ import { frodo } from '@rockcarver/frodo-lib'; import { ConnectorSkeleton } from '@rockcarver/frodo-lib/types/ops/ConnectorOps'; import fs from 'fs'; -import { printError, verboseMessage } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, + verboseMessage, +} from '../utils/Console'; const { connector } = frodo.idm; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -64,15 +70,37 @@ export async function configManagerExportConnectorDefinition( * @returns */ export async function configManagerExportConnectorDefinitionsAll(): Promise { + let indicatorId: string | undefined; try { const cs: ConnectorSkeleton[] = await connector.readConnectors(); + indicatorId = createProgressIndicator( + 'determinate', + cs.length, + 'Exporting connector definitions' + ); for (const c of cs) { + updateProgressIndicator( + indicatorId, + `Exporting connector definition ${c._id}` + ); if (c._id.includes('provisioner.openicf/')) { - configManagerExportConnectorDefinition({ c: c }); + await configManagerExportConnectorDefinition({ c: c }); } } + + stopProgressIndicator( + indicatorId, + `Exported ${cs.length} connector definitions` + ); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting connector definitions', + 'fail' + ); + } printError(error); } } diff --git a/src/configManagerOps/FrConfigConnectorMappingOps.ts b/src/configManagerOps/FrConfigConnectorMappingOps.ts index cf7031358..18849ca98 100644 --- a/src/configManagerOps/FrConfigConnectorMappingOps.ts +++ b/src/configManagerOps/FrConfigConnectorMappingOps.ts @@ -1,7 +1,12 @@ import { frodo } from '@rockcarver/frodo-lib'; import { extractFrConfigDataToFile } from '../utils/Config'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; const { getFilePath, saveJsonToFile } = frodo.utils; const { readConfigEntity } = frodo.idm.config; @@ -46,14 +51,33 @@ function processMappings(mapping, targetDir, name) { * @returns {Promise} true if successful, false otherwise */ export async function configManagerExportMappings(): Promise { + let indicatorId: string | undefined; try { const exportData = await readConfigEntity('sync'); + const allMappings = Object.values(exportData.mappings); const fileDir = `sync/mappings`; - for (const mapping of Object.values(exportData.mappings)) { - processMappings(mapping, `${fileDir}/${mapping.name}`, mapping.name); + indicatorId = createProgressIndicator( + 'determinate', + allMappings.length, + 'Exporting mappings' + ); + for (const mapping of allMappings) { + const m = mapping as { _id?: string; name: string }; + updateProgressIndicator( + indicatorId, + `Exporting mapping ${m._id ?? m.name}` + ); + processMappings(m, `${fileDir}/${m.name}`, m.name); } + stopProgressIndicator( + indicatorId, + `${allMappings.length} mappings exported.` + ); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, `Error exporting mappings`, 'fail'); + } printError(error, `Error exporting mappings to files`); } return false; diff --git a/src/configManagerOps/FrConfigCookieDomainsOps.ts b/src/configManagerOps/FrConfigCookieDomainsOps.ts index d23582a39..5d05e8f91 100644 --- a/src/configManagerOps/FrConfigCookieDomainsOps.ts +++ b/src/configManagerOps/FrConfigCookieDomainsOps.ts @@ -1,7 +1,11 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { getFilePath, saveJsonToFile } = frodo.utils; const { readCookieDomains, updateCookieDomains } = frodo.cloud.env; @@ -11,16 +15,30 @@ const { readCookieDomains, updateCookieDomains } = frodo.cloud.env; * @return {Promise} a promise that resolves to true if successful, false otherwise */ export async function configManagerExportCookieDomains(): Promise { + let indicatorId: string | undefined; try { + indicatorId = createProgressIndicator( + 'indeterminate', + undefined, + 'Exporting cookie domains' + ); const exportData = await readCookieDomains(); saveJsonToFile( exportData, getFilePath('cookie-domains/cookie-domains.json', true), false ); + stopProgressIndicator(indicatorId, 'Exported cookie domains'); return true; } catch (error) { - printError(error, `Error exporting custom domains`); + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting cookie domains', + 'fail' + ); + } + printError(error, `Error exporting cookie domains`); } return false; } diff --git a/src/configManagerOps/FrConfigCorsOps.ts b/src/configManagerOps/FrConfigCorsOps.ts index 74f332c0b..78c81470a 100644 --- a/src/configManagerOps/FrConfigCorsOps.ts +++ b/src/configManagerOps/FrConfigCorsOps.ts @@ -2,7 +2,11 @@ import { frodo } from '@rockcarver/frodo-lib'; import { IdObjectSkeletonInterface } from '@rockcarver/frodo-lib/types/api/ApiTypes'; import { FullService } from '@rockcarver/frodo-lib/types/api/ServiceApi'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { config } = frodo.idm; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -14,7 +18,13 @@ type CorsObject = { idmCorsConfig; corsServices; corsServiceGlobal }; * @returns True if file was successfully saved */ export async function configManagerExportCors(): Promise { + let indicatorId: string | undefined; try { + indicatorId = createProgressIndicator( + 'indeterminate', + undefined, + 'Exporting cors' + ); const cors: IdObjectSkeletonInterface = await config.readConfigEntity('servletfilter/cors'); const services: FullService[] = await frodo.service.getFullServices(true); @@ -35,8 +45,13 @@ export async function configManagerExportCors(): Promise { false, true ); + + stopProgressIndicator(indicatorId, 'Exported cors'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting cors', 'fail'); + } printError(error); return false; } diff --git a/src/configManagerOps/FrConfigCspOps.ts b/src/configManagerOps/FrConfigCspOps.ts index 90186afc9..db55f5c2b 100644 --- a/src/configManagerOps/FrConfigCspOps.ts +++ b/src/configManagerOps/FrConfigCspOps.ts @@ -3,7 +3,11 @@ import { ContentSecurityPolicy } from '@rockcarver/frodo-lib/types/api/cloud/Env import { applyDiff } from 'deep-diff'; import { readFile } from 'fs/promises'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { env } = frodo.cloud; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -15,7 +19,13 @@ const { getFilePath, saveJsonToFile } = frodo.utils; export async function configManagerExportCsp( file: string = null ): Promise { + let indicatorId: string | undefined; try { + indicatorId = createProgressIndicator( + 'indeterminate', + undefined, + 'Exporting content security policies' + ); const cspEnforced: ContentSecurityPolicy = await env.readEnforcedContentSecurityPolicy(); const cspReport: ContentSecurityPolicy = @@ -34,8 +44,16 @@ export async function configManagerExportCsp( } saveJsonToFile(csp, getFilePath('csp/csp.json', true), false, true); + stopProgressIndicator(indicatorId, 'Exported content security policies'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting content security policies', + 'fail' + ); + } printError(error); return false; } diff --git a/src/configManagerOps/FrConfigCustomNodesOps.ts b/src/configManagerOps/FrConfigCustomNodesOps.ts index 91db0016c..37449c504 100644 --- a/src/configManagerOps/FrConfigCustomNodesOps.ts +++ b/src/configManagerOps/FrConfigCustomNodesOps.ts @@ -1,6 +1,11 @@ import { frodo } from '@rockcarver/frodo-lib'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; const { saveJsonToFile, getFilePath, saveTextToFile } = frodo.utils; const { readCustomNode, readCustomNodes } = frodo.authn.node; @@ -15,16 +20,33 @@ const { readCustomNode, readCustomNodes } = frodo.authn.node; export async function configManagerExportCustomNodes( name?: string ): Promise { + let indicatorId: string | undefined; try { let customNodes; if (name) { + indicatorId = createProgressIndicator( + 'indeterminate', + 0, + 'Exporting custom nodes' + ); const customNode = await readCustomNode(undefined, name); customNodes = [customNode]; } else { customNodes = await readCustomNodes(); + indicatorId = createProgressIndicator( + 'determinate', + customNodes.length, + 'Exporting custom nodes' + ); } for (const node of customNodes) { + if (indicatorId) { + updateProgressIndicator( + indicatorId, + `Exporting custom node ${node.displayName}` + ); + } const nodeDir = getFilePath( `custom-nodes/nodes/${node.displayName}/`, true @@ -39,8 +61,16 @@ export async function configManagerExportCustomNodes( saveJsonToFile(node, filePath, false); } + stopProgressIndicator(indicatorId, 'Exported custom nodes'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting custom nodes', + 'fail' + ); + } printError(error); return false; } diff --git a/src/configManagerOps/FrConfigEmailProviderOps.ts b/src/configManagerOps/FrConfigEmailProviderOps.ts index a555bb947..1ce8d39b3 100644 --- a/src/configManagerOps/FrConfigEmailProviderOps.ts +++ b/src/configManagerOps/FrConfigEmailProviderOps.ts @@ -2,7 +2,11 @@ import { frodo } from '@rockcarver/frodo-lib'; import { IdObjectSkeletonInterface } from '@rockcarver/frodo-lib/types/api/ApiTypes'; import fs from 'fs'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { config } = frodo.idm; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -12,7 +16,13 @@ const { getFilePath, saveJsonToFile } = frodo.utils; * @returns True if file was successfully saved */ export async function configManagerExportEmailProviderConfiguration(): Promise { + let indicatorId: string | undefined; try { + indicatorId = createProgressIndicator( + 'indeterminate', + 0, + 'Exporting email provider configuration' + ); const emailProvider: IdObjectSkeletonInterface = await config.readConfigEntity('external.email'); @@ -22,8 +32,16 @@ export async function configManagerExportEmailProviderConfiguration(): Promise { + let indicatorId: string | undefined; try { if (name) { + indicatorId = createProgressIndicator( + 'indeterminate', + 0, + 'Exporting email templates' + ); const exportData = await readEmailTemplate(name); - processEmailTemplate(exportData, `email-templates`); + await processEmailTemplate(exportData, `email-templates`); + stopProgressIndicator(indicatorId, 'Exported email templates'); } else { const exportData = await readEmailTemplates(); - exportData.forEach(async (template) => { - processEmailTemplate(template, `email-templates`); - }); + indicatorId = createProgressIndicator( + 'determinate', + exportData.length, + 'Exporting email templates' + ); + for (const template of exportData) { + const templateName = template._id.split('/')[1]; + updateProgressIndicator( + indicatorId, + `Exporting email template ${templateName}` + ); + await processEmailTemplate(template, `email-templates`); + } + stopProgressIndicator( + indicatorId, + `${exportData.length} email templates exported` + ); } return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting email templates', + 'fail' + ); + } printError(error, `Error exporting email templates to files`); } return false; diff --git a/src/configManagerOps/FrConfigEndpointsOps.ts b/src/configManagerOps/FrConfigEndpointsOps.ts index 04d390c3e..1becf2420 100644 --- a/src/configManagerOps/FrConfigEndpointsOps.ts +++ b/src/configManagerOps/FrConfigEndpointsOps.ts @@ -2,7 +2,12 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; import { extractFrConfigDataToFile } from '../utils/Config'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; const { readConfigEntitiesByType, importConfigEntities } = frodo.idm.config; const { saveJsonToFile, getFilePath } = frodo.utils; @@ -15,45 +20,67 @@ const { saveJsonToFile, getFilePath } = frodo.utils; export async function configManagerExportEndpoints( endpointName?: string ): Promise { + let indicatorId: string | undefined; try { const exportData = await readConfigEntitiesByType('endpoint'); - processEndpoints(exportData, 'endpoints', endpointName); + const endpointsToExport = exportData.filter( + (endpoint) => + !endpoint.file && + endpoint._id.startsWith('endpoint') && + (endpoint.context === undefined || + endpoint.context === null || + (typeof endpoint.context === 'string' && + !endpoint.context.startsWith('util'))) && + endpoint._id !== 'endpoint/linkedView' && + (!endpointName || endpoint._id.split('/')[1] === endpointName) + ); + indicatorId = createProgressIndicator( + 'determinate', + endpointsToExport.length, + 'Exporting endpoints' + ); + processEndpoints(endpointsToExport, 'endpoints', undefined, indicatorId); + stopProgressIndicator( + indicatorId, + `${endpointsToExport.length} endpoints exported.` + ); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting endpoints', 'fail'); + } printError(error, `Error exporting config entity endpoints`); } return false; } -function processEndpoints(endpoints, fileDir, name?) { +function processEndpoints(endpoints, fileDir, name?, indicatorId?: string) { try { - endpoints - .filter( - (endpoint) => - !endpoint.file && - endpoint._id.startsWith('endpoint') && - (!endpoint.context || !endpoint.context.startsWith('util')) && - endpoint._id !== 'endpoint/linkedView' - ) - .forEach((endpoint) => { - const endpointName = endpoint._id.split('/')[1]; - if (name && name !== endpointName) { - return; - } - const endpointDir = `${fileDir}/${endpointName}`; - const scriptFilename = `${endpointName}.${endpoint.type === 'groovy' ? 'groovy' : 'js'}`; + endpoints.forEach((endpoint) => { + const endpointName = endpoint._id.split('/')[1]; + if (name && name !== endpointName) { + return; + } + const endpointDir = `${fileDir}/${endpointName}`; + const scriptFilename = `${endpointName}.${endpoint.type === 'groovy' ? 'groovy' : 'js'}`; - extractFrConfigDataToFile(endpoint.source, scriptFilename, endpointDir); - delete endpoint.source; - endpoint.file = `${scriptFilename}`; - const endpointFilename = `${endpointDir}/${endpointName}.json`; - saveJsonToFile( - endpoint, - getFilePath(endpointFilename, true), - false, - true + extractFrConfigDataToFile(endpoint.source, scriptFilename, endpointDir); + delete endpoint.source; + endpoint.file = `${scriptFilename}`; + const endpointFilename = `${endpointDir}/${endpointName}.json`; + saveJsonToFile( + endpoint, + getFilePath(endpointFilename, true), + false, + true + ); + if (indicatorId) { + updateProgressIndicator( + indicatorId, + `Exporting endpoint ${endpoint._id}` ); - }); + } + }); } catch (err) { printError(err); } diff --git a/src/configManagerOps/FrConfigEntityOps.ts b/src/configManagerOps/FrConfigEntityOps.ts index e74a22960..ea07fb4fe 100644 --- a/src/configManagerOps/FrConfigEntityOps.ts +++ b/src/configManagerOps/FrConfigEntityOps.ts @@ -1,7 +1,11 @@ import { frodo } from '@rockcarver/frodo-lib'; import { getIdmImportExportOptions } from '../ops/IdmOps'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { exportConfigEntity } = frodo.idm.config; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -14,8 +18,14 @@ const { getFilePath, saveJsonToFile } = frodo.utils; export async function configManagerExportConfigEntity( envFile?: string ): Promise { + let indicatorId: string | undefined; try { const options = getIdmImportExportOptions(undefined, envFile); + indicatorId = createProgressIndicator( + 'indeterminate', + 0, + 'Exporting config entity' + ); const exportData = ( await exportConfigEntity('ui/configuration', { envReplaceParams: options.envReplaceParams, @@ -28,8 +38,16 @@ export async function configManagerExportConfigEntity( getFilePath('ui-configuration.json', true), false ); + stopProgressIndicator(indicatorId, 'Exported config entity'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting config entity', + 'fail' + ); + } printError(error, `Error exporting config entity ui-configuration`); } return false; diff --git a/src/configManagerOps/FrConfigInternalRolesOps.ts b/src/configManagerOps/FrConfigInternalRolesOps.ts index a68c93518..a2d830333 100644 --- a/src/configManagerOps/FrConfigInternalRolesOps.ts +++ b/src/configManagerOps/FrConfigInternalRolesOps.ts @@ -1,7 +1,12 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; const { getFilePath, saveJsonToFile } = frodo.utils; const { readInternalRoles, importInternalRoles } = frodo.role; @@ -12,19 +17,44 @@ const { readInternalRoles, importInternalRoles } = frodo.role; export async function configManagerExportInternalRoles( name?: string ): Promise { + let indicatorId: string | undefined; try { const exportData = await readInternalRoles(); - for (const role of Object.values(exportData)) { + const rolesToExport = Object.values(exportData).filter((role) => { + if (name && name !== role.name) return false; + return !!(role.privileges && role.privileges.length > 0); + }); + indicatorId = createProgressIndicator( + 'determinate', + rolesToExport.length, + 'Exporting internal roles' + ); + for (const role of rolesToExport) { if (name && name !== role.name) { continue; } if (role.privileges && role.privileges.length > 0) { const fileName = `internal-roles/${role.name}.json`; saveJsonToFile(role, getFilePath(fileName, true), false, true); + updateProgressIndicator( + indicatorId, + `Exporting internal role ${role.name}` + ); } } + stopProgressIndicator( + indicatorId, + `${rolesToExport.length} internal roles exported.` + ); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting internal roles', + 'fail' + ); + } printError(error, `Error exporting internal roles to files`); } return false; diff --git a/src/configManagerOps/FrConfigKbaOps.ts b/src/configManagerOps/FrConfigKbaOps.ts index 79d8e7daa..98cb9d380 100644 --- a/src/configManagerOps/FrConfigKbaOps.ts +++ b/src/configManagerOps/FrConfigKbaOps.ts @@ -2,7 +2,11 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; import { getIdmImportExportOptions } from '../ops/IdmOps'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { exportConfigEntity, importConfigEntities } = frodo.idm.config; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -15,8 +19,10 @@ const { getFilePath, saveJsonToFile } = frodo.utils; export async function configManagerExportKbaConfig( envFile?: string ): Promise { + let indicatorId: string | undefined; try { const options = getIdmImportExportOptions(undefined, envFile); + indicatorId = createProgressIndicator('indeterminate', 0, 'Exporting kba'); const exportData = ( await exportConfigEntity('selfservice.kba', { envReplaceParams: options.envReplaceParams, @@ -29,8 +35,12 @@ export async function configManagerExportKbaConfig( getFilePath('kba/selfservice.kba.json', true), false ); + stopProgressIndicator(indicatorId, 'Exported kba'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting kba', 'fail'); + } printError(error, `Error exporting config entity selfservice.kba`); } return false; diff --git a/src/configManagerOps/FrConfigLocalesOps.ts b/src/configManagerOps/FrConfigLocalesOps.ts index 288676380..4983f0a6c 100644 --- a/src/configManagerOps/FrConfigLocalesOps.ts +++ b/src/configManagerOps/FrConfigLocalesOps.ts @@ -1,7 +1,12 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; const { readConfigEntitiesByType, importConfigEntities } = frodo.idm.config; const { saveJsonToFile, getFilePath } = frodo.utils; @@ -14,26 +19,43 @@ const { saveJsonToFile, getFilePath } = frodo.utils; export async function configManagerExportLocales( localeName?: string ): Promise { + let indicatorId: string | undefined; try { const exportData = await readConfigEntitiesByType('uilocale'); - processLocales(exportData, 'locales', localeName); + const localesToExport = exportData.filter((locale) => { + const name = locale._id.split('/')[1]; + return !localeName || localeName === name; + }); + indicatorId = createProgressIndicator( + 'determinate', + localesToExport.length, + 'Exporting locales' + ); + processLocales(localesToExport, 'locales', indicatorId); + stopProgressIndicator( + indicatorId, + `${localesToExport.length} locales exported.` + ); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting locales', 'fail'); + } printError(error, `Error exporting config entity locales`); } return false; } -function processLocales(locales, fileDir, name?) { +function processLocales(locales, fileDir, indicatorId?: string) { try { locales.forEach((locale) => { const localeName = locale._id.split('/')[1]; - if (name && name !== localeName) { - return; - } const localeFilename = `${fileDir}/${localeName}.json`; saveJsonToFile(locale, getFilePath(localeFilename, true), false, true); + if (indicatorId) { + updateProgressIndicator(indicatorId, `Exporting locale ${localeName}`); + } }); } catch (err) { printError(err); diff --git a/src/configManagerOps/FrConfigManagedObjectsOps.ts b/src/configManagerOps/FrConfigManagedObjectsOps.ts index 97d8fef88..4d42e4ded 100644 --- a/src/configManagerOps/FrConfigManagedObjectsOps.ts +++ b/src/configManagerOps/FrConfigManagedObjectsOps.ts @@ -4,7 +4,12 @@ import fs from 'fs'; import path from 'path'; import { extractFrConfigDataToFile } from '../utils/Config'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; const { readConfigEntity, importConfigEntities, importSubConfigEntity } = frodo.idm.config; @@ -28,17 +33,47 @@ const SCRIPT_HOOKS = ['onStore', 'onRetrieve', 'onValidate']; export async function configManagerExportManagedObjects( objectName?: string ): Promise { + let indicatorId: string | undefined; try { const exportData = (await readConfigEntity('managed')) as ManagedSkeleton; - processManagedObjects(exportData.objects, 'managed-objects', objectName); + const objectsToExport = exportData.objects.filter((o) => { + return !objectName || objectName === o.name; + }); + indicatorId = createProgressIndicator( + 'determinate', + objectsToExport.length, + 'Exporting managed objects' + ); + processManagedObjects( + objectsToExport, + 'managed-objects', + undefined, + indicatorId + ); + stopProgressIndicator( + indicatorId, + `${objectsToExport.length} managed objects exported.` + ); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting managed objects', + 'fail' + ); + } printError(error, `Error exporting config entity endpoints`); } return false; } -function processManagedObjects(managedObjects, targetDir, name) { +function processManagedObjects( + managedObjects, + targetDir, + name, + indicatorId?: string +) { try { managedObjects.forEach((managedObject) => { if (name && name !== managedObject.name) { @@ -108,6 +143,12 @@ function processManagedObjects(managedObjects, targetDir, name) { const fileName = `${objectPath}/${managedObject.name}.json`; saveTextToFile(stringify(managedObject), getFilePath(fileName, true)); + if (indicatorId) { + updateProgressIndicator( + indicatorId, + `Exporting managed object ${managedObject.name}` + ); + } }); } catch (err) { printError(err); diff --git a/src/configManagerOps/FrConfigOauth2AgentOps.ts b/src/configManagerOps/FrConfigOauth2AgentOps.ts index b053e66bb..9ed004a00 100644 --- a/src/configManagerOps/FrConfigOauth2AgentOps.ts +++ b/src/configManagerOps/FrConfigOauth2AgentOps.ts @@ -2,7 +2,13 @@ import { frodo, state } from '@rockcarver/frodo-lib'; import { AgentSkeleton } from '@rockcarver/frodo-lib/types/api/AgentApi'; import { readFile } from 'fs/promises'; -import { printError, verboseMessage } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, + verboseMessage, +} from '../utils/Console'; const { getFilePath, saveJsonToFile } = frodo.utils; const { readRealms } = frodo.realm; @@ -122,19 +128,40 @@ export async function configManagerExportAgent( export async function configManagerExportConfigAgents( configFile: string ): Promise { + let indicatorId: string | undefined; try { verboseMessage(`Reading the config file "${configFile}"`); const configFileData = JSON.parse( await readFile(configFile, { encoding: 'utf8' }) ); + const totalAgents = Object.keys(configFileData).reduce( + (acc: number, realm) => { + return acc + getAgents(configFileData, realm).length; + }, + 0 + ); + indicatorId = createProgressIndicator( + 'determinate', + totalAgents, + 'Exporting oauth2 agents' + ); for (const realm of Object.keys(configFileData)) { state.setRealm(realm); const agents: idAndOverrids[] = getAgents(configFileData, realm); if (agents.length !== 0) { for (const agent of agents) { + updateProgressIndicator( + indicatorId, + `Exporting agent ${realm}/${agent.id}` + ); if ( !(await configManagerExportAgent(agent.id, null, agent.overrides)) ) { + stopProgressIndicator( + indicatorId, + 'Error exporting oauth2 agents', + 'fail' + ); return false; } } @@ -144,8 +171,16 @@ export async function configManagerExportConfigAgents( ); } } + stopProgressIndicator(indicatorId, 'Exported oauth2 agents'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting oauth2 agents', + 'fail' + ); + } printError(error); return false; } diff --git a/src/configManagerOps/FrConfigOrgPrivilegesOps.ts b/src/configManagerOps/FrConfigOrgPrivilegesOps.ts index 7b50a5a38..fc5855923 100644 --- a/src/configManagerOps/FrConfigOrgPrivilegesOps.ts +++ b/src/configManagerOps/FrConfigOrgPrivilegesOps.ts @@ -2,7 +2,12 @@ import { frodo, state } from '@rockcarver/frodo-lib'; import { IdObjectSkeletonInterface } from '@rockcarver/frodo-lib/types/api/ApiTypes'; import fs from 'fs'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; const { config } = frodo.idm; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -59,23 +64,56 @@ export async function configManagerExportOrgPrivileges(): Promise { * @returns True if configuration files were successfully saved */ export async function configManagerExportOrgPrivilegesAllRealms(): Promise { + let indicatorId: string | undefined; try { - configManagerExportOrgPrivileges(); - for (const realm of await readRealms()) { + const realms = await readRealms(); + const exportableRealms = realms.filter((realm) => { if ( realm.name === '/' && state.getDeploymentType() === frodo.utils.constants.CLOUD_DEPLOYMENT_TYPE_KEY ) - continue; + return false; + return true; + }); + + indicatorId = createProgressIndicator( + 'determinate', + exportableRealms.length + 1, + 'Exporting organization privileges' + ); + updateProgressIndicator( + indicatorId, + 'Exporting organization privileges (privilegeAssignments)' + ); + await configManagerExportOrgPrivileges(); + + for (const realm of exportableRealms) { state.setRealm(realm.name); + updateProgressIndicator( + indicatorId, + `Exporting organization privileges (${realm.name})` + ); if (!(await configManagerExportOrgPrivilegesRealm(realm.name))) { + stopProgressIndicator( + indicatorId, + 'Error exporting organization privileges', + 'fail' + ); return false; } } + stopProgressIndicator(indicatorId, 'Exported organization privileges'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting organization privileges', + 'fail' + ); + } printError(error); return false; } diff --git a/src/configManagerOps/FrConfigPasswordPolicyOps.ts b/src/configManagerOps/FrConfigPasswordPolicyOps.ts index 1f3520cce..d74900bf5 100644 --- a/src/configManagerOps/FrConfigPasswordPolicyOps.ts +++ b/src/configManagerOps/FrConfigPasswordPolicyOps.ts @@ -2,7 +2,12 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; import { getIdmImportExportOptions } from '../ops/IdmOps'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; import { realmList } from '../utils/FrConfig'; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -17,9 +22,15 @@ export async function configManagerExportPasswordPolicy( realm?: string, envFile?: string ): Promise { + let indicatorId: string | undefined; try { const options = getIdmImportExportOptions(undefined, envFile); if (realm && realm !== '__default__realm__') { + indicatorId = createProgressIndicator( + 'indeterminate', + 0, + 'Exporting password policy' + ); const realmData = ( await exportConfigEntity(`fieldPolicy/${realm}_user`, { envReplaceParams: options.envReplaceParams, @@ -28,10 +39,23 @@ export async function configManagerExportPasswordPolicy( ).idm[`fieldPolicy/${realm}_user`]; const fileName = `realms/${realm}/password-policy/${realm}_user-password-policy.json`; saveJsonToFile(realmData, getFilePath(fileName, true), false, true); + stopProgressIndicator(indicatorId, 'Exported password policy'); } else { - for (const realmName of await realmList()) { + const realms = (await realmList()).filter( + (realmName) => realmName !== '/' + ); + indicatorId = createProgressIndicator( + 'determinate', + realms.length, + 'Exporting password policy' + ); + for (const realmName of realms) { // fr-config-manager doesn't support root themes if (realmName === '/') continue; + updateProgressIndicator( + indicatorId, + `Exporting password policy (${realmName})` + ); const realmData = ( await exportConfigEntity(`fieldPolicy/${realmName}_user`, { envReplaceParams: options.envReplaceParams, @@ -41,9 +65,17 @@ export async function configManagerExportPasswordPolicy( const fileName = `realms/${realmName}/password-policy/${realmName}_user-password-policy.json`; saveJsonToFile(realmData, getFilePath(fileName, true), false, true); } + stopProgressIndicator(indicatorId, 'Exported password policy'); } return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting password policy', + 'fail' + ); + } printError(error, `Error exporting config entity ui-configuration`); } return false; diff --git a/src/configManagerOps/FrConfigRawOps.ts b/src/configManagerOps/FrConfigRawOps.ts index 934cce819..de52ae253 100644 --- a/src/configManagerOps/FrConfigRawOps.ts +++ b/src/configManagerOps/FrConfigRawOps.ts @@ -2,7 +2,13 @@ import { frodo } from '@rockcarver/frodo-lib'; import { IdObjectSkeletonInterface } from '@rockcarver/frodo-lib/types/api/ApiTypes'; import { readFile } from 'fs/promises'; -import { printError, verboseMessage } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, + verboseMessage, +} from '../utils/Console'; const { getFilePath, saveJsonToFile } = frodo.utils; const { exportRawConfig } = frodo.rawConfig; @@ -12,11 +18,21 @@ const { exportRawConfig } = frodo.rawConfig; * @returns True if each file was successfully exported */ export async function configManagerExportRaw(file: string): Promise { + let indicatorId: string | undefined; try { const jsonData = JSON.parse(await readFile(file, { encoding: 'utf8' })); + indicatorId = createProgressIndicator( + 'determinate', + jsonData.length, + 'Exporting raw config' + ); // Create export json file for every item in the provided json file for (const config of jsonData) { + updateProgressIndicator( + indicatorId, + `Exporting raw config ${config.path ?? ''}`.trim() + ); const response: IdObjectSkeletonInterface = await exportRawConfig(config); verboseMessage(`Saving ${response._id} at ${config.path}.json.`); saveJsonToFile( @@ -27,8 +43,15 @@ export async function configManagerExportRaw(file: string): Promise { ); } + stopProgressIndicator( + indicatorId, + `${jsonData.length} raw config items exported` + ); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting raw config', 'fail'); + } printError(error); return false; } diff --git a/src/configManagerOps/FrConfigRemoteServersOps.ts b/src/configManagerOps/FrConfigRemoteServersOps.ts index 3068b7d78..4616ff185 100644 --- a/src/configManagerOps/FrConfigRemoteServersOps.ts +++ b/src/configManagerOps/FrConfigRemoteServersOps.ts @@ -1,7 +1,11 @@ import { frodo } from '@rockcarver/frodo-lib'; import { getIdmImportExportOptions } from '../ops/IdmOps'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { exportConfigEntity } = frodo.idm.config; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -14,8 +18,14 @@ const { getFilePath, saveJsonToFile } = frodo.utils; export async function configManagerExportRemoteServers( envFile?: string ): Promise { + let indicatorId: string | undefined; try { const options = getIdmImportExportOptions(undefined, envFile); + indicatorId = createProgressIndicator( + 'indeterminate', + 0, + 'Exporting remote servers' + ); const exportData = ( await exportConfigEntity('provisioner.openicf.connectorinfoprovider', { envReplaceParams: options.envReplaceParams, @@ -31,8 +41,16 @@ export async function configManagerExportRemoteServers( ), false ); + stopProgressIndicator(indicatorId, 'Exported remote servers'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting remote servers', + 'fail' + ); + } printError( error, `Error exporting config entity RCS: provisioner.openicf.connectorinfoprovider` diff --git a/src/configManagerOps/FrConfigSamlOps.ts b/src/configManagerOps/FrConfigSamlOps.ts index 312311f73..66cbd9bc1 100644 --- a/src/configManagerOps/FrConfigSamlOps.ts +++ b/src/configManagerOps/FrConfigSamlOps.ts @@ -1,7 +1,12 @@ import { frodo, state } from '@rockcarver/frodo-lib'; import fs from 'fs'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; import { escapePlaceholders, replaceAllInJson, @@ -17,11 +22,26 @@ const { exportCircleOfTrust } = frodo.saml2.circlesOfTrust; * @return {Promise} a promise that resolves to true if successful, false otherwise */ export async function configManagerExportSaml(file): Promise { + let indicatorId: string | undefined; try { const objects = JSON.parse(fs.readFileSync(file, 'utf8')); + const total = Object.keys(objects).reduce((acc: number, realm: string) => { + const samlProviders = objects[realm]?.samlProviders?.length ?? 0; + const cots = objects[realm]?.circlesOfTrust?.length ?? 0; + return acc + samlProviders + cots; + }, 0); + indicatorId = createProgressIndicator( + 'determinate', + total, + 'Exporting saml' + ); for (const realm of Object.keys(objects)) { state.setRealm(realm); for (const samlProvider of objects[realm].samlProviders) { + updateProgressIndicator( + indicatorId, + `Exporting saml provider ${samlProvider.entityId}` + ); const result = await exportSaml2Provider(samlProvider.entityId, { deps: false, }); @@ -81,6 +101,10 @@ export async function configManagerExportSaml(file): Promise { ); } for (const cot of objects[realm].circlesOfTrust) { + updateProgressIndicator( + indicatorId, + `Exporting circle of trust ${cot}` + ); const cotResult = await exportCircleOfTrust(cot); const fileDirectory = `realms/${realm}/realm-config/saml/COT`; @@ -92,8 +116,12 @@ export async function configManagerExportSaml(file): Promise { ); } } + stopProgressIndicator(indicatorId, 'Exported saml'); return true; } catch (err) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting saml', 'fail'); + } printError(err, `Error exporting SAML`); } return false; diff --git a/src/configManagerOps/FrConfigSchedulesOps.ts b/src/configManagerOps/FrConfigSchedulesOps.ts index cc21e78f7..e1b70063b 100644 --- a/src/configManagerOps/FrConfigSchedulesOps.ts +++ b/src/configManagerOps/FrConfigSchedulesOps.ts @@ -2,7 +2,12 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; import { extractFrConfigDataToFile } from '../utils/Config'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; const { readConfigEntitiesByType, importConfigEntities } = frodo.idm.config; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -13,17 +18,35 @@ const { getFilePath, saveJsonToFile } = frodo.utils; export async function configManagerExportSchedules( name?: string ): Promise { + let indicatorId: string | undefined; try { const exportData = await readConfigEntitiesByType('schedule'); - processSchedules(exportData, 'schedules', name); + const schedulesToExport = exportData.filter((schedule) => { + if (schedule._id === 'scheduler') return false; + const scheduleName = schedule._id.split('/')[1]; + return !name || name === scheduleName; + }); + indicatorId = createProgressIndicator( + 'determinate', + schedulesToExport.length, + 'Exporting schedules' + ); + processSchedules(schedulesToExport, 'schedules', undefined, indicatorId); + stopProgressIndicator( + indicatorId, + `${schedulesToExport.length} schedules exported.` + ); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting schedules', 'fail'); + } printError(error, `Error exporting internal schedules to files`); } return false; } -function processSchedules(schedules, fileDir, name?) { +function processSchedules(schedules, fileDir, name?, indicatorId?: string) { try { schedules.forEach((schedule) => { if (schedule._id !== 'scheduler') { @@ -64,6 +87,12 @@ function processSchedules(schedules, fileDir, name?) { false, true ); + if (indicatorId) { + updateProgressIndicator( + indicatorId, + `Exporting schedule ${schedule._id ?? scheduleName}` + ); + } } }); } catch (err) { diff --git a/src/configManagerOps/FrConfigScriptOps.ts b/src/configManagerOps/FrConfigScriptOps.ts index c68a6a852..d0e7188b1 100644 --- a/src/configManagerOps/FrConfigScriptOps.ts +++ b/src/configManagerOps/FrConfigScriptOps.ts @@ -1,7 +1,13 @@ import { frodo, state } from '@rockcarver/frodo-lib'; import { ScriptSkeleton } from '@rockcarver/frodo-lib/types/api/ScriptApi'; -import { printError, verboseMessage } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, + verboseMessage, +} from '../utils/Console'; import { realmList, safeFileName } from '../utils/FrConfig'; const { getFilePath, saveJsonToFile, decodeBase64, saveTextToFile } = @@ -104,8 +110,10 @@ export async function configManagerExportScriptsRealms( justContent: boolean = false, justConfig: boolean = false, scriptType: string = null, - language: string = 'JAVASCRIPT' + language: string = 'JAVASCRIPT', + showProgress: boolean = true ): Promise { + let indicatorId: string | undefined; try { // create scripts directory if it doesnt exist even if there are no scripts, thats what fr-config-manager does getFilePath(`realms/${state.getRealm()}/scripts/`, true); @@ -162,18 +170,41 @@ export async function configManagerExportScriptsRealms( // if there are no scripts, return if (allScripts.length !== 0) { + if (showProgress) { + indicatorId = createProgressIndicator( + 'determinate', + allScripts.length, + `Exporting scripts (${state.getRealm()})` + ); + } for (const s of allScripts) { + if (indicatorId) { + updateProgressIndicator(indicatorId, `Exporting script ${s.name}`); + } if ( !(await configManagerExportScript({ ss: s }, justContent, justConfig)) ) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting scripts', + 'fail' + ); + } return false; } } + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Exported scripts'); + } } else { verboseMessage(`There are no scripts in the realm "${state.getRealm()}"`); } return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting scripts', 'fail'); + } printError(error); return false; } @@ -190,8 +221,26 @@ export async function configManagerExportScriptsAll( scriptType: string = null, language: string = 'JAVASCRIPT' ): Promise { + let indicatorId: string | undefined; try { - for (const realm of await realmList()) { + const realms = await realmList(); + const exportableRealms = realms.filter((realm) => { + if ( + realm === '/' && + state.getDeploymentType() === + frodo.utils.constants.CLOUD_DEPLOYMENT_TYPE_KEY + ) + return false; + return true; + }); + + indicatorId = createProgressIndicator( + 'determinate', + exportableRealms.length, + 'Exporting scripts' + ); + + for (const realm of exportableRealms) { if ( realm === '/' && state.getDeploymentType() === @@ -200,6 +249,10 @@ export async function configManagerExportScriptsAll( continue; state.setRealm(realm); + updateProgressIndicator( + indicatorId, + `Exporting scripts (${state.getRealm()})` + ); verboseMessage(`\n${state.getRealm()} realm:`); if ( !(await configManagerExportScriptsRealms( @@ -207,14 +260,20 @@ export async function configManagerExportScriptsAll( justContent, justConfig, scriptType, - language + language, + false )) ) { + stopProgressIndicator(indicatorId, 'Error exporting scripts', 'fail'); return false; } } + stopProgressIndicator(indicatorId, 'Exported scripts'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting scripts', 'fail'); + } printError(error); return false; } diff --git a/src/configManagerOps/FrConfigSecretMappingsOps.ts b/src/configManagerOps/FrConfigSecretMappingsOps.ts index 0aaf29f90..9a8050a2b 100644 --- a/src/configManagerOps/FrConfigSecretMappingsOps.ts +++ b/src/configManagerOps/FrConfigSecretMappingsOps.ts @@ -1,6 +1,11 @@ import { frodo, state } from '@rockcarver/frodo-lib'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; import { realmList } from '../utils/FrConfig'; const { saveJsonToFile, getFilePath } = frodo.utils; @@ -10,37 +15,89 @@ export async function configManagerExportSecretMappings( name?, realm? ): Promise { + let indicatorId: string | undefined; try { if (realm && realm !== '__default__realm__') { + indicatorId = createProgressIndicator( + 'indeterminate', + 0, + `Exporting secret mappings (${realm})` + ); const readData = await readSecretStoreMappings( 'ESV', 'GoogleSecretManagerSecretStoreProvider', false ); - processSecretMappings(readData, `realms/${realm}/secret-mappings`, name); + const mappingsToExport = readData.filter((mapping) => { + if (!name) return true; + return mapping._id === name || (mapping.aliases || []).includes(name); + }); + stopProgressIndicator(indicatorId); + indicatorId = createProgressIndicator( + 'determinate', + mappingsToExport.length, + `Exporting secret mappings (${realm})` + ); + await processSecretMappings( + mappingsToExport, + `realms/${realm}/secret-mappings`, + name, + indicatorId + ); + stopProgressIndicator( + indicatorId, + `${mappingsToExport.length} secret mappings exported` + ); } else { - for (const realm of await realmList()) { - state.setRealm(realm); + const realms = await realmList(); + indicatorId = createProgressIndicator( + 'determinate', + realms.length, + 'Exporting secret mappings' + ); + for (const realmName of realms) { + state.setRealm(realmName); + updateProgressIndicator( + indicatorId, + `Exporting secret mappings (${realmName})` + ); const readData = await readSecretStoreMappings( 'ESV', 'GoogleSecretManagerSecretStoreProvider', false ); - processSecretMappings( - readData, - `realms/${realm}/secret-mappings`, + const mappingsToExport = readData.filter((mapping) => { + if (!name) return true; + return mapping._id === name || (mapping.aliases || []).includes(name); + }); + await processSecretMappings( + mappingsToExport, + `realms/${realmName}/secret-mappings`, name ); } + stopProgressIndicator(indicatorId, 'Exported secret mappings'); } return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting secret mappings', + 'fail' + ); + } printError(error, `Error exporting config entity endpoints`); } return false; } -async function processSecretMappings(mappings, targetDir, name) { +async function processSecretMappings( + mappings, + targetDir, + name, + indicatorId?: string +) { try { for (const mapping of mappings) { if ( @@ -52,6 +109,12 @@ async function processSecretMappings(mappings, targetDir, name) { } const fileName = `${targetDir}/${mapping._id}.json`; saveJsonToFile(mapping, getFilePath(fileName, true), false, true); + if (indicatorId) { + updateProgressIndicator( + indicatorId, + `Exporting secret mapping ${mapping._id}` + ); + } } } catch (err) { printError(err); diff --git a/src/configManagerOps/FrConfigSecretOps.ts b/src/configManagerOps/FrConfigSecretOps.ts index 231f9fa09..4d73720fd 100644 --- a/src/configManagerOps/FrConfigSecretOps.ts +++ b/src/configManagerOps/FrConfigSecretOps.ts @@ -75,7 +75,7 @@ export async function configManagerExportSecrets( ); updateProgressIndicator(indicatorId, `Exported secret ${secret._id}`); } - stopProgressIndicator(indicatorId, `${secrets.length} secrets exported.`); + stopProgressIndicator(indicatorId, `${secrets.length} secrets exported`); return true; } catch (error) { stopProgressIndicator( diff --git a/src/configManagerOps/FrConfigServiceObjectsOps.ts b/src/configManagerOps/FrConfigServiceObjectsOps.ts index 440583444..f94c16bff 100644 --- a/src/configManagerOps/FrConfigServiceObjectsOps.ts +++ b/src/configManagerOps/FrConfigServiceObjectsOps.ts @@ -1,7 +1,12 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; const { getFilePath, saveJsonToFile } = frodo.utils; const { queryManagedObjects, updateManagedObject } = frodo.idm.managed; @@ -13,10 +18,24 @@ const { queryManagedObjects, updateManagedObject } = frodo.idm.managed; export async function configManagerExportServiceObjectsFromFile( file ): Promise { + let indicatorId: string | undefined; try { const objects = JSON.parse(fs.readFileSync(file, 'utf8')); + const totalObjects = Object.values(objects).reduce( + (acc: number, list: any) => acc + (Array.isArray(list) ? list.length : 0), + 0 + ); + indicatorId = createProgressIndicator( + 'determinate', + totalObjects, + 'Exporting service objects' + ); for (const objectType of Object.keys(objects)) { for (const object of objects[objectType]) { + updateProgressIndicator( + indicatorId, + `Exporting service object ${objectType}/${object.searchValue}` + ); const queryFilter = `${object.searchField} eq "${object.searchValue}"`; const queryResult = await queryManagedObjects( objectType, @@ -28,12 +47,22 @@ export async function configManagerExportServiceObjectsFromFile( `Unexpected result from search: ${queryResult.length} entries found for ${objectType} - ${object.searchValue}` ); printError(error); + stopProgressIndicator( + indicatorId, + 'Error exporting service objects', + 'fail' + ); return false; } else if (queryResult.length == 0) { const error = new Error( `No result from search: ${queryResult.length} entries found for ${objectType} - ${object.searchValue}` ); printError(error); + stopProgressIndicator( + indicatorId, + 'Error exporting service objects', + 'fail' + ); return false; } else { const result = queryResult[0]; @@ -63,8 +92,19 @@ export async function configManagerExportServiceObjectsFromFile( } } } + stopProgressIndicator( + indicatorId, + `${totalObjects} service objects exported` + ); return true; } catch (err) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting service objects', + 'fail' + ); + } printError(err, `Error exporting service-objects`); } return false; diff --git a/src/configManagerOps/FrConfigServiceOps.ts b/src/configManagerOps/FrConfigServiceOps.ts index a10bd4958..77c3332c1 100644 --- a/src/configManagerOps/FrConfigServiceOps.ts +++ b/src/configManagerOps/FrConfigServiceOps.ts @@ -1,6 +1,11 @@ import { frodo, state } from '@rockcarver/frodo-lib'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; import { realmList } from '../utils/FrConfig'; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -14,25 +19,49 @@ export async function configManagerExportServices( realm?, name? ): Promise { + let indicatorId: string | undefined; try { if (realm && realm !== '__default__realm__') { const services = await getFullServices(false); - processServices(services, realm, name); + const filteredServices = name + ? services.filter((s) => s._type._id === name) + : services; + indicatorId = createProgressIndicator( + 'determinate', + filteredServices.length, + `Exporting services (${realm})` + ); + await processServices(filteredServices, realm, name, indicatorId); + stopProgressIndicator(indicatorId, 'Exported services'); } else { - for (const realm of await realmList()) { - state.setRealm(realm); + const realms = await realmList(); + indicatorId = createProgressIndicator( + 'determinate', + realms.length, + 'Exporting services' + ); + for (const realmName of realms) { + state.setRealm(realmName); + updateProgressIndicator( + indicatorId, + `Exporting services (${realmName})` + ); const services = await getFullServices(false); - processServices(services, realm, name); + await processServices(services, realmName, name); } + stopProgressIndicator(indicatorId, 'Exported services'); } return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting services', 'fail'); + } printError(error); } return false; } -async function processServices(services, realm, name) { +async function processServices(services, realm, name, indicatorId?: string) { const fileDir = `realms/${realm}/services`; for (const service of services) { if (name && name !== service._type._id) { @@ -59,5 +88,11 @@ async function processServices(services, realm, name) { false, true ); + if (indicatorId) { + updateProgressIndicator( + indicatorId, + `Exporting service ${service._type._id}` + ); + } } } diff --git a/src/configManagerOps/FrConfigTermsAndConditionsOps.ts b/src/configManagerOps/FrConfigTermsAndConditionsOps.ts index 1079738c2..c981cb14c 100644 --- a/src/configManagerOps/FrConfigTermsAndConditionsOps.ts +++ b/src/configManagerOps/FrConfigTermsAndConditionsOps.ts @@ -2,7 +2,11 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; import { extractFrConfigDataToFile } from '../utils/Config'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { saveJsonToFile, getFilePath } = frodo.utils; const { readConfigEntity, importConfigEntities } = frodo.idm.config; @@ -12,7 +16,13 @@ const { readConfigEntity, importConfigEntities } = frodo.idm.config; * @returns {Promise} true if successful, false otherwise */ export async function configManagerExportTermsAndConditions(): Promise { + let indicatorId: string | undefined; try { + indicatorId = createProgressIndicator( + 'indeterminate', + 0, + 'Exporting terms and conditions' + ); const exportData = (await readConfigEntity('selfservice.terms')) as any; for (const version of exportData.versions) { for (const [language, text] of Object.entries( @@ -32,8 +42,16 @@ export async function configManagerExportTermsAndConditions(): Promise getFilePath('terms-conditions/terms-conditions.json', true), false ); + stopProgressIndicator(indicatorId, 'Exported terms and conditions'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting terms and conditions', + 'fail' + ); + } printError(error); return false; } diff --git a/src/configManagerOps/FrConfigThemeOps.ts b/src/configManagerOps/FrConfigThemeOps.ts index cfadc1f52..2d0d65ec2 100644 --- a/src/configManagerOps/FrConfigThemeOps.ts +++ b/src/configManagerOps/FrConfigThemeOps.ts @@ -2,7 +2,13 @@ import { frodo, state } from '@rockcarver/frodo-lib'; import { ThemeSkeleton } from '@rockcarver/frodo-lib/types/ops/ThemeOps'; import fs from 'fs'; -import { printError, printMessage } from '../utils/Console'; +import { + createProgressIndicator, + printError, + printMessage, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; import { decodeOrNot } from '../utils/FrConfig'; const { saveJsonToFile, getFilePath } = frodo.utils; @@ -63,11 +69,18 @@ function extractHtmlFields(theme: ThemeSkeleton, themePath: string): void { } export async function configManagerExportThemes(): Promise { + let indicatorId: string | undefined; try { const realms = await readRealms(); - for (const realm of realms) { + const exportableRealms = realms.filter((realm) => realm.name !== '/'); + indicatorId = createProgressIndicator( + 'determinate', + exportableRealms.length, + 'Exporting themes' + ); + for (const realm of exportableRealms) { // fr-config-manager doesn't support root themes - if (realm.name === '/') continue; + updateProgressIndicator(indicatorId, `Exporting themes (${realm.name})`); state.setRealm(realm.name); const themes = await readThemes(); const exportDir = getFilePath(`realms/${realm.name}/themes`, true); @@ -79,8 +92,12 @@ export async function configManagerExportThemes(): Promise { saveJsonToFile(theme, `${themeDir}/${theme.name}.json`, false); } } + stopProgressIndicator(indicatorId, 'Exported themes'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator(indicatorId, 'Error exporting themes', 'fail'); + } printError(error); return false; } diff --git a/src/configManagerOps/FrConfigUiConfigOps.ts b/src/configManagerOps/FrConfigUiConfigOps.ts index c2aecb87f..d45bf6b45 100644 --- a/src/configManagerOps/FrConfigUiConfigOps.ts +++ b/src/configManagerOps/FrConfigUiConfigOps.ts @@ -2,7 +2,11 @@ import { frodo } from '@rockcarver/frodo-lib'; import fs from 'fs'; import { getIdmImportExportOptions } from '../ops/IdmOps'; -import { printError } from '../utils/Console'; +import { + createProgressIndicator, + printError, + stopProgressIndicator, +} from '../utils/Console'; const { exportConfigEntity, importConfigEntities } = frodo.idm.config; const { getFilePath, saveJsonToFile } = frodo.utils; @@ -15,8 +19,14 @@ const { getFilePath, saveJsonToFile } = frodo.utils; export async function configManagerExportUiConfig( envFile?: string ): Promise { + let indicatorId: string | undefined; try { const options = getIdmImportExportOptions(undefined, envFile); + indicatorId = createProgressIndicator( + 'indeterminate', + 0, + 'Exporting ui configuration' + ); const exportData = ( await exportConfigEntity('ui/configuration', { envReplaceParams: options.envReplaceParams, @@ -29,8 +39,16 @@ export async function configManagerExportUiConfig( getFilePath('ui/ui-configuration.json', true), false ); + stopProgressIndicator(indicatorId, 'Exported ui configuration'); return true; } catch (error) { + if (indicatorId) { + stopProgressIndicator( + indicatorId, + 'Error exporting ui configuration', + 'fail' + ); + } printError(error, `Error exporting config entity ui-configuration`); } return false; diff --git a/src/configManagerOps/FrConfigVariableOps.ts b/src/configManagerOps/FrConfigVariableOps.ts index b4ed54637..f0b7a7f9e 100644 --- a/src/configManagerOps/FrConfigVariableOps.ts +++ b/src/configManagerOps/FrConfigVariableOps.ts @@ -38,7 +38,7 @@ export async function configManagerExportVariables(): Promise { return false; } try { - const indicatorId = createProgressIndicator( + indicatorId = createProgressIndicator( 'determinate', variableList.length, 'Exporting variables' @@ -66,7 +66,9 @@ export async function configManagerExportVariables(): Promise { ); return true; } catch (error) { - stopProgressIndicator(indicatorId, `Error exporting variables`); + if (indicatorId) { + stopProgressIndicator(indicatorId, `Error exporting variables`, 'fail'); + } printError(error); } return false;