diff --git a/README.md b/README.md index 78c2fb61..4a7d5978 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,8 @@ Additionally, the following parameters can be used for additional control over t ### [notify-pipeline-complete](.github/workflows/notify-pipeline-complete.yml) - Sets a "pipeline" commit status to green for all open PRs +- Pass `state` (e.g. `failure`) to report a state other than `success` on the deployment and commit statuses. Defaults to `success`. +- Pass `sha` to mark the deployment for that commit instead of the newest deployment in the environment. Commit statuses are still set on open PRs (or merge queue commits when `merge_queue_enabled` is `true`). ### [prepare-queued-pr-for-merge](.github/workflows/prepare-queued-pr-for-merge.yml) diff --git a/dist/helpers/notify-pipeline-complete.js b/dist/helpers/notify-pipeline-complete.js index 077f450b..d2e54a36 100644 --- a/dist/helpers/notify-pipeline-complete.js +++ b/dist/helpers/notify-pipeline-complete.js @@ -34,9 +34,12 @@ var notifyPipelineComplete = async ({ description = DEFAULT_PIPELINE_DESCRIPTION, environment = PRODUCTION_ENVIRONMENT, target_url, - merge_queue_enabled + merge_queue_enabled, + state = "success", + sha }) => { const { data: deployments } = await octokit.repos.listDeployments({ + sha, environment, ...context.repo }); @@ -46,7 +49,7 @@ var notifyPipelineComplete = async ({ await octokit.repos.createDeploymentStatus({ environment, deployment_id, - state: "success", + state, description, target_url, ...context.repo @@ -56,7 +59,7 @@ var notifyPipelineComplete = async ({ return import_bluebird.map(mergeQueueCommitHashes, async (sha) => octokit.repos.createCommitStatus({ sha, context: context2, - state: "success", + state, description, target_url, ...context.repo @@ -71,7 +74,7 @@ var notifyPipelineComplete = async ({ return import_bluebird.map(commitHashesForOpenPullRequests, async (sha) => octokit.repos.createCommitStatus({ sha, context: context2, - state: "success", + state, description, target_url, ...context.repo @@ -82,4 +85,4 @@ export { notifyPipelineComplete }; -//# debugId=3D89CC9C798FA26064756E2164756E21 +//# debugId=3185D1B530D6728564756E2164756E21 diff --git a/dist/helpers/notify-pipeline-complete.js.map b/dist/helpers/notify-pipeline-complete.js.map index eea71f4f..684ad482 100644 --- a/dist/helpers/notify-pipeline-complete.js.map +++ b/dist/helpers/notify-pipeline-complete.js.map @@ -2,9 +2,9 @@ "version": 3, "sources": ["../src/helpers/notify-pipeline-complete.ts"], "sourcesContent": [ - "/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { DEFAULT_PIPELINE_DESCRIPTION, DEFAULT_PIPELINE_STATUS, PRODUCTION_ENVIRONMENT } from '../constants';\nimport { HelperInputs } from '../types/generated';\nimport { context as githubContext } from '@actions/github';\nimport { map } from 'bluebird';\nimport { octokit } from '../octokit';\nimport { getMergeQueueCommitHashes } from '../utils/merge-queue';\n\nexport class NotifyPipelineComplete extends HelperInputs {\n declare context?: string;\n declare description?: string;\n declare environment?: string;\n declare target_url?: string;\n declare merge_queue_enabled?: string;\n}\n\nexport const notifyPipelineComplete = async ({\n context = DEFAULT_PIPELINE_STATUS,\n description = DEFAULT_PIPELINE_DESCRIPTION,\n environment = PRODUCTION_ENVIRONMENT,\n target_url,\n merge_queue_enabled\n}: NotifyPipelineComplete) => {\n const { data: deployments } = await octokit.repos.listDeployments({\n environment,\n ...githubContext.repo\n });\n const deployment_id = deployments.find(Boolean)?.id;\n if (!deployment_id) return;\n await octokit.repos.createDeploymentStatus({\n environment,\n deployment_id,\n state: 'success',\n description,\n target_url,\n ...githubContext.repo\n });\n\n if (merge_queue_enabled === 'true') {\n const mergeQueueCommitHashes = await getMergeQueueCommitHashes();\n return map(mergeQueueCommitHashes, async sha =>\n octokit.repos.createCommitStatus({\n sha,\n context,\n state: 'success',\n description,\n target_url,\n ...githubContext.repo\n })\n );\n }\n\n const { data: pullRequests } = await octokit.pulls.list({\n state: 'open',\n per_page: 100,\n ...githubContext.repo\n });\n const commitHashesForOpenPullRequests = pullRequests.map(pullRequest => pullRequest.head.sha);\n return map(commitHashesForOpenPullRequests, async sha =>\n octokit.repos.createCommitStatus({\n sha,\n context,\n state: 'success',\n description,\n target_url,\n ...githubContext.repo\n })\n );\n};\n" + "/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { DEFAULT_PIPELINE_DESCRIPTION, DEFAULT_PIPELINE_STATUS, PRODUCTION_ENVIRONMENT } from '../constants';\nimport { DeploymentState, PipelineState } from '../types/github';\nimport { HelperInputs } from '../types/generated';\nimport { context as githubContext } from '@actions/github';\nimport { map } from 'bluebird';\nimport { octokit } from '../octokit';\nimport { getMergeQueueCommitHashes } from '../utils/merge-queue';\n\nexport class NotifyPipelineComplete extends HelperInputs {\n declare context?: string;\n declare description?: string;\n declare environment?: string;\n declare target_url?: string;\n declare merge_queue_enabled?: string;\n declare state?: string;\n declare sha?: string;\n}\n\nexport const notifyPipelineComplete = async ({\n context = DEFAULT_PIPELINE_STATUS,\n description = DEFAULT_PIPELINE_DESCRIPTION,\n environment = PRODUCTION_ENVIRONMENT,\n target_url,\n merge_queue_enabled,\n state = 'success',\n sha\n}: NotifyPipelineComplete) => {\n const { data: deployments } = await octokit.repos.listDeployments({\n sha,\n environment,\n ...githubContext.repo\n });\n const deployment_id = deployments.find(Boolean)?.id;\n if (!deployment_id) return;\n await octokit.repos.createDeploymentStatus({\n environment,\n deployment_id,\n state: state as DeploymentState,\n description,\n target_url,\n ...githubContext.repo\n });\n\n if (merge_queue_enabled === 'true') {\n const mergeQueueCommitHashes = await getMergeQueueCommitHashes();\n return map(mergeQueueCommitHashes, async sha =>\n octokit.repos.createCommitStatus({\n sha,\n context,\n state: state as PipelineState,\n description,\n target_url,\n ...githubContext.repo\n })\n );\n }\n\n const { data: pullRequests } = await octokit.pulls.list({\n state: 'open',\n per_page: 100,\n ...githubContext.repo\n });\n const commitHashesForOpenPullRequests = pullRequests.map(pullRequest => pullRequest.head.sha);\n return map(commitHashesForOpenPullRequests, async sha =>\n octokit.repos.createCommitStatus({\n sha,\n context,\n state: state as PipelineState,\n description,\n target_url,\n ...githubContext.repo\n })\n );\n};\n" ], - "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBA;AAIO,MAAM,+BAA+B,aAAa;AAMzD;AAEO,IAAM,yBAAyB;AAAA,EACpC,oBAAU;AAAA,EACV,cAAc;AAAA,EACd,cAAc;AAAA,EACd;AAAA,EACA;AAAA,MAC4B;AAAA,EAC5B,QAAQ,MAAM,gBAAgB,MAAM,QAAQ,MAAM,gBAAgB;AAAA,IAChE;AAAA,OACG,QAAc;AAAA,EACnB,CAAC;AAAA,EACD,MAAM,gBAAgB,YAAY,KAAK,OAAO,GAAG;AAAA,EACjD,IAAI,CAAC;AAAA,IAAe;AAAA,EACpB,MAAM,QAAQ,MAAM,uBAAuB;AAAA,IACzC;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,OACG,QAAc;AAAA,EACnB,CAAC;AAAA,EAED,IAAI,wBAAwB,QAAQ;AAAA,IAClC,MAAM,yBAAyB,MAAM,0BAA0B;AAAA,IAC/D,OAAO,oBAAI,wBAAwB,OAAM,QACvC,QAAQ,MAAM,mBAAmB;AAAA,MAC/B;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,SACG,QAAc;AAAA,IACnB,CAAC,CACH;AAAA,EACF;AAAA,EAEA,QAAQ,MAAM,iBAAiB,MAAM,QAAQ,MAAM,KAAK;AAAA,IACtD,OAAO;AAAA,IACP,UAAU;AAAA,OACP,QAAc;AAAA,EACnB,CAAC;AAAA,EACD,MAAM,kCAAkC,aAAa,IAAI,iBAAe,YAAY,KAAK,GAAG;AAAA,EAC5F,OAAO,oBAAI,iCAAiC,OAAM,QAChD,QAAQ,MAAM,mBAAmB;AAAA,IAC/B;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,OACG,QAAc;AAAA,EACnB,CAAC,CACH;AAAA;", - "debugId": "3D89CC9C798FA26064756E2164756E21", + "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBA;AAIO,MAAM,+BAA+B,aAAa;AAQzD;AAEO,IAAM,yBAAyB;AAAA,EACpC,oBAAU;AAAA,EACV,cAAc;AAAA,EACd,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,MAC4B;AAAA,EAC5B,QAAQ,MAAM,gBAAgB,MAAM,QAAQ,MAAM,gBAAgB;AAAA,IAChE;AAAA,IACA;AAAA,OACG,QAAc;AAAA,EACnB,CAAC;AAAA,EACD,MAAM,gBAAgB,YAAY,KAAK,OAAO,GAAG;AAAA,EACjD,IAAI,CAAC;AAAA,IAAe;AAAA,EACpB,MAAM,QAAQ,MAAM,uBAAuB;AAAA,IACzC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,OACG,QAAc;AAAA,EACnB,CAAC;AAAA,EAED,IAAI,wBAAwB,QAAQ;AAAA,IAClC,MAAM,yBAAyB,MAAM,0BAA0B;AAAA,IAC/D,OAAO,oBAAI,wBAAwB,OAAM,QACvC,QAAQ,MAAM,mBAAmB;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,SACG,QAAc;AAAA,IACnB,CAAC,CACH;AAAA,EACF;AAAA,EAEA,QAAQ,MAAM,iBAAiB,MAAM,QAAQ,MAAM,KAAK;AAAA,IACtD,OAAO;AAAA,IACP,UAAU;AAAA,OACP,QAAc;AAAA,EACnB,CAAC;AAAA,EACD,MAAM,kCAAkC,aAAa,IAAI,iBAAe,YAAY,KAAK,GAAG;AAAA,EAC5F,OAAO,oBAAI,iCAAiC,OAAM,QAChD,QAAQ,MAAM,mBAAmB;AAAA,IAC/B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,OACG,QAAc;AAAA,EACnB,CAAC,CACH;AAAA;", + "debugId": "3185D1B530D6728564756E2164756E21", "names": [] } \ No newline at end of file diff --git a/src/helpers/notify-pipeline-complete.ts b/src/helpers/notify-pipeline-complete.ts index f8f3f112..bfd67ade 100644 --- a/src/helpers/notify-pipeline-complete.ts +++ b/src/helpers/notify-pipeline-complete.ts @@ -12,6 +12,7 @@ limitations under the License. */ import { DEFAULT_PIPELINE_DESCRIPTION, DEFAULT_PIPELINE_STATUS, PRODUCTION_ENVIRONMENT } from '../constants'; +import { DeploymentState, PipelineState } from '../types/github'; import { HelperInputs } from '../types/generated'; import { context as githubContext } from '@actions/github'; import { map } from 'bluebird'; @@ -24,6 +25,8 @@ export class NotifyPipelineComplete extends HelperInputs { declare environment?: string; declare target_url?: string; declare merge_queue_enabled?: string; + declare state?: string; + declare sha?: string; } export const notifyPipelineComplete = async ({ @@ -31,9 +34,12 @@ export const notifyPipelineComplete = async ({ description = DEFAULT_PIPELINE_DESCRIPTION, environment = PRODUCTION_ENVIRONMENT, target_url, - merge_queue_enabled + merge_queue_enabled, + state = 'success', + sha }: NotifyPipelineComplete) => { const { data: deployments } = await octokit.repos.listDeployments({ + sha, environment, ...githubContext.repo }); @@ -42,7 +48,7 @@ export const notifyPipelineComplete = async ({ await octokit.repos.createDeploymentStatus({ environment, deployment_id, - state: 'success', + state: state as DeploymentState, description, target_url, ...githubContext.repo @@ -54,7 +60,7 @@ export const notifyPipelineComplete = async ({ octokit.repos.createCommitStatus({ sha, context, - state: 'success', + state: state as PipelineState, description, target_url, ...githubContext.repo @@ -72,7 +78,7 @@ export const notifyPipelineComplete = async ({ octokit.repos.createCommitStatus({ sha, context, - state: 'success', + state: state as PipelineState, description, target_url, ...githubContext.repo diff --git a/test/helpers/notify-pipeline-complete.test.ts b/test/helpers/notify-pipeline-complete.test.ts index b27ac212..844254dc 100644 --- a/test/helpers/notify-pipeline-complete.test.ts +++ b/test/helpers/notify-pipeline-complete.test.ts @@ -152,4 +152,136 @@ describe('notify-pipeline-complete', () => { ...context.repo }); }); + + it('should notify with the provided state for open pull requests', async () => { + await notifyPipelineComplete({ state: 'failure' }); + + expect(octokit.repos.createCommitStatus).toHaveBeenCalledTimes(3); + expect(octokit.repos.createCommitStatus).toHaveBeenCalledWith({ + sha: 'sha 1', + context: DEFAULT_PIPELINE_STATUS, + state: 'failure', + description, + ...context.repo + }); + expect(octokit.repos.createCommitStatus).toHaveBeenCalledWith({ + sha: 'sha 2', + context: DEFAULT_PIPELINE_STATUS, + state: 'failure', + description, + ...context.repo + }); + expect(octokit.repos.createCommitStatus).toHaveBeenCalledWith({ + sha: 'sha 3', + context: DEFAULT_PIPELINE_STATUS, + state: 'failure', + description, + ...context.repo + }); + + expect(octokit.repos.createDeploymentStatus).toHaveBeenCalledWith({ + state: 'failure', + environment: PRODUCTION_ENVIRONMENT, + deployment_id: 123, + description, + ...context.repo + }); + }); + + it('should notify with the provided state when merge queue is enabled', async () => { + (octokit.repos.listBranches as unknown as Mock).mockImplementation(async ({ page }: { page: number }) => + page > 1 + ? { data: [] } + : { + data: [ + { + name: 'some-branch', + commit: { sha: 'normal sha 1' } + }, + { + name: 'gh-readonly-queue/default-branch/pr-123-79a5ad2b1a46f6b5d77e02573937667979635f27', + commit: { sha: 'merge queue sha 1' } + }, + { + name: 'gh-readonly-queue/default-branch/pr-456-79a5ad2b1a46f6b5d77e02573937667979635f27', + commit: { sha: 'merge queue sha 2' } + } + ] + } + ); + await notifyPipelineComplete({ merge_queue_enabled: 'true', state: 'failure' }); + + expect(octokit.pulls.list).not.toHaveBeenCalled(); + + expect(octokit.repos.createCommitStatus).toHaveBeenCalledTimes(2); + expect(octokit.repos.createCommitStatus).toHaveBeenCalledWith({ + sha: 'merge queue sha 1', + context: DEFAULT_PIPELINE_STATUS, + state: 'failure', + description, + ...context.repo + }); + expect(octokit.repos.createCommitStatus).toHaveBeenCalledWith({ + sha: 'merge queue sha 2', + context: DEFAULT_PIPELINE_STATUS, + state: 'failure', + description, + ...context.repo + }); + + expect(octokit.repos.createDeploymentStatus).toHaveBeenCalledWith({ + state: 'failure', + environment: PRODUCTION_ENVIRONMENT, + deployment_id: 123, + description, + ...context.repo + }); + }); + + it('should list deployments without a sha when none is provided', async () => { + await notifyPipelineComplete({}); + + expect(octokit.repos.listDeployments).toHaveBeenCalledWith({ + environment: PRODUCTION_ENVIRONMENT, + ...context.repo + }); + expect(octokit.repos.listDeployments).not.toHaveBeenCalledWith(expect.objectContaining({ sha: expect.anything() })); + }); + + it('should mark the deployment for the provided sha', async () => { + (octokit.repos.listDeployments as unknown as Mock).mockImplementationOnce(async () => ({ + data: [{ id: 456, environment: 'production' }] + })); + await notifyPipelineComplete({ sha: 'target sha', state: 'failure' }); + + expect(octokit.repos.listDeployments).toHaveBeenCalledWith({ + sha: 'target sha', + environment: PRODUCTION_ENVIRONMENT, + ...context.repo + }); + + expect(octokit.repos.createDeploymentStatus).toHaveBeenCalledWith({ + state: 'failure', + environment: PRODUCTION_ENVIRONMENT, + deployment_id: 456, + description, + ...context.repo + }); + + expect(octokit.repos.createCommitStatus).toHaveBeenCalledTimes(3); + expect(octokit.repos.createCommitStatus).toHaveBeenCalledWith({ + sha: 'sha 1', + context: DEFAULT_PIPELINE_STATUS, + state: 'failure', + description, + ...context.repo + }); + expect(octokit.repos.createCommitStatus).not.toHaveBeenCalledWith({ + sha: 'target sha', + context: DEFAULT_PIPELINE_STATUS, + state: 'failure', + description, + ...context.repo + }); + }); });