From d7d880f2d4a30f04279a363db39c7d69b5da817f Mon Sep 17 00:00:00 2001 From: Irina Shestak Date: Wed, 12 Aug 2020 12:26:39 +0200 Subject: [PATCH 1/4] export evergreen artifact path to be submitted to barque --- packages/build/src/evergreen.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/build/src/evergreen.ts b/packages/build/src/evergreen.ts index f95693e0ff..2e7b2454df 100644 --- a/packages/build/src/evergreen.ts +++ b/packages/build/src/evergreen.ts @@ -29,9 +29,15 @@ const uploadArtifactToEvergreen = (artifact: string, awsKey: string, awsSecret: Key: key, Body: fs.createReadStream(artifact) }; + console.log(`mongosh: uploading ${artifact} to evergreen bucket:`, BUCKET, key); console.log(`mongosh: artifact download url: https://s3.amazonaws.com/${BUCKET}/${key}`); return upload(uploadParams, s3); }; +const getArtifactPath = (project: string, revision: string, artifact: string): string => { + return `https://s3/amazoonaws.com/${BUCKET}/${project}/${revision}/${path.basename(artifact)}` +}; + export default uploadArtifactToEvergreen; +export { getArtifactPath }; From 9bc3d93d8ff58736117a4f99c1945b4786c9fc12 Mon Sep 17 00:00:00 2001 From: Irina Shestak Date: Thu, 13 Aug 2020 15:28:21 +0200 Subject: [PATCH 2/4] chore(build): upload linux packages to barque. - Introduces `curator` as a tool to upload packaged assets to barque, MongoDB's PPA service. - Uses an already bundled and packaged mongosh binary that was already uploaded to Evergreen's S3 bucket to send to barque. - Adds additional environmental variables to the build process: `BARQUE_USERNAME`, `BARQUE_API_KEY`, `NOTARY_TOKEN`, `NOTARY_KEY_NAME`. These are then used by `curator`. - Adds `repo-config.yml` that is necessary for a barque upload. The config specifies uploads for 3 different distros: `debian10`, `ubuntu1804`, `rhel80`. - We only upload the package to `org` MongoDB repository. - We only upload along with `4.4.0` MongoDB version. - These artifacts are only sent to `barque` when we do a public release (`shouldDoPublicRelease`), i.e. a tagged commit and on main branch. - We fetch the latest curator tarball before proceeding to upload to barque. --- config/repo-config.yml | 89 +++++++++ package-lock.json | 121 ++++++++++++- package.json | 4 + packages/build/README.md | 46 +++-- packages/build/src/barque.spec.ts | 183 +++++++++++++++++++ packages/build/src/barque.ts | 190 ++++++++++++++++++++ packages/build/src/build-and-upload.spec.ts | 53 +++++- packages/build/src/build-and-upload.ts | 12 +- packages/build/src/evergreen.ts | 6 +- packages/build/src/release.ts | 9 +- scripts/import-expansions.js | 2 +- 11 files changed, 678 insertions(+), 37 deletions(-) create mode 100644 config/repo-config.yml create mode 100644 packages/build/src/barque.spec.ts create mode 100644 packages/build/src/barque.ts diff --git a/config/repo-config.yml b/config/repo-config.yml new file mode 100644 index 0000000000..95210522ad --- /dev/null +++ b/config/repo-config.yml @@ -0,0 +1,89 @@ +services: + notary_url: "http://notary-service.build.10gen.cc:5000" + +templates: + deb: + org: | + Origin: mongodb + Label: mongodb + Suite: {{ .CodeName }} + Codename: {{ .CodeName }}/mongodb-org + Architectures: {{ .Architectures }} + Components: {{ .Component }} + Description: MongoDB packages + enterprise: | + Origin: mongodb + Label: mongodb + Suite: {{ .CodeName }} + Codename: {{ .CodeName }}/mongodb-enterprise + Architectures: {{ .Architectures }} + Components: {{ .Component }} + Description: MongoDB packages + index_page: | + + + + {{ .Title }} + + + + + + + {{ range $fn := .Files }} + + {{ end }} + + +
+

{{ .Title }}

