salesforce-alm
Version:
This package contains tools, and APIs, for an improved salesforce.com developer experience.
216 lines (214 loc) • 10.7 kB
JavaScript
"use strict";
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SandboxOrg = void 0;
const events_1 = require("events");
const core_1 = require("@salesforce/core");
const core_2 = require("@salesforce/core");
const kit_1 = require("@salesforce/kit");
const sandboxOrgConfig_1 = require("@salesforce/core/lib/config/sandboxOrgConfig");
const ConfigApi = require("../../../lib/core/configApi");
const sandboxConstants_1 = require("./sandboxConstants");
const sandboxOrgApi_1 = require("./sandboxOrgApi");
core_1.Messages.importMessagesDirectory(__dirname);
class SandboxOrg extends events_1.EventEmitter {
constructor(prodOrg, wait, logger, userSuppliedClientId) {
super();
this.prodOrg = prodOrg;
this.wait = wait;
this.logger = logger;
this.userSuppliedClientId = userSuppliedClientId;
this.api = sandboxOrgApi_1.SandboxOrgApi.getInstance(prodOrg, logger);
}
static getInstance(prodOrg, wait, logger, clientId) {
return new SandboxOrg(prodOrg, wait, logger, clientId);
}
/**
*
* @param sandboxReq - the sandbox creation request object
* @param sandboxName
*/
async cloneSandbox(sandboxReq, sandboxName) {
if (sandboxName) {
sandboxReq.SourceId = await this.api.querySandboxInfoIdBySandboxName(sandboxName);
this.logger.debug('Clone sandbox sourceId %s', sandboxReq.SourceId);
}
return this.createSandbox(sandboxReq);
}
/**
*
* @param masterProdOrg - the production org that is authed... the sandbox is created from this org
* @param sandboxReq - the sandbox creation request object
* @param maxPollingRetries - calculated based on wait and polling interval
* @param logapi
*/
async createSandbox(sandboxReq) {
const sandboxProcessObj = await this.api.createSandbox(sandboxReq);
return await this.authWithRetries(sandboxProcessObj);
}
async authWithRetriesByName(sandboxProcessName) {
return this.authWithRetries(await this.api.queryLatestSandboxProcessBySandboxName(sandboxProcessName));
}
async authWithRetries(sandboxProcessObj) {
const maxPollingRetries = this.getMaxPollingRetries();
this.logger.debug('AuthWithRetries sandboxProcessObj %s, maxPollingRetries %i', sandboxProcessObj, maxPollingRetries);
return await this.pollStatusAndAuth(sandboxProcessObj, maxPollingRetries, maxPollingRetries > 0);
}
getMaxPollingRetries() {
return this.wait
? this.wait.seconds / sandboxConstants_1.SandboxConstants.DEFAULT_POLL_INTERVAL.seconds
: sandboxConstants_1.SandboxConstants.DEFAULT_MAX_RETRIES;
}
async getAuthInfoFields() {
if (this.userSuppliedClientId) {
// give out warning we do not support -i flag for the command
this.emit(sandboxConstants_1.SandboxEventNames.EVENT_CLIENTID_NOTSUPPORT, this.userSuppliedClientId);
}
else {
// return the prod org auth file client id
// eslint-disable-next-line @typescript-eslint/await-thenable
return await this.prodOrg.getConnection().getAuthInfoFields();
}
}
async writeAuthFile(sandboxProcessObj, sandboxRes) {
this.logger.debug('writeAuthFile sandboxProcessObj: %s, sandboxRes: %s', sandboxProcessObj, sandboxRes);
if (sandboxRes.authUserName) {
const authFields = await this.getAuthInfoFields();
this.logger.debug('Result from getAuthInfoFields: AuthFields %s', authFields);
// let's do headless auth via jwt (if we have privateKey) or web auth
const oauth2Options = {
loginUrl: sandboxRes.loginUrl,
instanceUrl: sandboxRes.instanceUrl,
username: sandboxRes.authUserName,
};
// If we don't have a privateKey then we assume it's web auth.
if (!authFields.privateKey) {
const config = new ConfigApi.Config();
oauth2Options.redirectUri = config.getOauthCallbackUrl();
oauth2Options.authCode = sandboxRes.authCode;
}
const authInfo = await core_1.AuthInfo.create({
username: sandboxRes.authUserName,
oauth2Options,
parentUsername: authFields.username,
});
await authInfo.save();
const sandboxOrg = await core_1.Org.create({ aliasOrUsername: authInfo.getUsername() });
await sandboxOrg.setSandboxOrgConfigField(sandboxOrgConfig_1.SandboxOrgConfig.Fields.PROD_ORG_USERNAME, authFields.username);
this.emit(sandboxConstants_1.SandboxEventNames.EVENT_RESULT, {
sandboxProcessObj,
sandboxRes,
});
}
else {
// no authed sandbox user, error
throw core_2.SfdxError.create(new core_2.SfdxErrorConfig('salesforce-alm', 'org', 'missingAuthUsername', [sandboxProcessObj.SandboxName]));
}
}
async validateSandboxCompleteAndGetAuthenticationInfo(sandboxProcessObj) {
this.logger.debug('validateSandboxCompleteAndGetAuthenticationInfo called with SandboxProcessObject %s', sandboxProcessObj);
const endDate = sandboxProcessObj.EndDate;
let result = null;
if (endDate) {
try {
// call server side /sandboxAuth API to auth the sandbox org user with the connected app
const config = new ConfigApi.Config();
const authFields = await this.getAuthInfoFields();
const sandboxReq = new sandboxOrgApi_1.SandboxUserAuthRequest();
sandboxReq.clientId = authFields.clientId;
sandboxReq.callbackUrl = config.getOauthCallbackUrl();
sandboxReq.sandboxName = sandboxProcessObj.SandboxName;
this.logger.debug('Calling sandboxAuth with SandboxUserAuthRequest %s', sandboxReq);
result = await this.api.sandboxAuth(sandboxReq);
this.logger.debug('Result of calling sandboxAuth %s', result);
}
catch (err) {
// THere are cases where the endDate is set before the sandbox has actually completed.
// In that case, the sandboxAuth call will throw a specific exception.
// TODO when we fix the SandboxProcess.Status field to be a proper enum, remove extra checks
if (err.name == sandboxConstants_1.SandboxConstants.SANDBOX_INCOMPLETE_EXCEPTION_MESSAGE) {
this.logger.debug('Error while authenticating the user %s', err.toString());
}
else {
// If it fails for any unexpected reason, just pass that through
throw err;
}
}
}
return result;
}
/**
*
* @param sandboxProcessObj - 0GR000xxx, latest non deleted sandbox process id we got from the sandbox creation
* @param retries : number - calculated based on wait and polling interval
* @param shouldPoll : boolean - used to determine if the initial call to the recursive function is intended to poll
*/
async pollStatusAndAuth(sandboxProcessObj, retries, shouldPoll) {
this.logger.debug('PollStatusAndAuth called with SandboxProcessObject %s, retries %1', sandboxProcessObj, retries);
let pollFinished = false;
let waitingOnAuth = false;
const response = await this.validateSandboxCompleteAndGetAuthenticationInfo(sandboxProcessObj);
if (response) {
try {
await this.writeAuthFile(sandboxProcessObj, response);
pollFinished = true;
}
catch (err) {
this.logger.debug('Exception while calling writeAuthFile %s', err);
// This is a really gross way to find out if the error is the expected "JWT can't auth user because it hasn't been replicated" exception
// but I couldn't think of a better way because the exception is so sparsely populated (no stack trace, no particular exception type)
// -wm
if (err.name == 'JWTAuthError' && err.stack.includes("user hasn't approved")) {
waitingOnAuth = true;
}
else {
throw err;
}
}
}
if (!pollFinished) {
if (retries > 0) {
this.emit(sandboxConstants_1.SandboxEventNames.EVENT_STATUS, {
sandboxProcessObj,
interval: sandboxConstants_1.SandboxConstants.DEFAULT_POLL_INTERVAL.seconds,
retries,
waitingOnAuth,
});
await kit_1.sleep(sandboxConstants_1.SandboxConstants.DEFAULT_POLL_INTERVAL);
const polledSandboxProcessObj = await this.api.querySandboxProcessById(sandboxProcessObj.Id);
return this.pollStatusAndAuth(polledSandboxProcessObj, retries - 1, shouldPoll);
}
else {
if (shouldPoll) {
// timed out on retries
throw core_2.SfdxError.create(new core_2.SfdxErrorConfig('salesforce-alm', 'org', 'pollingTimeout', [sandboxProcessObj.Status]));
}
else {
// The user didn't want us to poll, so simply return the status
// simply report status and exit
this.emit(sandboxConstants_1.SandboxEventNames.EVENT_ASYNCRESULT, { sandboxProcessObj });
}
}
}
return sandboxProcessObj;
}
/**
*
* @param masterProdOrg - the production org that is authed... the sandbox is created from this org
* @param sandboxReq - the sandbox creation request object
* @param maxPollingRetries - calculated based on wait and polling interval
* @param logapi
*/
async deleteSandbox(sandboxOrgId) {
const shortId = core_1.sfdc.trimTo15(sandboxOrgId);
const sandboxProcessObject = await this.api.querySandboxProcessBySandboxOrgId(shortId);
await this.api.deleteSandbox(sandboxProcessObject.SandboxInfoId);
}
}
exports.SandboxOrg = SandboxOrg;
//# sourceMappingURL=sandboxOrg.js.map