From c9718b65137df63dbaaa63bd543eabad418c0c7c Mon Sep 17 00:00:00 2001 From: Alan Ly Date: Mon, 15 Mar 2021 21:01:39 +1100 Subject: [PATCH 1/2] Promote source and data packages All packages must now be promoted to be deployed to production, when using the orchestrator --- packages/core/src/PackageMetadata.ts | 1 + packages/core/src/org/OrgDetails.ts | 2 +- .../sfpowerscripts-cli/messages/promote.json | 1 + packages/sfpowerscripts-cli/package-lock.json | 15 +++- packages/sfpowerscripts-cli/package.json | 3 +- .../sfpowerscripts/orchestrator/promote.ts | 88 ++++++++++++++++--- .../sfpowerscripts/orchestrator/publish.ts | 3 +- .../src/impl/deploy/DeployImpl.ts | 18 +++- 8 files changed, 110 insertions(+), 21 deletions(-) diff --git a/packages/core/src/PackageMetadata.ts b/packages/core/src/PackageMetadata.ts index 7301b3a21..74dfa39a5 100644 --- a/packages/core/src/PackageMetadata.ts +++ b/packages/core/src/PackageMetadata.ts @@ -18,6 +18,7 @@ export default interface PackageMetadata { apexTestClassses?:string[]; isTriggerAllTests?:boolean; isProfilesFound?:boolean; + isPromoted?: boolean; tag?:string; isDependencyValidated?:boolean; destructiveChanges?:any; diff --git a/packages/core/src/org/OrgDetails.ts b/packages/core/src/org/OrgDetails.ts index a62bdcc29..f1fb8e8ff 100644 --- a/packages/core/src/org/OrgDetails.ts +++ b/packages/core/src/org/OrgDetails.ts @@ -4,7 +4,7 @@ const retry = require("async-retry"); export default class OrgDetails { - public static async getOrgDetails(username: string): Promise { + public static async getOrgDetails(username: string): Promise { return await retry( async bail => { diff --git a/packages/sfpowerscripts-cli/messages/promote.json b/packages/sfpowerscripts-cli/messages/promote.json index 15f089cc3..b29f47053 100644 --- a/packages/sfpowerscripts-cli/messages/promote.json +++ b/packages/sfpowerscripts-cli/messages/promote.json @@ -1,5 +1,6 @@ { "commandDescription": "Promotes validated unlocked packages with code coverage greater than 75%", "artifactDirectoryFlagDescription": "The directory where artifacts are located", + "outputDirectoryFlagDescription": "Output directory where promoted artifacts are written", "devhubAliasFlagDescription": "Provide the alias of the devhub previously authenticated, default value is HubOrg if using the Authenticate Devhub task" } diff --git a/packages/sfpowerscripts-cli/package-lock.json b/packages/sfpowerscripts-cli/package-lock.json index d141e7e12..f3f98a5e3 100644 --- a/packages/sfpowerscripts-cli/package-lock.json +++ b/packages/sfpowerscripts-cli/package-lock.json @@ -2090,6 +2090,15 @@ "defer-to-connect": "^2.0.0" } }, + "@types/adm-zip": { + "version": "0.4.33", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.33.tgz", + "integrity": "sha512-WM0DCWFLjXtddl0fu0+iN2ZF+qz8RF9RddG5OSy/S90AQz01Fu8lHn/3oTIZDxvG8gVcnBLAHMHOdBLbV6m6Mw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/babel__core": { "version": "7.1.12", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", @@ -2357,9 +2366,9 @@ "dev": true }, "adm-zip": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.1.tgz", - "integrity": "sha512-a5ABmIFUJ9OxHV5zrXM9Q41JzpRIflFtdgpL4UQM9DsTHHxQzPRaeyAdnMW7kxL0NRWm/NHafJdj6pO+ty7L2g==" + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.4.tgz", + "integrity": "sha512-GMQg1a1cAegh+/EgWbz+XHZrwB467iB/IgtToldvxs7Xa5Br8mPmvCeRfY/Un2fLzrlIPt6Yu7Cej+8Ut9TGPg==" }, "agent-base": { "version": "4.3.0", diff --git a/packages/sfpowerscripts-cli/package.json b/packages/sfpowerscripts-cli/package.json index 7aebf358c..b4c05e185 100644 --- a/packages/sfpowerscripts-cli/package.json +++ b/packages/sfpowerscripts-cli/package.json @@ -14,7 +14,7 @@ "@oclif/errors": "^1", "@salesforce/command": "^2", "@salesforce/core": "^2", - "adm-zip": "^0.5.0", + "adm-zip": "^0.5.4", "async-retry": "^1.3.1", "bottleneck": "^2.19.5", "cli-table": "^0.3.4", @@ -34,6 +34,7 @@ "ts-node": "^9.0.0" }, "devDependencies": { + "@types/adm-zip": "^0.4.33", "@types/jest": "^26.0.20", "jest": "^26.6.3", "ts-jest": "^26.5.0", diff --git a/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/promote.ts b/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/promote.ts index 5288b8117..28a0fac1d 100644 --- a/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/promote.ts +++ b/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/promote.ts @@ -5,6 +5,8 @@ import * as fs from "fs-extra" import PromoteUnlockedPackageImpl from "@dxatscale/sfpowerscripts.core/lib/sfdxwrappers/PromoteUnlockedPackageImpl" import ArtifactFilePathFetcher from "@dxatscale/sfpowerscripts.core/lib/artifacts/ArtifactFilePathFetcher"; import PackageMetadata from "@dxatscale/sfpowerscripts.core/lib/PackageMetadata"; +import AdmZip = require("adm-zip"); +import path = require("path"); Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@dxatscale/sfpowerscripts', 'promote'); @@ -21,14 +23,28 @@ export default class Promote extends SfpowerscriptsCommand { protected static requiresDevhubUsername = false; protected static flagsConfig = { - artifactdir: flags.directory({required: true, char: 'd', description: messages.getMessage('artifactDirectoryFlagDescription'), default: 'artifacts'}), - devhubalias: flags.string({char: 'v', description: messages.getMessage('devhubAliasFlagDescription'), default: 'HubOrg'}), + artifactdir: flags.directory({ + required: true, char: 'd', + description: messages.getMessage('artifactDirectoryFlagDescription'), + default: 'artifacts' + }), + outputdir: flags.directory({ + required: true, + char: 'o', + description: messages.getMessage('outputDirectoryFlagDescription') + }), + devhubalias: flags.string({ + char: 'v', + description: messages.getMessage('devhubAliasFlagDescription'), + default: 'HubOrg' + }) }; public async execute(){ + if (this.flags.outputdir === this.flags.artifactdir) + throw new Error("--outputdir flag cannot be the same as --artifactdir flag"); - console.log("-----------sfpowerscripts orchestrator ------------------"); console.log("command: promote"); console.log("---------------------------------------------------------"); @@ -44,6 +60,8 @@ export default class Promote extends SfpowerscriptsCommand { throw new Error(`No artifacts found at ${this.flags.artifactdir}`); } + fs.mkdirpSync(this.flags.outputdir); + let result: boolean = true; let promotedPackages: string[] = []; for (let artifact of artifacts) { @@ -51,24 +69,47 @@ export default class Promote extends SfpowerscriptsCommand { fs.readFileSync(artifact.packageMetadataFilePath, 'utf8') ); - if (packageMetadata.package_type === "unlocked") { - try { + try { + if (packageMetadata.package_type === "unlocked") { let promoteUnlockedPackageImpl = new PromoteUnlockedPackageImpl( artifact.sourceDirectoryPath, packageMetadata.package_version_id, this.flags.devhubalias ); await promoteUnlockedPackageImpl.exec(); - - promotedPackages.push(packageMetadata.package_name); - } catch (err) { - result = false; - - unpromotedPackages.push({ - name: packageMetadata.package_name, - error: err.message - }); } + + packageMetadata.isPromoted = true; + fs.writeFileSync( + artifact.packageMetadataFilePath, + JSON.stringify(packageMetadata, null, 4) + ); + + let artifactRootDir: string = path.dirname(artifact.sourceDirectoryPath); + let zip = new AdmZip(); + zip.addLocalFolder( + artifactRootDir, + path.basename(artifactRootDir) + ); + + + let zipArtifactFilepath: string = path.resolve( + this.flags.outputdir, + packageMetadata.package_name + `_sfpowerscripts_artifact_` + + this.substituteBuildNumberWithPreRelease(packageMetadata.package_version_number) + + `.zip` + ); + zip.writeZip(zipArtifactFilepath); + + + promotedPackages.push(packageMetadata.package_name); + } catch (err) { + result = false; + + unpromotedPackages.push({ + name: packageMetadata.package_name, + error: err.message + }); } } console.log(`Promoted packages:`, promotedPackages); @@ -89,4 +130,23 @@ export default class Promote extends SfpowerscriptsCommand { process.exitCode = 1; } } + + private substituteBuildNumberWithPreRelease( + packageVersionNumber: string + ) { + let segments = packageVersionNumber.split("."); + + if (segments.length === 4) { + packageVersionNumber = segments.reduce( + (version, segment, segmentsIdx) => { + if (segmentsIdx === 3) return version + "-" + segment; + else return version + "." + segment; + } + ); + } + + return packageVersionNumber; + } + + } diff --git a/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/publish.ts b/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/publish.ts index 32db06f9f..1b413a2e9 100644 --- a/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/publish.ts +++ b/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/publish.ts @@ -28,7 +28,8 @@ export default class Promote extends SfpowerscriptsCommand { protected static flagsConfig = { artifactdir: flags.directory({ - required: true, char: 'd', + required: true, + char: 'd', description: messages.getMessage('artifactDirectoryFlagDescription'), default: 'artifacts' }), diff --git a/packages/sfpowerscripts-cli/src/impl/deploy/DeployImpl.ts b/packages/sfpowerscripts-cli/src/impl/deploy/DeployImpl.ts index 9934d7d6a..81674efcb 100644 --- a/packages/sfpowerscripts-cli/src/impl/deploy/DeployImpl.ts +++ b/packages/sfpowerscripts-cli/src/impl/deploy/DeployImpl.ts @@ -8,6 +8,7 @@ import InstallSourcePackageImpl from "@dxatscale/sfpowerscripts.core/lib/sfpcomm import InstallDataPackageImpl from "@dxatscale/sfpowerscripts.core/lib/sfpcommands/package/InstallDataPackageImpl"; import ArtifactInstallationStatusChecker from "@dxatscale/sfpowerscripts.core/lib/artifacts/ArtifactInstallationStatusChecker" import InstalledAritfactsFetcher from "@dxatscale/sfpowerscripts.core/lib/artifacts/InstalledAritfactsFetcher" +import OrgDetails from "@dxatscale/sfpowerscripts.core/lib/org/OrgDetails"; import fs = require("fs"); import path = require("path"); @@ -58,6 +59,8 @@ export default class DeployImpl { testFailure: string; error: any; }> { + let orgDetails = await OrgDetails.getOrgDetails(this.props.targetUsername); + let deployed: string[] = []; let failed: string[] = []; @@ -109,7 +112,9 @@ export default class DeployImpl { this.printArtifactVersions(queue,packagesToPackageInfo); } - + if (!orgDetails.IsSandbox) { + this.checkIfPackagesPromoted(queue, packagesToPackageInfo); + } SFPStatsSender.logCount("deploy.scheduled",this.props.tags); SFPStatsSender.logGauge( @@ -266,6 +271,17 @@ export default class DeployImpl { } + private checkIfPackagesPromoted(queue: any[], packagesToPackageInfo: { [p: string]: PackageInfo; }) { + let unpromotedPackages: string[] = []; + queue.forEach((pkg) => { + if (!packagesToPackageInfo[pkg.package].packageMetadata.isPromoted) + unpromotedPackages.push(pkg.package); + }); + + if (unpromotedPackages.length > 0) + throw new Error(`Packages must be promoted for deployments to production org: ${unpromotedPackages}`); + } + private printArtifactVersionsWhenSkipped(queue:any[],packagesToPackageInfo:{[p: string]: PackageInfo},isBaselinOrgModeActivated:boolean) { this.printOpenLoggingGroup(`Full Deployment Breakdown`); let maxTable = new Table({ From 7c6fe6b0952d1f485da80fe675c797f2fe10e60e Mon Sep 17 00:00:00 2001 From: Alan Ly Date: Tue, 16 Mar 2021 09:58:03 +1100 Subject: [PATCH 2/2] Add hidden flag --allowunmpromotedpackages --- packages/sfpowerscripts-cli/messages/deploy.json | 3 ++- .../src/commands/sfpowerscripts/orchestrator/deploy.ts | 7 ++++++- packages/sfpowerscripts-cli/src/impl/deploy/DeployImpl.ts | 4 +++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/sfpowerscripts-cli/messages/deploy.json b/packages/sfpowerscripts-cli/messages/deploy.json index bd173b6f9..3cf174b99 100644 --- a/packages/sfpowerscripts-cli/messages/deploy.json +++ b/packages/sfpowerscripts-cli/messages/deploy.json @@ -7,5 +7,6 @@ "tagFlagDescription":"Tag the deploy with a label, useful for identification in metrics", "validateModeFlagDescription": "Enable for validation deployments", "skipIfAlreadyInstalled":"Skip the package installation if the package is already installed in the org", - "baselineorgFlagDescription": "The org against which the package skip should be baselined" + "baselineorgFlagDescription": "The org against which the package skip should be baselined", + "allowUnpromotedPackagesFlagDescription": "Allow un-promoted packages to be installed in production" } diff --git a/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/deploy.ts b/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/deploy.ts index 08debf8db..a0cd0842e 100644 --- a/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/deploy.ts +++ b/packages/sfpowerscripts-cli/src/commands/sfpowerscripts/orchestrator/deploy.ts @@ -58,6 +58,10 @@ export default class Deploy extends SfpowerscriptsCommand { required: false, dependsOn: ['skipifalreadyinstalled'] }), + allowunpromotedpackages: flags.boolean({ + description: messages.getMessage("allowUnpromotedPackagesFlagDescription"), + hidden: true + }) }; public async execute() { @@ -99,7 +103,8 @@ export default class Deploy extends SfpowerscriptsCommand { skipIfPackageInstalled:this.flags.skipifalreadyinstalled, logsGroupSymbol:this.flags.logsgroupsymbol, currentStage:Stage.DEPLOY, - baselineOrg: this.flags.baselineorg + baselineOrg: this.flags.baselineorg, + isCheckIfPackagesPromoted: !this.flags.allowunpromotedpackages } try { diff --git a/packages/sfpowerscripts-cli/src/impl/deploy/DeployImpl.ts b/packages/sfpowerscripts-cli/src/impl/deploy/DeployImpl.ts index 34fe48c41..922b724ec 100644 --- a/packages/sfpowerscripts-cli/src/impl/deploy/DeployImpl.ts +++ b/packages/sfpowerscripts-cli/src/impl/deploy/DeployImpl.ts @@ -48,6 +48,7 @@ export interface DeployProps { packageLogger?: any; currentStage?: Stage; baselineOrg?:string; + isCheckIfPackagesPromoted?: boolean; } export default class DeployImpl { @@ -112,7 +113,8 @@ export default class DeployImpl { } if (!orgDetails.IsSandbox) { - this.checkIfPackagesPromoted(queue, packagesToPackageInfo); + if (this.props.isCheckIfPackagesPromoted) + this.checkIfPackagesPromoted(queue, packagesToPackageInfo); } SFPStatsSender.logCount("deploy.scheduled",this.props.tags);