UNPKG

salesforce-alm

Version:

This package contains tools, and APIs, for an improved salesforce.com developer experience.

216 lines (214 loc) 10.7 kB
"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