sfdx-hardis
Version:
Swiss-army-knife Toolbox for Salesforce. Allows you to define a complete CD/CD Pipeline. Orchestrate base commands and assist users with interactive wizards
236 lines (229 loc) âĸ 15.1 kB
JavaScript
/* jscpd:ignore-start */
import { SfCommand, Flags, requiredOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core';
import { Messages, SfError } from '@salesforce/core';
import c from 'chalk';
import fs from 'fs-extra';
import * as path from 'path';
import open from 'open';
import { ensureGitBranch, ensureGitRepository, execCommand, generateSSLCertificate, getCurrentGitBranch, getGitRepoName, git, gitAddCommitPush, uxLog, } from '../../../../common/utils/index.js';
import { prompts } from '../../../../common/utils/prompts.js';
import { CONSTANTS, setInConfigFile } from '../../../../config/index.js';
import { PACKAGE_ROOT_DIR } from '../../../../settings.js';
import { promptOrg } from '../../../../common/utils/orgUtils.js';
import { WebSocketClient } from '../../../../common/websocketClient.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('sfdx-hardis', 'org');
export default class OrgConfigureMonitoring extends SfCommand {
static title = 'Configure org monitoring';
static description = `
## Command Behavior
**Configures the monitoring of a Salesforce org within a dedicated Git repository.**
This command streamlines the setup of continuous monitoring for a Salesforce organization, ensuring that changes and health metrics are tracked and reported. It is designed to be run within a Git repository specifically dedicated to monitoring configurations.
Key functionalities include:
- **Git Repository Validation:** Ensures the current Git repository's name contains "monitoring" to enforce best practices for separating monitoring configurations from deployment sources.
- **Prerequisite Check:** Guides the user to confirm that necessary monitoring prerequisites (CI/CD variables, permissions) are configured on their Git server.
- **Org Selection:** Prompts the user to select or connect to the Salesforce org they wish to monitor.
- **Monitoring Branch Creation:** Creates or checks out a dedicated Git branch (e.g., \`monitoring_yourinstanceurl\`) for the monitoring configuration.
- **SFDX Project Setup:** Initializes an SFDX project structure within the repository if it doesn't already exist, and copies default monitoring files.
- **Configuration File Update:** Updates the local \`.sfdx-hardis.yml\` file with the target org's username and instance URL.
- **SSL Certificate Generation:** Generates an SSL certificate for secure authentication to the monitored org.
- **Automated Commit and Push:** Offers to automatically commit and push the generated configuration files to the remote Git repository.
- **Scheduling Guidance:** Provides instructions and links for scheduling the monitoring job on the Git server.
<details markdown="1">
<summary>Technical explanations</summary>
The command's technical implementation involves a series of Git operations, file system manipulations, and Salesforce CLI interactions:
- **Git Operations:** Utilizes \`ensureGitRepository\`, \`getGitRepoName\`, \`execCommand\` (for \`git add\`, \`git stash\`), \`ensureGitBranch\`, and \`gitAddCommitPush\` to manage the Git repository, branches, and commits.
- **Interactive Prompts:** Employs the \`prompts\` library to interact with the user for confirmations and selections.
- **File System Management:** Uses \`fs-extra\` for copying default monitoring files (\`defaults/monitoring\`) and managing the SFDX project structure.
- **Salesforce CLI Integration:** Calls \`sf project generate\` to create a new SFDX project and uses \`promptOrg\` for Salesforce org authentication and selection.
- **Configuration Management:** Updates the \`.sfdx-hardis.yml\` file using \`setInConfigFile\` to store org-specific monitoring configurations.
- **SSL Certificate Generation:** Leverages \`generateSSLCertificate\` to create the necessary SSL certificates for JWT-based authentication to the Salesforce org.
- **External Tool Integration:** Requires \`openssl\` to be installed on the system for SSL certificate generation.
- **WebSocket Communication:** Uses \`WebSocketClient.sendRunSfdxHardisCommandMessage\` to restart the command in VS Code if the default org changes, and \`WebSocketClient.sendRefreshStatusMessage\` to update the status.
</details>
`;
static examples = ['$ sf hardis:org:configure:monitoring'];
static flags = {
orginstanceurl: Flags.string({
description: 'Org instance url (technical param, do not use manually)',
}),
debug: Flags.boolean({
char: 'd',
default: false,
description: messages.getMessage('debugMode'),
}),
websocket: Flags.string({
description: messages.getMessage('websocket'),
}),
skipauth: Flags.boolean({
description: 'Skip authentication check when a default username is required',
}),
'target-org': requiredOrgFlagWithDeprecations,
};
// Comment this out if your command does not require an org username
static requiresProject = false;
/* jscpd:ignore-end */
static requiresDependencies = ['openssl'];
async run() {
const { flags } = await this.parse(OrgConfigureMonitoring);
// Make sure that we are located in a git repository
await ensureGitRepository();
// Check git repo name is valid (contains monitoring)
const repoName = (await getGitRepoName()) || '';
if (!repoName.includes('monitoring')) {
const confirmMix = await prompts({
type: 'select',
name: 'value',
choices: [
{ title: "Yes, I'm sure because I know what I'm doing, like Roman đ", value: 'yes' },
{ title: 'Mmmmm no, let me create another repo with the word "monitoring" in its name !', value: 'no' },
],
message: c.cyanBright("Are you sure you want to mix monitoring and deployment sources ?"),
description: 'It is recommended to separate monitoring configuration from deployment sources in different repositories',
placeholder: 'Select an option',
});
if (confirmMix.value === 'no') {
throw new SfError('Your git repository name must contain the expression "monitoring"');
}
}
const preRequisitesUrl = `${CONSTANTS.DOC_URL_ROOT}/salesforce-monitoring-config-home/#instructions`;
uxLog("warning", this, c.yellow('Monitoring pre-requisites documentation: ' + c.bold(preRequisitesUrl)));
WebSocketClient.sendReportFileMessage(preRequisitesUrl, 'Monitoring pre-requisites', "docUrl");
// Confirm pre-requisites
const confirmPreRequisites = await prompts({
type: 'select',
name: 'value',
choices: [
{ title: 'đ Yes', value: 'yes' },
{ title: 'âšī¸ No, bring me to the documentation!', value: 'no' },
],
message: c.cyanBright('Did you configure the sfdx-hardis monitoring pre-requisites on your Git server ?'),
description: 'Confirm that you have set up the required CI/CD variables and permissions for monitoring',
placeholder: 'Select an option',
});
if (confirmPreRequisites.value === 'no') {
const msg = 'Please follow the instructions to configure the sfdx-hardis monitoring pre-requisites on your Git server\n' +
preRequisitesUrl;
uxLog("warning", this, c.yellow(msg));
await open(preRequisitesUrl, { wait: true });
return { outputString: msg };
}
// Get current default org
const currentOrgId = flags['target-org']?.getOrgId() || '';
if (flags.orginstanceurl && flags['target-org']?.getConnection()?.instanceUrl === flags.orginstanceurl) {
uxLog("action", this, c.cyan(`Default org ${flags['target-org'].getConnection()?.instanceUrl} is selected, let's configure its monitoring !`));
}
else {
// Select the org that must be monitored
const org = await promptOrg(this, {
devHub: false,
setDefault: true,
scratch: false,
promptMessage: 'Please select or connect to the org that you want to monitor',
defaultOrgUsername: flags['target-org']?.getUsername(),
});
// Restart command so the org is selected as default org (will help to select profiles)
if (currentOrgId !== org.orgId) {
const infoMsg = 'Default org changed. Please restart the same command if VS Code does not do that automatically for you đ';
uxLog("warning", this, c.yellow(infoMsg));
const currentCommand = 'sf ' + this.id + ' ' + this.argv.join(' ') + ' --orginstanceurl ' + org.instanceUrl;
WebSocketClient.sendRunSfdxHardisCommandMessage(currentCommand);
return { outputString: infoMsg };
}
}
// Build monitoring branch name
const branchName = 'monitoring_' +
flags['target-org']
?.getConnection()
.instanceUrl.replace('https://', '')
.replace('.my.salesforce.com', '')
.replace(/\./gm, '_')
.replace(/--/gm, '__')
.replace(/-/gm, '_');
uxLog("action", this, c.cyan(`Handling monitoring git branch ${c.bold(branchName)}...`));
// Checkout branch, or create it if not existing (stash before if necessary)
await execCommand('git add --all', this, { output: true, fail: false });
await execCommand('git stash', this, { output: true, fail: false });
await ensureGitBranch(branchName, { parent: 'main', logAsAction: true });
// Create sfdx project if not existing yet
if (!fs.existsSync('sfdx-project.json')) {
const createCommand = 'sf project generate' + ` --name "sfdx-hardis-monitoring"`;
uxLog("action", this, c.cyan('Creating sfdx-project...'));
await execCommand(createCommand, this, {
output: true,
fail: true,
});
uxLog("action", this, c.cyan('Moving sfdx-project to root...'));
await fs.copy('sfdx-hardis-monitoring', process.cwd(), { overwrite: true });
await fs.remove('sfdx-hardis-monitoring');
// Copying monitoring folder structure
uxLog("other", this, 'Copying default monitoring files...');
if (fs.existsSync('README.md') && fs.readFileSync('README.md', 'utf8').toString().split('\n').length < 5) {
// Remove default README if necessary
await fs.remove('README.md');
}
await fs.copy(path.join(PACKAGE_ROOT_DIR, 'defaults/monitoring', '.'), process.cwd(), { overwrite: true });
}
// Update config file
await setInConfigFile([], {
targetUsername: flags['target-org'].getUsername(),
instanceUrl: flags['target-org'].getConnection().instanceUrl,
}, './.sfdx-hardis.yml');
// Check if current branch is existing on remote origin, and if not suggest to push (with confirmation)
const remoteBranches = await git().listRemote(['--heads', 'origin']);
const branchExistsOnRemote = remoteBranches.includes(`refs/heads/${branchName}`);
if (!branchExistsOnRemote) {
const confirmPushToRemote = await prompts({
type: 'confirm',
initial: true,
message: c.cyanBright(`Branch ${branchName} does not exist on server. Do you want to push it now? (it is harmless but required to configure Azure Pipelines for example)`),
description: 'This will create the branch on remote origin; if you don\'t understand this, just say yes đ',
});
if (confirmPushToRemote.value === true) {
uxLog("action", this, c.cyan(`Pushing branch ${c.bold(branchName)} to git remote server...`));
await gitAddCommitPush({
message: '[sfdx-hardis] Update monitoring configuration',
});
}
}
// Generate SSL certificate (requires openssl to be installed on computer)
await generateSSLCertificate(branchName, './.ssh', this, flags['target-org'].getConnection(), {});
// Confirm & push on server
const confirmPush = await prompts({
type: 'confirm',
name: 'value',
initial: true,
message: c.cyanBright('Do you want sfdx-hardis to save your configuration on server ?'),
description: 'Automatically commit and push the monitoring configuration files to your git repository (recommended)',
});
if (confirmPush.value === true) {
await gitAddCommitPush({
message: '[sfdx-hardis] Update monitoring configuration',
});
uxLog("success", this, c.green('Your configuration for org monitoring is now ready đ'));
}
else {
uxLog("warning", this, c.yellow('Please manually git add, commit and push to the remote repository đ'));
}
const branch = await getCurrentGitBranch();
uxLog("warning", this, c.yellow(`Now you must schedule monitoring to run the job automatically every night on branch ${c.bold(branch)}đ`));
const scheduleMonitoringUrl = `${CONSTANTS.DOC_URL_ROOT}/salesforce-monitoring-config-home/#instructions`;
const msg = 'Please follow the instructions to schedule sfdx-hardis monitoring on your Git server: ' +
c.bold(scheduleMonitoringUrl);
uxLog("warning", this, c.yellow(msg));
WebSocketClient.sendReportFileMessage(scheduleMonitoringUrl, 'Schedule sfdx-hardis monitoring', "actionUrl");
uxLog("warning", this, c.yellow('You can also configure Slack/Teams notifications and Grafana integration to visualize monitoring results more easily.'));
const slackIntegrationUrl = `${CONSTANTS.DOC_URL_ROOT}/salesforce-ci-cd-setup-integration-slack/`;
WebSocketClient.sendReportFileMessage(slackIntegrationUrl, 'Slack integration', "docUrl");
const teamsIntegrationUrl = `${CONSTANTS.DOC_URL_ROOT}/salesforce-ci-cd-setup-integration-ms-teams/`;
WebSocketClient.sendReportFileMessage(teamsIntegrationUrl, 'Teams integration', "docUrl");
const grafanaIntegrationUrl = `${CONSTANTS.DOC_URL_ROOT}/salesforce-ci-cd-setup-integration-api/`;
WebSocketClient.sendReportFileMessage(grafanaIntegrationUrl, 'Grafana integration', "docUrl");
uxLog("log", this, 'Slack integration doc: ' + slackIntegrationUrl);
uxLog("log", this, 'Teams integration doc: ' + teamsIntegrationUrl);
uxLog("log", this, 'Grafana integration doc: ' + grafanaIntegrationUrl);
// Return an object to be displayed with --json
return { outputString: 'Configured branch for authentication' };
}
}
//# sourceMappingURL=monitoring.js.map