Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { frodo } from '@rockcarver/frodo-lib';
import { Option } from 'commander';

import { configManagerExportSecrets } from '../../../configManagerOps/FrConfigSecretOps';
import { getTokens } from '../../../ops/AuthenticateOps';
Expand All @@ -22,6 +23,12 @@ export default function setup() {

program
.description('Export secrets.')
.addOption(
new Option(
'-a, --active-only',
'Exports only active secrets, otherwise exports all secrets and their versions.'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should only have it say Export only active secret versions. It's implied that if it's not provided all secret versions will be exported.

)
)
.action(async (host, realm, user, password, options, command) => {
command.handleDefaultArgsAndOpts(
host,
Expand All @@ -34,7 +41,7 @@ export default function setup() {

if (await getTokens(false, true, deploymentTypes)) {
verboseMessage('Exporting secrets');
const outcome = await configManagerExportSecrets(options);
const outcome = await configManagerExportSecrets(options.activeOnly);
if (!outcome) process.exitCode = 1;
}
// unrecognized combination of options or no options
Expand Down
48 changes: 26 additions & 22 deletions src/configManagerOps/FrConfigSecretOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,22 @@ import {
stopProgressIndicator,
updateProgressIndicator,
} from '../utils/Console';
import { esvToEnv } from '../utils/FrConfig';

const { getFilePath, saveJsonToFile } = frodo.utils;
const { readSecrets, exportSecret } = frodo.cloud.secret;
const { readSecrets, exportSecret, readVersionsOfSecret } = frodo.cloud.secret;

/**
* Export all secrets to individual files in fr-config-manager format
* @param {boolean} includeMeta true to include metadata, false otherwise. Default: true
* @param {boolean} includeActiveValues include active value of secret (default: false)
* @param {string} target Host URL of target environment to encrypt secret value for
* @returns {Promise<boolean>} true if successful, false otherwise
*/
type FrConfigSecret = SecretSkeleton & {
valueBase64: string;
};

Comment on lines 16 to 19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this type either, so I would just remove it. If you delete all the places we are using it you shouldn't run into any errors

async function getFrConfigSecrets(): Promise<FrConfigSecret[]> {
const originalSecrets = await readSecrets();
return originalSecrets.map((secret) => ({
...secret,
valueBase64: `\${${secret._id.toUpperCase().replace(/-/g, '_')}}`,
}));
}

/**
* Export all secrets to individual files in fr-config-manager format
* @param {boolean} activeOnly true to export only active secrets, false will export all active or not
* @returns {Promise<boolean>} true if successful, false otherwise
*/
export async function configManagerExportSecrets(
target?: string
activeOnly?: boolean
): Promise<boolean> {
let secrets: FrConfigSecret[] = [];
const spinnerId = createProgressIndicator(
Expand All @@ -41,7 +32,7 @@ export async function configManagerExportSecrets(
`Reading secrets...`
);
try {
secrets = await getFrConfigSecrets();
secrets = (await readSecrets()) as FrConfigSecret[];
secrets.sort((a, b) => a._id.localeCompare(b._id));
stopProgressIndicator(
spinnerId,
Expand All @@ -56,18 +47,31 @@ export async function configManagerExportSecrets(
for (const secret of secrets) {
const exportData: SecretsExportInterface = await exportSecret(
secret._id,
false,
target
false
);
const [secretKey] = Object.keys(exportData.secret);
const fullSecret = exportData.secret[secretKey] as FrConfigSecret;
const cleanSecret = {
const cleanSecret: Partial<SecretSkeleton> = {
_id: fullSecret._id,
description: fullSecret.description,
encoding: fullSecret.encoding,
useInPlaceholders: fullSecret.useInPlaceholders,
valueBase64: `\${${secret._id.toUpperCase().replace(/-/g, '_')}}`,
};
if (activeOnly) {
cleanSecret.valueBase64 = `\${${esvToEnv(secret._id)}}`;
} else {
const versionsResponse = await readVersionsOfSecret(fullSecret._id);
const versions = versionsResponse.filter(
(version) => version.status !== 'DESTROYED'
);
const versionInfo = versions.map((version) => ({
version: version.version,
status: version.status,
valueBase64: `\${${esvToEnv(`${secret._id}_${version.version}`)}}`,
}));
Comment on lines +67 to +71

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about it, I think you should change the version number to be (i + 1).toString() instead of version.version, that way we match fr-config-manager. The reason I think they do it this way is because when pushing the secrets there is a prune flag which can delete old versions. In other words, the version numbers don't really matter, because when you push the versions it will create new ones, so I think having them numbered in order so that it matches fr-config-manager is the best way to do this

cleanSecret.versions = versionInfo;
}

saveJsonToFile(
cleanSecret,
getFilePath(`esvs/secrets/${secret._id}.json`, true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@ exports[`CLI help interface for 'config export' should be expected english 1`] =
[Experimental] Export secrets.

Arguments:
host AM base URL, e.g.: https://cdk.iam.example.com/am. To use a
connection profile, just specify a unique substring or
alias.
realm Realm. Specify realm as '/' for the root realm or 'realm' or
'/parent/child' otherwise. (default: "alpha" for Identity
Cloud tenants, "/" otherwise.)
username Username to login with. Must be an admin user with
appropriate rights to manage authentication journeys/trees.
password Password.
host AM base URL, e.g.: https://cdk.iam.example.com/am. To use a
connection profile, just specify a unique substring or
alias.
realm Realm. Specify realm as '/' for the root realm or 'realm'
or '/parent/child' otherwise. (default: "alpha" for
Identity Cloud tenants, "/" otherwise.)
username Username to login with. Must be an admin user with
appropriate rights to manage authentication journeys/trees.
password Password.

Options:
-h, --help Help
-hh, --help-more Help with all options.
-hhh, --help-all Help with all options, environment variables, and usage
examples.
-a, --active-only Exports only active secrets, otherwise exports all secrets
and their versions.
-h, --help Help
-hh, --help-more Help with all options.
-hhh, --help-all Help with all options, environment variables, and usage
examples.
"
`;
Loading