+
+
+
+ Parent Directory +
+ {{ $fn }} +
+
+
+
{{ .RepoName }}
+
+ + +repos: + +#################### +# +# Community Repos: +# +#################### + + - name: debian10 + type: deb + code_name: "buster" + bucket: repo.mongodb.org + edition: org + component: main + architectures: + - amd64 + repos: + - apt/debian/dists/buster/mongodb-org + + - name: ubuntu1804 + type: deb + code_name: "bionic" + edition: org + bucket: repo.mongodb.org + component: multiverse + architectures: + - amd64 + repos: + - apt/ubuntu/dists/bionic/mongodb-org + + - name: rhel80 + type: rpm + edition: org + bucket: repo.mongodb.org + repos: + - yum/redhat/8/mongodb-org + - yum/redhat/8Server/mongodb-org \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2e9f291f18..6b3f4974b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10216,6 +10216,37 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "gunzip-maybe": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", + "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==", + "dev": true, + "requires": { + "browserify-zlib": "^0.1.4", + "is-deflate": "^1.0.0", + "is-gzip": "^1.0.0", + "peek-stream": "^1.1.0", + "pumpify": "^1.3.3", + "through2": "^2.0.3" + }, + "dependencies": { + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "requires": { + "pako": "~0.2.0" + } + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + } + } + }, "handlebars": { "version": "4.7.6", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", @@ -11049,6 +11080,12 @@ "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, + "is-deflate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz", + "integrity": "sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ=", + "dev": true + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -11116,6 +11153,12 @@ "is-extglob": "^2.1.1" } }, + "is-gzip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", + "integrity": "sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=", + "dev": true + }, "is-html": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-html/-/is-html-1.1.0.tgz", @@ -14045,7 +14088,7 @@ "dev": true }, "node-codesign": { - "version": "github:durran/node-codesign#92863a258c2108556c0a33284d26f635729041da", + "version": "github:durran/node-codesign#8f7adaf9889a6be26f4fd60efa610967cfdf8730", "from": "github:durran/node-codesign", "dev": true, "requires": { @@ -14144,9 +14187,9 @@ } }, "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, "node-fetch-npm": { "version": "2.0.4", @@ -15308,6 +15351,17 @@ "sha.js": "^2.4.8" } }, + "peek-stream": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", + "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "duplexify": "^3.5.0", + "through2": "^2.0.3" + } + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -18750,6 +18804,65 @@ } } }, + "tar-fs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", + "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + }, + "dependencies": { + "bl": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-stream": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", + "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + } + } + }, "tar-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", diff --git a/package.json b/package.json index 1896c27561..22e623a2c7 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "eslint-config-mongodb-js": "^5.0.3", "eslint-plugin-mocha": "^6.2.2", "fs-extra": "^8.1.0", + "gunzip-maybe": "^1.4.2", "handlebars": "^4.7.6", "js-yaml": "^3.13.1", "karma": "^4.4.1", @@ -96,6 +97,7 @@ "mongodb-js-precommit": "^2.0.0", "mongodb-runner": "^4.7.5", "node-codesign": "durran/node-codesign", + "node-fetch": "^2.6.1", "parcel-bundler": "^1.12.4", "pkg": "^4.4.3", "pkg-deb": "^1.1.1", @@ -105,6 +107,8 @@ "sinon": "^7.5.0", "sinon-chai": "^3.4.0", "tar": "^6.0.1", + "tar-fs": "^2.1.0", + "tmp-promise": "^3.0.2", "ts-node": "^8.5.2", "ts-sinon": "^1.2.0", "typescript": "^3.9.7", diff --git a/packages/build/README.md b/packages/build/README.md index 4f70b4955b..3b7ea696a5 100644 --- a/packages/build/README.md +++ b/packages/build/README.md @@ -10,25 +10,33 @@ root of the project. Current build and release flow is as follows: -- `npm run evergreen-release package - - A commit triggers an evergreen build based on currently available build - variants: MacOS, Windows, Linux, Debian, and RedHat. - - MacOS, Linux and Windows run three tasks: check, test, and release. Debian and - Redhat run two tasks: check and release. Debian and Redhat also depend on - tests to pass on Linux. - - Identical bundle and binary are built on all five variants. - - Each variant creates its own tarball (`.zip`, `.tgz`, `.deb`, `.rpm`). Type of - tarball is determined by the current build variant. - - Each variant uploads its own tarball to Evergreen’s AWS. - - MacOS build variant uploads config file with information about the new version - for each platform to Downloads Centre. This only happens on a tagged commit. - - MacOS build variant creates a github release. This only happens on a tagged - commit. - - The five build variants run in parallel. -- `npm run evergreen-release publish` - - All the previous build steps succeeded. - - A separate MacOS build variant (darwin_publish_release) uploads config file with information about the new version for each platform to Downloads Centre. This only happens on a tagged commit. - - A separate MacOS build variant (darwin_publish_release) promotes the draft github release to public. This only happens on a tagged commit. +### `npm run evergreen-release package` +- A commit triggers an evergreen build based on currently available build + variants: MacOS, Windows, Linux, Debian, and RedHat. +- MacOS, Linux and Windows run three tasks: check, test, and release. Debian and + Redhat run two tasks: check and release. Debian and Redhat also depend on + tests to pass on Linux. +- Identical bundle and binary are built on all five variants. +- Each variant creates its own tarball (`.zip`, `.tgz`, `.deb`, `.rpm`). Type of + tarball is determined by the current build variant. +- Each variant uploads its own tarball to Evergreen’s AWS. +- Linux build variants upload their artifacts to `barque` using + [`curator`](https://github.com/mongodb/curator) to be used with MongoDB's PPA. The uploaded packages can be found under the following URLs: + 1. Ubuntu: https://repo.mongodb.org/apt/ubuntu/dists/bionic/mongodb-org/4.4/multiverse/binary-amd64/ + 2. Redhat: https://repo.mongodb.org/yum/redhat/8Server/mongodb-org/4.4/x86_64/RPMS/ + 3. Debian: https://repo.mongodb.org/apt/debian/dists/buster/mongodb-org/4.4/main/binary-amd64/ +- MacOS build variant uploads config file with information about the new version + for each platform to Downloads Centre. This only happens on a tagged commit. +- MacOS build variant creates a github release. This only happens on a tagged + commit. +- The five build variants run in parallel. +### `npm run evergreen-release publish` +- All the previous build steps succeeded. +- A separate MacOS build variant (darwin_publish_release) uploads config file + with information about the new version for each platform to Downloads Centre. +This only happens on a tagged commit. +- A separate MacOS build variant (darwin_publish_release) promotes the draft + github release to public. This only happens on a tagged commit. ![build flow][build-img] diff --git a/packages/build/src/barque.spec.ts b/packages/build/src/barque.spec.ts new file mode 100644 index 0000000000..aeaa458b17 --- /dev/null +++ b/packages/build/src/barque.spec.ts @@ -0,0 +1,183 @@ +import { Barque } from './barque'; +import { expect } from 'chai'; +import Config from './config'; +import sinon from 'sinon'; +import fs from 'fs-extra'; +import path from 'path'; + +describe('Barque', () => { + let barque: Barque; + let config: Config; + + beforeEach(() => { + config = { + version: 'version', + bundleId: 'bundleId', + input: 'input', + execInput: 'execInput', + outputDir: 'outputDir', + analyticsConfig: 'analyticsConfig', + project: 'project', + revision: 'revision', + branch: 'branch', + evgAwsKey: 'evgAwsKey', + evgAwsSecret: 'evgAwsSecret', + downloadCenterAwsKey: 'downloadCenterAwsKey', + downloadCenterAwsSecret: 'downloadCenterAwsSecret', + githubToken: 'githubToken', + segmentKey: 'segmentKey', + rootDir: '../../../', + appleUser: 'appleUser', + applePassword: 'applePassword', + appleAppIdentity: 'appleAppIdentity', + isCi: true, + platform: 'linux', + buildVariant: 'linux', + repo: { + owner: 'owner', + repo: 'repo', + } + }; + + barque = new Barque(config); + }); + + describe('.releaseToBarque', () => { + context('platform is linux', () => { + it('execCurator function succeeds', async() => { + barque.execCurator = sinon.stub().returns(Promise.resolve(true)); + barque.createCuratorDir = sinon.stub().returns(Promise.resolve('./')); + barque.extractLatestCurator = sinon.stub().returns(Promise.resolve(true)); + + const tarballURL = 'https://s3.amazonaws.com/mciuploads/mongosh/5ed7ee5d8683818eb28d9d3b5c65837cde4a08f5/mongosh-0.1.0-linux.tgz'; + + let err; + + try { + await barque.releaseToBarque(tarballURL); + } catch (error) { + err = error; + } + expect(err).to.be.undefined; + expect(barque.createCuratorDir).to.have.been.called; + expect(barque.extractLatestCurator).to.have.been.called; + expect(barque.execCurator).to.have.been.called; + }); + + it('execCurator function fails', async() => { + barque.execCurator = sinon.stub().rejects(new Error('error')); + barque.createCuratorDir = sinon.stub().returns(Promise.resolve('./')); + barque.extractLatestCurator = sinon.stub().returns(Promise.resolve(true)); + + const tarballURL = 'https://s3.amazonaws.com/mciuploads/mongosh/5ed7ee5d8683818eb28d9d3b5c65837cde4a08f5/mongosh-0.1.0-linux.tgz'; + + let err; + + try { + await barque.releaseToBarque(tarballURL); + } catch (error) { + err = error; + } + + expect(err).to.not.be.undefined; + expect(err.message).to.include('Curator is unable to upload to barque'); + expect(barque.createCuratorDir).to.have.been.called; + expect(barque.extractLatestCurator).to.have.been.called; + expect(barque.execCurator).to.have.been.called; + }); + }); + + it('platform is not linux', async() => { + config.platform = 'macos'; + barque = new Barque(config); + + barque.execCurator = sinon.stub().returns(Promise.resolve(true)); + barque.createCuratorDir = sinon.stub().returns(Promise.resolve('./')); + barque.extractLatestCurator = sinon.stub().returns(Promise.resolve(true)); + + const tarballURL = 'https://s3.amazonaws.com/mciuploads/mongosh/5ed7ee5d8683818eb28d9d3b5c65837cde4a08f5/mongosh-0.1.0-linux.tgz'; + + await barque.releaseToBarque(tarballURL); + expect(barque.createCuratorDir).to.have.been.called; + expect(barque.extractLatestCurator).to.have.been.called; + expect(barque.execCurator).to.not.have.been.called; + }); + }); + + describe('.determineDistro', () => { + it('determines distro for ubuntu', async() => { + const distro = barque.determineDistro('linux'); + expect(distro).to.be.equal('ubuntu1804'); + }); + + it('determines distro for debian', async() => { + const distro = barque.determineDistro('debian'); + expect(distro).to.be.equal('debian10'); + }); + + it('determines distro for redhat', async() => { + const distro = barque.determineDistro('rhel'); + expect(distro).to.be.equal('rhel80'); + }); + + it('defaults to ubuntu distro', async() => { + const distro = barque.determineDistro('redhat'); + expect(distro).to.be.equal('ubuntu1804'); + }); + }); + + describe('.determineArch', () => { + it('determines arch for ubuntu', async() => { + const distro = barque.determineArch('linux'); + expect(distro).to.be.equal('amd64'); + }); + + it('determines arch for debian', async() => { + const distro = barque.determineArch('debian'); + expect(distro).to.be.equal('amd64'); + }); + + it('determines arch for redhat', async() => { + const distro = barque.determineArch('rhel'); + expect(distro).to.be.equal('x86_64'); + }); + + it('defaults to ubuntu arch', async() => { + const distro = barque.determineArch('redhat'); + expect(distro).to.be.equal('amd64'); + }); + }); + + describe('.createCuratorDir', () => { + it('creates tmp directory that exists', async() => { + const curatorDirPath = await barque.createCuratorDir(); + + let accessErr + try { + await fs.access(curatorDirPath) + } catch (e) { + accessErr = e + } + // eslint-disable-next-line + expect(accessErr).to.be.undefined + }); + }); + + describe('.extractLatestCurator', () => { + it('extractss latest curator to tmp directory', async() => { + const curatorDirPath = await barque.createCuratorDir(); + const curatorPath = path.join(curatorDirPath, 'curator'); + + await barque.extractLatestCurator(curatorDirPath); + + let accessErr + try { + await fs.access(curatorPath) + } catch (e) { + accessErr = e + } + // eslint-disable-next-line + expect(accessErr).to.be.undefined + }); + }); +}); diff --git a/packages/build/src/barque.ts b/packages/build/src/barque.ts new file mode 100644 index 0000000000..b61c951270 --- /dev/null +++ b/packages/build/src/barque.ts @@ -0,0 +1,190 @@ +import BuildVariant from './build-variant'; +import child_process from 'child_process'; +import gunzip from 'gunzip-maybe'; +import Platform from './platform'; +import fetch from 'node-fetch'; +import tmp from 'tmp-promise'; +import Config from './config'; +import stream from 'stream'; +import fs from 'fs-extra'; +import tar from 'tar-fs'; +import util from 'util'; +import path from 'path'; + +const pipeline = util.promisify(stream.pipeline); +const execFile = util.promisify(child_process.execFile); + +const LATEST_CURATOR = + 'https://s3.amazonaws.com/boxes.10gen.com/build/curator/curator-dist-ubuntu1604-latest.tar.gz'; + +// make sure everything written in /tmp is cleared if an uncaught exception occurs +tmp.setGracefulCleanup() + +/** + * Distro enum to be used when making a curator call. + */ +enum Distro { + Ubuntu = 'ubuntu1804', + Debian = 'debian10', + Redhat = 'rhel80' +} + +/** + * Arch enum to be used when making a curator call. + * + * If we were to target a different arch for these distros, make sure + * config/repo-config.yml and packages/build/src/tarball.ts are changed accordingly. + */ +enum Arch { + Ubuntu = 'amd64', + Debian = 'amd64', + Redhat = 'x86_64', +} + +export class Barque { + private config: Config; + private mongodbEdition: string; + private mongodbVersion: string; + + constructor(config: Config) { + this.config = config; + // hard code mongodb edition to 'org' for now + this.mongodbEdition = 'org'; + // linux mongodb versions to release to. This should perhaps be an array of + // [4.3.0, 4.4.0], like mongo-tools + this.mongodbVersion = '4.4.0'; + } + + /** + * Upload current package to barque, MongoDB's PPA for linux distros. + * + * @param {string} tarballURL- The uploaded to Evergreen tarball URL. + * @param {Config} config - Config object. + * + * @returns {Promise} The promise. + */ + async releaseToBarque(tarballURL: string): Promise { + const repoConfig = path.join(this.config.rootDir, 'config', 'repo-config.yml'); + const curatorDirPath = await this.createCuratorDir(); + await this.extractLatestCurator(curatorDirPath); + + if (this.config.platform === Platform.Linux) { + try { + await this.execCurator(curatorDirPath, tarballURL, repoConfig); + } catch (error) { + throw new Error(`Curator is unable to upload to barque ${error}`); + } + } + + return; + } + + /** + * Run the child_process.exec to run curator command. + * + * @param {string} curatorDir - Path to freshly downloaded curator. + * @param {string} tarballURL- The uploaded to Evergreen tarball URL. + * @param {string} repoConfig - Path to repo-config.yml used to uplaod assets + * to appropriate distros. + * + * @returns {Promise} The promise. + */ + async execCurator( + curatorDirPath: string, + tarballURL: string, + repoConfig: string): Promise { + return await execFile( + `${curatorDirPath}/curator`, [ + '--level', 'debug', + 'repo', 'submit', + '--service', 'https://barque.corp.mongodb.com', + '--config', repoConfig, + '--distro', this.determineDistro(this.config.buildVariant), + '--arch', this.determineArch(this.config.buildVariant), + '--edition', this.mongodbEdition, + '--version', this.mongodbVersion, + '--packages', tarballURL + ], { + // curator looks for these options in env + env: { + NOTARY_KEY_NAME: 'server-4.4', + NOTARY_TOKEN: process.env.SIGNING_AUTH_TOKEN_44, + BARQUE_API_KEY: process.env.BARQUE_API_KEY, + BARQUE_USERNAME: process.env.BARQUE_USERNAME + } + }); + } + + /** + * Determine the current arch to be passed on to curator given current build + * variant. + * + * @param {string} variant - Current build variant. + * + * @returns {string} Arch to be passed as an argument to curator + */ + determineArch(variant: string): string { + // we can't use distro_id from evergreen's env variables, since those + // sometimes come with other attributes like -test -large -small, and are not + // valid distros for barque. + if (variant === BuildVariant.Linux) return Arch.Ubuntu; + if (variant === BuildVariant.Debian) return Arch.Debian; + if (variant === BuildVariant.Redhat) return Arch.Redhat; + + return Arch.Ubuntu; + } + /** + * Determine the current distro to be passed on to curator given current build + * variant. + * + * @param {string} variant - Current build variant. + * + * @returns {string} Distro to be passed as an argument to curator + */ + determineDistro(variant: string): string { + // we can't use distro_id from evergreen's env variables, since those + // sometimes come with other attributes like -test -large -small, and are not + // valid distros for barque. + if (variant === BuildVariant.Linux) return Distro.Ubuntu; + if (variant === BuildVariant.Debian) return Distro.Debian; + if (variant === BuildVariant.Redhat) return Distro.Redhat; + + return Distro.Ubuntu; + } + + /** + * Create a staging dir in /tmp to download the latest version of curator. + * + * @returns {Promise} Staging directory path. + */ + async createCuratorDir(): Promise { + const dir = await tmp.dir({ prefix: 'curator-', unsafeCleanup: true }); + fs.ensureDir(dir.path, '0755'); + + return dir.path; + } + + /** + * Fetch, unzip, and un-tar the latest version of curator. Then write it to + * previoiusly created /tmp staging directory. + * + * @param {string} dest - Destination to write the un-packaged curator + * executable. + * + * @returns {Promise} Written binary in the given location. + * + * Debian and Ubuntu Build Variants' curator errors out on `execFile`, to make + * sure this functionality works as expected, download the latest curator. + * + */ + async extractLatestCurator(dest: string): Promise { + const response = await fetch(LATEST_CURATOR) + if (response.ok) { + return pipeline( + response.body, + gunzip(), + tar.extract(dest) + ); + } + } +} diff --git a/packages/build/src/build-and-upload.spec.ts b/packages/build/src/build-and-upload.spec.ts index b516db2bdd..b281591dc8 100644 --- a/packages/build/src/build-and-upload.spec.ts +++ b/packages/build/src/build-and-upload.spec.ts @@ -1,9 +1,10 @@ -import { GithubRepo } from './github-repo'; import buildAndUpload from './build-and-upload'; +import { GithubRepo } from './github-repo'; +import { TarballFile } from './tarball'; import chai, { expect } from 'chai'; +import { Barque } from './barque'; import Config from './config'; import sinon from 'ts-sinon'; -import { TarballFile } from './tarball'; chai.use(require('sinon-chai')); @@ -11,12 +12,17 @@ function createStubRepo(overrides?: any): GithubRepo { return sinon.createStubInstance(GithubRepo, overrides) as unknown as GithubRepo; } +function createStubBarque(overrides?: any): Barque { + return sinon.createStubInstance(Barque, overrides) as unknown as Barque; +} + describe('buildAndRelease', () => { let config: Config; let tarballFile: TarballFile; let compileAndZipExecutable: (Config) => Promise; let uploadToEvergreen: (artifact: string, awsKey: string, awsSecret: string, project: string, revision: string) => Promise; let uploadToDownloadCenter: (artifact: string, awsKey: string, awsSecret: string) => Promise; + let barque: Barque; let githubRepo: GithubRepo; beforeEach(() => { @@ -50,20 +56,26 @@ describe('buildAndRelease', () => { tarballFile = { path: 'path', contentType: 'application/gzip' }; compileAndZipExecutable = sinon.stub().resolves(tarballFile); + barque = createStubBarque(); uploadToEvergreen = sinon.spy(); uploadToDownloadCenter = sinon.spy(); githubRepo = createStubRepo(); }); [true, false].forEach((isPublicRelease) => { - it(`uploads the artifact to evergreen if is ${isPublicRelease ? 'a' : 'not a'} public release`, async() => { + it(`uploads the artifact to evergreen if is ${isPublicRelease ? 'a' : 'not a'} public release`, async () => { githubRepo = createStubRepo({ shouldDoPublicRelease: sinon.stub().returns(Promise.resolve(isPublicRelease)) }); + barque = createStubBarque({ + releaseToBarque: sinon.stub().returns(Promise.resolve(true)) + }); + await buildAndUpload( config, githubRepo, + barque, compileAndZipExecutable, uploadToEvergreen, uploadToDownloadCenter @@ -79,14 +91,19 @@ describe('buildAndRelease', () => { }); }); - it('releases to github if a public release', async() => { + it('releases to github if a public release', async () => { githubRepo = createStubRepo({ shouldDoPublicRelease: sinon.stub().returns(Promise.resolve(true)) }); + barque = createStubBarque({ + releaseToBarque: sinon.stub().returns(Promise.resolve(true)) + }); + await buildAndUpload( config, githubRepo, + barque, compileAndZipExecutable, uploadToEvergreen, uploadToDownloadCenter @@ -99,15 +116,41 @@ describe('buildAndRelease', () => { ); }); - it('releases to downloads centre if a public release', async() => { + it('releases to barque if a public release', async () => { + githubRepo = createStubRepo({ + shouldDoPublicRelease: sinon.stub().returns(Promise.resolve(true)) + }); + + barque = createStubBarque({ + releaseToBarque: sinon.stub().returns(Promise.resolve(true)) + }); + + await buildAndUpload( + config, + githubRepo, + barque, + compileAndZipExecutable, + uploadToEvergreen, + uploadToDownloadCenter + ); + + expect(barque.releaseToBarque).to.have.been.called; + }); + + it('releases to downloads centre if a public release', async () => { githubRepo = createStubRepo({ shouldDoPublicRelease: sinon.stub().returns(Promise.resolve(true)), releaseToGithub: sinon.stub().returns(Promise.resolve(true)) } as any); + barque = createStubBarque({ + releaseToBarque: sinon.stub().returns(Promise.resolve(true)) + }); + await buildAndUpload( config, githubRepo, + barque, compileAndZipExecutable, uploadToEvergreen, uploadToDownloadCenter diff --git a/packages/build/src/build-and-upload.ts b/packages/build/src/build-and-upload.ts index 3f918fe157..5772e74f18 100644 --- a/packages/build/src/build-and-upload.ts +++ b/packages/build/src/build-and-upload.ts @@ -1,11 +1,14 @@ +import { redactConfig } from './redact-config'; +import { getArtifactUrl } from './evergreen'; import { GithubRepo } from './github-repo'; -import Config from './config'; import { TarballFile } from './tarball'; -import { redactConfig } from './redact-config'; +import { Barque } from './barque'; +import Config from './config'; export default async function buildAndUpload( config: Config, githubRepo: GithubRepo, + barque: Barque, compileAndZipExecutable: (Config) => Promise, uploadToEvergreen: (artifact: string, awsKey: string, awsSecret: string, project: string, revision: string) => Promise, uploadToDownloadCenter: (artifact: string, awsKey: string, awsSecret: string) => Promise): Promise { @@ -30,6 +33,8 @@ export default async function buildAndUpload( ); console.log('mongosh: internal release completed.'); + const evergreenTarball = getArtifactUrl(config.project, config.revision, tarballFile.path); + // Only release to public from master and when tagged with the right version. if (await githubRepo.shouldDoPublicRelease(config)) { console.log('mongosh: start public release.'); @@ -40,6 +45,9 @@ export default async function buildAndUpload( config.downloadCenterAwsSecret ); + await barque.releaseToBarque(evergreenTarball); + console.log('mongosh: submitting to barque complete'); + await githubRepo.releaseToGithub(tarballFile, config); } diff --git a/packages/build/src/evergreen.ts b/packages/build/src/evergreen.ts index 2e7b2454df..5f43817a36 100644 --- a/packages/build/src/evergreen.ts +++ b/packages/build/src/evergreen.ts @@ -35,9 +35,9 @@ const uploadArtifactToEvergreen = (artifact: string, awsKey: string, awsSecret: return upload(uploadParams, s3); }; -const getArtifactPath = (project: string, revision: string, artifact: string): string => { - return `https://s3/amazoonaws.com/${BUCKET}/${project}/${revision}/${path.basename(artifact)}` +const getArtifactUrl = (project: string, revision: string, artifact: string): string => { + return `https://s3.amazonaws.com/${BUCKET}/${project}/${revision}/${path.basename(artifact)}` }; export default uploadArtifactToEvergreen; -export { getArtifactPath }; +export { getArtifactUrl }; diff --git a/packages/build/src/release.ts b/packages/build/src/release.ts index 6a91814dec..674f4d60e3 100644 --- a/packages/build/src/release.ts +++ b/packages/build/src/release.ts @@ -1,13 +1,14 @@ /* eslint-disable no-shadow */ import uploadToDownloadCenter from './upload-to-download-center'; import compileAndZipExecutable from './compile-and-zip-executable'; +import uploadDownloadCenterConfig from './download-center'; import uploadArtifactToEvergreen from './evergreen'; +import buildAndUpload from './build-and-upload'; import { GithubRepo } from './github-repo'; import { Octokit } from '@octokit/rest'; -import Config from './config'; -import buildAndUpload from './build-and-upload'; +import { Barque } from './barque'; import publish from './publish'; -import uploadDownloadCenterConfig from './download-center'; +import Config from './config'; /** * Run the release process. @@ -24,11 +25,13 @@ export default async function release( }); const githubRepo = new GithubRepo(config.repo, octokit); + const barque = new Barque(config); if (command === 'package') { await buildAndUpload( config, githubRepo, + barque, compileAndZipExecutable, uploadArtifactToEvergreen, uploadToDownloadCenter diff --git a/scripts/import-expansions.js b/scripts/import-expansions.js index b18370cc9c..c8328f0710 100644 --- a/scripts/import-expansions.js +++ b/scripts/import-expansions.js @@ -33,4 +33,4 @@ const importExpansions = () => { console.info('Imported expansions:', Object.keys(expansions).join(', ')); } -importExpansions(); \ No newline at end of file +importExpansions(); From 9788925990fd5baaea8a6eee5fb6f375c21d4e73 Mon Sep 17 00:00:00 2001 From: Irina Shestak Date: Fri, 25 Sep 2020 12:06:39 +0200 Subject: [PATCH 3/4] Update packages/build/src/build-and-upload.spec.ts Co-authored-by: Anna Henningsen --- packages/build/src/build-and-upload.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/build/src/build-and-upload.spec.ts b/packages/build/src/build-and-upload.spec.ts index b281591dc8..06a6ee681b 100644 --- a/packages/build/src/build-and-upload.spec.ts +++ b/packages/build/src/build-and-upload.spec.ts @@ -144,7 +144,7 @@ describe('buildAndRelease', () => { } as any); barque = createStubBarque({ - releaseToBarque: sinon.stub().returns(Promise.resolve(true)) + releaseToBarque: sinon.stub().resolves(true) }); await buildAndUpload( From ddee27ef263429e38955e2b7943b5bc4951af5ec Mon Sep 17 00:00:00 2001 From: Irina Shestak Date: Fri, 25 Sep 2020 12:28:52 +0200 Subject: [PATCH 4/4] additional tests --- packages/build/src/barque.ts | 4 +- packages/build/src/build-and-upload.spec.ts | 72 +++++++++++++++++++-- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/packages/build/src/barque.ts b/packages/build/src/barque.ts index b61c951270..702bcff802 100644 --- a/packages/build/src/barque.ts +++ b/packages/build/src/barque.ts @@ -30,10 +30,12 @@ enum Distro { } /** - * Arch enum to be used when making a curator call. + * Target arch enum to be used when making a curator call. * * If we were to target a different arch for these distros, make sure * config/repo-config.yml and packages/build/src/tarball.ts are changed accordingly. + * + * This can be also moved to /config/build.conf.js in the future. */ enum Arch { Ubuntu = 'amd64', diff --git a/packages/build/src/build-and-upload.spec.ts b/packages/build/src/build-and-upload.spec.ts index 06a6ee681b..8e4cd9d784 100644 --- a/packages/build/src/build-and-upload.spec.ts +++ b/packages/build/src/build-and-upload.spec.ts @@ -93,11 +93,11 @@ describe('buildAndRelease', () => { it('releases to github if a public release', async () => { githubRepo = createStubRepo({ - shouldDoPublicRelease: sinon.stub().returns(Promise.resolve(true)) + shouldDoPublicRelease: sinon.stub().resolves(true) }); barque = createStubBarque({ - releaseToBarque: sinon.stub().returns(Promise.resolve(true)) + releaseToBarque: sinon.stub().resolves(true) }); await buildAndUpload( @@ -116,6 +116,27 @@ describe('buildAndRelease', () => { ); }); + it('does not release to github if not a public release', async () => { + githubRepo = createStubRepo({ + shouldDoPublicRelease: sinon.stub().resolves(false) + }); + + barque = createStubBarque({ + releaseToBarque: sinon.stub().resolves(true) + }); + + await buildAndUpload( + config, + githubRepo, + barque, + compileAndZipExecutable, + uploadToEvergreen, + uploadToDownloadCenter + ); + + expect(uploadToDownloadCenter).to.not.have.been.called; + }); + it('releases to barque if a public release', async () => { githubRepo = createStubRepo({ shouldDoPublicRelease: sinon.stub().returns(Promise.resolve(true)) @@ -137,10 +158,31 @@ describe('buildAndRelease', () => { expect(barque.releaseToBarque).to.have.been.called; }); + it('does not releases to barque if not a public release', async () => { + githubRepo = createStubRepo({ + shouldDoPublicRelease: sinon.stub().resolves(false) + }); + + barque = createStubBarque({ + releaseToBarque: sinon.stub().resolves(true) + }); + + await buildAndUpload( + config, + githubRepo, + barque, + compileAndZipExecutable, + uploadToEvergreen, + uploadToDownloadCenter + ); + + expect(barque.releaseToBarque).to.not.have.been.called; + }); + it('releases to downloads centre if a public release', async () => { githubRepo = createStubRepo({ - shouldDoPublicRelease: sinon.stub().returns(Promise.resolve(true)), - releaseToGithub: sinon.stub().returns(Promise.resolve(true)) + shouldDoPublicRelease: sinon.stub().resolves(true), + releaseToGithub: sinon.stub().resolves(true) } as any); barque = createStubBarque({ @@ -158,4 +200,26 @@ describe('buildAndRelease', () => { expect(githubRepo.releaseToGithub).to.have.been.calledWith(tarballFile, config); }); + + it('does not release to downloads centre if not a public release', async () => { + githubRepo = createStubRepo({ + shouldDoPublicRelease: sinon.stub().resolves(false), + releaseToGithub: sinon.stub().resolves(true) + } as any); + + barque = createStubBarque({ + releaseToBarque: sinon.stub().resolves(true) + }); + + await buildAndUpload( + config, + githubRepo, + barque, + compileAndZipExecutable, + uploadToEvergreen, + uploadToDownloadCenter + ); + + expect(githubRepo.releaseToGithub).to.not.have.been.called; + }); });