diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/circle-ci-api.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/circle-ci-api.ts index 592ddd7407c..5e5735f4723 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/circle-ci-api.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/common/circle-ci-api.ts @@ -1,10 +1,10 @@ // Imports -import fetch from 'node-fetch'; +import fetch, {RequestInit} from 'node-fetch'; import {assertNotMissingOrEmpty} from './utils'; // Constants -const CIRCLE_CI_API_URL = 'https://circleci.com/api/v1.1/project/github'; +const CIRCLE_CI_API_URL = 'https://circleci.com/api/v2/project/gh'; // Interfaces - Types export interface ArtifactInfo { @@ -32,8 +32,6 @@ export interface BuildInfo { * A Helper that can interact with the CircleCI API. */ export class CircleCiApi { - private tokenParam = `circle-token=${this.circleCiToken}`; - /** * Construct a helper that can interact with the CircleCI REST API. * @param githubOrg The Github organisation whose repos we want to access in CircleCI (e.g. @@ -51,6 +49,15 @@ export class CircleCiApi { assertNotMissingOrEmpty('circleCiToken', circleCiToken); } + public async fetchFromCircleCi(url: string, params: RequestInit = {}) { + params.headers = {...params.headers, 'Circle-Token': this.circleCiToken}; + const response = await fetch(url, params); + if (response.status !== 200) { + throw new Error(`${url}: ${response.status} - ${response.statusText}`); + } + return response; + } + /** * Get the info for a build from the CircleCI API * @param buildNumber The CircleCI build number that generated the artifact. @@ -58,11 +65,8 @@ export class CircleCiApi { */ public async getBuildInfo(buildNumber: number): Promise { try { - const baseUrl = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`; - const response = await fetch(`${baseUrl}?${this.tokenParam}`); - if (response.status !== 200) { - throw new Error(`${baseUrl}: ${response.status} - ${response.statusText}`); - } + const url = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/job/${buildNumber}`; + const response = await this.fetchFromCircleCi(url); return response.json(); } catch (error) { throw new Error(`CircleCI build info request failed (${(error as Error).message})`); @@ -77,9 +81,9 @@ export class CircleCiApi { public async getBuildArtifactUrl(buildNumber: number, artifactPath: string): Promise { const baseUrl = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`; try { - const response = await fetch(`${baseUrl}/artifacts?${this.tokenParam}`); - const artifacts = await response.json() as ArtifactResponse; - const artifact = artifacts.find(item => item.path === artifactPath); + const response = await this.fetchFromCircleCi(`${baseUrl}/artifacts`); + const artifacts = await response.json() as {items: ArtifactResponse}; + const artifact = artifacts.items.find(item => item.path === artifactPath); if (!artifact) { throw new Error(`Missing artifact (${artifactPath}) for CircleCI build: ${buildNumber}`); } diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-retriever.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-retriever.ts index b06a770a12d..97f3954c388 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-retriever.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/preview-server/build-retriever.ts @@ -1,5 +1,4 @@ import * as fs from 'fs'; -import fetch from 'node-fetch'; import {dirname} from 'path'; import {mkdir} from 'shelljs'; import {promisify} from 'util'; @@ -63,11 +62,7 @@ export class BuildRetriever { await new Promise(resolve => fs.exists(outPath, exists => resolve(exists))); if (!downloadExists) { const url = await this.api.getBuildArtifactUrl(buildNum, artifactPath); - const response = await fetch(url, {size: this.downloadSizeLimit}); - if (response.status !== 200) { - throw new PreviewServerError( - response.status, `Error ${response.status} - ${response.statusText}`); - } + const response = await this.api.fetchFromCircleCi(url, {size: this.downloadSizeLimit}); const buffer = await response.buffer(); mkdir('-p', dirname(outPath)); await promisify(fs.writeFile)(outPath, buffer); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/circleci-api.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/circleci-api.spec.ts index 7e9defb8dbf..831b88ef066 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/circleci-api.spec.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/test/common/circleci-api.spec.ts @@ -4,7 +4,7 @@ import {CircleCiApi} from '../../lib/common/circle-ci-api'; const ORG = 'testorg'; const REPO = 'testrepo'; const TOKEN = 'xxxx'; -const BASE_URL = `https://circleci.com/api/v1.1/project/github/${ORG}/${REPO}`; +const BASE_URL = `https://circleci.com/api/v2/project/gh/${ORG}/${REPO}`; describe('CircleCIApi', () => { describe('constructor()', () => { @@ -24,6 +24,18 @@ describe('CircleCIApi', () => { }); }); + describe('fetchFromCircleCI', () => { + it('should include the authentication token in the headers on every request', async () => { + const api = new CircleCiApi(ORG, REPO, TOKEN); + const request = nock(BASE_URL) + .get('/') + .matchHeader('Circle-Token', TOKEN) + .reply(200); + await api.fetchFromCircleCi(`${BASE_URL}/`); + request.done(); + }) + }) + describe('getBuildInfo', () => { it('should make a request to the CircleCI API for the given build number', async () => { const api = new CircleCiApi(ORG, REPO, TOKEN); @@ -31,7 +43,7 @@ describe('CircleCIApi', () => { const expectedBuildInfo: any = { org: ORG, repo: REPO, build_num: buildNum }; const request = nock(BASE_URL) - .get(`/${buildNum}?circle-token=${TOKEN}`) + .get(`/job/${buildNum}`) .reply(200, expectedBuildInfo); const buildInfo = await api.getBuildInfo(buildNum); @@ -43,17 +55,17 @@ describe('CircleCIApi', () => { const api = new CircleCiApi(ORG, REPO, TOKEN); const buildNum = 12345; const errorMessage = 'Invalid request'; - const request = nock(BASE_URL).get(`/${buildNum}?circle-token=${TOKEN}`); + const request = nock(BASE_URL).get(`/job/${buildNum}`); request.replyWithError(errorMessage); await expectAsync(api.getBuildInfo(buildNum)).toBeRejectedWithError( `CircleCI build info request failed ` + - `(request to ${BASE_URL}/${buildNum}?circle-token=${TOKEN} failed, reason: ${errorMessage})`); + `(request to ${BASE_URL}/job/${buildNum} failed, reason: ${errorMessage})`); request.reply(404, errorMessage); await expectAsync(api.getBuildInfo(buildNum)).toBeRejectedWithError( `CircleCI build info request failed ` + - `(request to ${BASE_URL}/${buildNum}?circle-token=${TOKEN} failed, reason: ${errorMessage})`); + `(request to ${BASE_URL}/job/${buildNum} failed, reason: ${errorMessage})`); }); }); @@ -65,8 +77,8 @@ describe('CircleCIApi', () => { const artifact1: any = { path: 'some/path/1', url: 'https://url/1' }; const artifact2: any = { path: 'some/path/2', url: 'https://url/2' }; const request = nock(BASE_URL) - .get(`/${buildNum}/artifacts?circle-token=${TOKEN}`) - .reply(200, [artifact0, artifact1, artifact2]); + .get(`/${buildNum}/artifacts`) + .reply(200, {items: [artifact0, artifact1, artifact2]}); await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeResolvedTo('https://url/1'); request.done(); @@ -77,17 +89,17 @@ describe('CircleCIApi', () => { const api = new CircleCiApi(ORG, REPO, TOKEN); const buildNum = 12345; const errorMessage = 'Invalid request'; - const request = nock(BASE_URL).get(`/${buildNum}/artifacts?circle-token=${TOKEN}`); + const request = nock(BASE_URL).get(`/${buildNum}/artifacts`); request.replyWithError(errorMessage); await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeRejectedWithError( `CircleCI artifact URL request failed ` + - `(request to ${BASE_URL}/${buildNum}/artifacts?circle-token=${TOKEN} failed, reason: ${errorMessage})`); + `(request to ${BASE_URL}/${buildNum}/artifacts failed, reason: ${errorMessage})`); request.reply(404, errorMessage); await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeRejectedWithError( `CircleCI artifact URL request failed ` + - `(request to ${BASE_URL}/${buildNum}/artifacts?circle-token=${TOKEN} failed, reason: ${errorMessage})`); + `(request to ${BASE_URL}/${buildNum}/artifacts failed, reason: ${errorMessage})`); }); it('should throw an error if the response does not contain the specified artifact', async () => { @@ -97,8 +109,8 @@ describe('CircleCIApi', () => { const artifact1: any = { path: 'some/path/1', url: 'https://url/1' }; const artifact2: any = { path: 'some/path/2', url: 'https://url/2' }; nock(BASE_URL) - .get(`/${buildNum}/artifacts?circle-token=${TOKEN}`) - .reply(200, [artifact0, artifact1, artifact2]); + .get(`/${buildNum}/artifacts`) + .reply(200, {items: [artifact0, artifact1, artifact2]}); await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/3')).toBeRejectedWithError( `CircleCI artifact URL request failed ` + diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-retriever.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-retriever.spec.ts index bd7a65a50db..d5586419355 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-retriever.spec.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/test/preview-server/build-retriever.spec.ts @@ -160,7 +160,7 @@ describe('BuildRetriever', () => { const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(404, 'No such artifact'); await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)). - toBeRejectedWithError('CircleCI artifact download failed (Error 404 - Not Found)'); + toBeRejectedWithError(`CircleCI artifact download failed (${BASE_URL}/some/path/build.zip: 404 - Not Found)`); artifactRequest.done(); }); diff --git a/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md b/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md index 48e890401c0..346966c0360 100644 --- a/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md +++ b/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md @@ -25,8 +25,8 @@ Necessary secrets: - Generate new token with the `public_repo` scope. 2. `CIRCLE_CI_TOKEN` - - Visit https://circleci.com/gh/angular/angular/edit#api. - - Create an API token with `Build Artifacts` scope. + - Visit https://app.circleci.com/settings/user/tokens. + - Create a user API token. ## Save secrets on the VM