From 37048900cd10498b7db3520700295a0b7c6b5304 Mon Sep 17 00:00:00 2001 From: Josh Brucker Date: Mon, 1 Apr 2024 17:43:14 -0400 Subject: [PATCH] Add param support for payload configurations --- README.md | 32 ++++++++++++++++++++------------ bin/out.js | 8 ++++++-- bin/out.test.js | 9 +++++---- bin/validate.js | 16 ++++++++++++++-- bin/validate.test.js | 31 ++++++++++++++++++++++++++----- 5 files changed, 71 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 27e6292..43bac65 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ resources: github_token: ((github-token)) ``` -- `github_api`: *Required.* The Github API URL for your repo. -- `github_token`: *Required.* [A Github token with the `admin:repo_hook` scope.](https://github.com/settings/tokens/new?scopes=admin:repo_hook) Additionally, the token's account must [be an administrator of your repo](https://help.github.com/en/articles/managing-an-individuals-access-to-an-organization-repository) to manage the repo's webhooks. +- `github_api`: *Required.* The Github API URL for your repo. +- `github_token`: *Required.* [A Github token with the `admin:repo_hook` scope.](https://github.com/settings/tokens/new?scopes=admin:repo_hook) Additionally, the token's account must [be an administrator of your repo](https://help.github.com/en/articles/managing-an-individuals-access-to-an-organization-repository) to manage the repo's webhooks. Behavior -------- @@ -54,18 +54,26 @@ Create or delete a webhook using the configured parameters. pipeline_instance_vars: { your_instance_var_name: value } + payload_base_url: your-payload-base-url + payload_content_type: json + payload_secret: your-payload-secret ``` -- `org`: *Required.* Your github organization. -- `repo`: *Required.* Your github repository. -- `resource_name`: *Required.* Name of the resource to be associated with your webhook. -- `webhook_token`: *Required.* Arbitrary string to identify your webhook. Must match the `webhook_token` property of the resource your webhook points to. -- `operation`: *Required.* - - `create` to create a new webhook. Updates existing webhook if your configuration differs from remote. - - `delete` to delete an existing webhook. Outputs current timestamp on non-existing webhooks. -- `events`: *Optional*. An array of [events](https://developer.github.com/webhooks/#events) which will trigger your webhook. Default: `push` -- `pipeline`: *Optional.* Defaults to the name of the pipeline executing the task -- `pipeline_instance_vars`: *Optional.* Instance vars to append to the webhook url. These help Concourse identify which [instance pipeline](https://concourse-ci.org/resources.html#schema.resource.webhook_token) it should invoke +- `org`: *Required.* Your github organization. +- `repo`: *Required.* Your github repository. +- `resource_name`: *Required.* Name of the resource to be associated with your webhook. +- `webhook_token`: *Required.* Arbitrary string to identify your webhook. Must match the `webhook_token` property of the resource your webhook points to. +- `operation`: *Required.* + - `create` to create a new webhook. Updates existing webhook if your configuration differs from remote. + - `delete` to delete an existing webhook. Outputs current timestamp on non-existing webhooks. +- `events`: *Optional*. An array of [events](https://developer.github.com/webhooks/#events) which will trigger your webhook. Default: `push` +- `pipeline`: *Optional.* Defaults to the name of the pipeline executing the task +- `pipeline_instance_vars`: *Optional.* Instance vars to append to the webhook url. These help Concourse identify which [instance pipeline](https://concourse-ci.org/resources.html#schema.resource.webhook_token) it should invoke +- `payload_base_url`: *Optional.* The base URL to send the webhook payload to. Defaults to the external Concourse URL of the pipeline executing the task. +- `payload_content_type`: *Optional.* Default: `json` + - `json` to serialize payloads to JSON. + - `form` to serialize payloads to x-www-form-urlencoded. +- `payload_secret`: *Optional.* Secret that is used as the key to generate [delivery signature headers](https://docs.github.com/en/webhooks/webhook-events-and-payloads#delivery-headers), if the destination requires it for delivery validation. ## Example Include the github-webhook-resource in your pipeline.yml file diff --git a/bin/out.js b/bin/out.js index 8ea7752..54a900b 100755 --- a/bin/out.js +++ b/bin/out.js @@ -47,7 +47,10 @@ stdin.on('end', function () { function buildUrl(source, params) { const instanceVars = buildInstanceVariables(params); - return encodeURI(`${env.ATC_EXTERNAL_URL}/api/v1/teams/${env.BUILD_TEAM_NAME}/pipelines/${params.pipeline ? params.pipeline : env.BUILD_PIPELINE_NAME}/resources/${params.resource_name}/check/webhook?webhook_token=${params.webhook_token}${instanceVars}`); + const payloadBaseUrl = params.payload_base_url ? params.payload_base_url : env.ATC_EXTERNAL_URL; + const pipeline = params.pipeline ? params.pipeline : env.BUILD_PIPELINE_NAME; + + return encodeURI(`${payloadBaseUrl}/api/v1/teams/${env.BUILD_TEAM_NAME}/pipelines/${pipeline}/resources/${params.resource_name}/check/webhook?webhook_token=${params.webhook_token}${instanceVars}`); } function buildInstanceVariables(params) { @@ -84,7 +87,8 @@ async function processWebhook(source, params) { const config = { 'url': url, - 'content-type': 'json' + 'content_type': params.payload_content_type ? params.payload_content_type : 'json', + 'secret': params.payload_secret }; const body = { diff --git a/bin/out.test.js b/bin/out.test.js index 6d886b7..f32ba18 100644 --- a/bin/out.test.js +++ b/bin/out.test.js @@ -43,7 +43,7 @@ describe('out', () => { }); describe('buildUrl', () => { - it('defaults to using the current pipeline name', () => { + it('defaults to using the concourse env vars', () => { process.env.ATC_EXTERNAL_URL = 'https://example.com'; process.env.BUILD_PIPELINE_NAME = 'pipeline'; process.env.BUILD_TEAM_NAME = 'team'; @@ -55,17 +55,18 @@ describe('out', () => { expect(instanceVar).toEqual("https://example.com/api/v1/teams/team/pipelines/pipeline/resources/resource/check/webhook?webhook_token=token") }); - it('prefers the pipeline name from params', () => { + it('prefers the concourse vars from params', () => { process.env.ATC_EXTERNAL_URL = 'https://example.com'; process.env.BUILD_PIPELINE_NAME = 'pipeline'; process.env.BUILD_TEAM_NAME = 'team'; const params = { - pipeline: 'another-pipeline', + pipeline: 'param-pipeline', + payload_base_url: 'https://param-example.com', resource_name: 'resource', webhook_token: 'token' } const instanceVar = out.buildUrl(null, params); - expect(instanceVar).toEqual("https://example.com/api/v1/teams/team/pipelines/another-pipeline/resources/resource/check/webhook?webhook_token=token") + expect(instanceVar).toEqual("https://param-example.com/api/v1/teams/team/pipelines/param-pipeline/resources/resource/check/webhook?webhook_token=token") }); }); }); diff --git a/bin/validate.js b/bin/validate.js index 4320a17..dc932f2 100644 --- a/bin/validate.js +++ b/bin/validate.js @@ -4,6 +4,7 @@ require('ajv-errors')(ajv); require('ajv-keywords')(ajv, 'transform'); const validOperations = ['create', 'delete']; +const validContentTypes = ['form', 'json']; const envSchema = { type: 'object', @@ -47,13 +48,24 @@ const configSchema = { enum: validOperations, errorMessage: { enum: 'must be either create or delete' } }, - pipeline: { + pipeline: { type: 'string', transform: ['trim', 'toLowerCase'] }, - pipeline_instance_vars: { + pipeline_instance_vars: { type: 'object', }, + payload_base_url: { + type: 'string', + transform: ['trim', 'toLowerCase'] + }, + payload_content_type: { + type: 'string', + transform: ['trim', 'toEnumCase'], + enum: validContentTypes, + errorMessage: { enum: 'must be either form or json' } + }, + payload_secret: { type: 'string' } }, required: ['org', 'repo', 'resource_name', 'webhook_token', 'operation'] }, diff --git a/bin/validate.test.js b/bin/validate.test.js index 50e07f0..e297e01 100644 --- a/bin/validate.test.js +++ b/bin/validate.test.js @@ -118,6 +118,9 @@ describe('validate.input', () => { 'params.webhook_token', 'params.operation', 'params.pipeline', + 'params.payload_base_url', + 'params.payload_content_type', + 'params.payload_secret' ]; constrainedFields.forEach(field => { @@ -131,7 +134,11 @@ describe('validate.input', () => { repo: '', resource_name: '', webhook_token: '', - operation: 'create' + operation: 'create', + pipeline: '', + payload_base_url: '', + payload_content_type: 'json', + payload_secret: '' } }; @@ -158,7 +165,9 @@ describe('validate.input', () => { operation: 'CrEaTe', events: ['pUsH'], pipeline: 'mYPipeline', - pipeline_instance_vars: {} + pipeline_instance_vars: {}, + payload_base_url: 'hTTps://ExampLe.com', + payload_content_type: 'JsOn' } }; @@ -166,6 +175,8 @@ describe('validate.input', () => { expect(config.params.operation).toBe('create'); expect(config.params.events).toEqual(['push']); expect(config.params.pipeline).toBe('mypipeline'); + expect(config.params.payload_base_url).toBe('https://example.com'); + expect(config.params.payload_content_type).toBe('json'); }); it('trims whitespace', () => { @@ -182,7 +193,9 @@ describe('validate.input', () => { operation: ' create ', events: [' push '], pipeline: ' mypipeline ', - pipeline_instance_vars: {} + pipeline_instance_vars: {}, + payload_base_url: ' https://example.com ', + payload_content_type: ' json ' } }; @@ -190,6 +203,8 @@ describe('validate.input', () => { expect(config.params.operation).toBe('create'); expect(config.params.events).toEqual(['push']); expect(config.params.pipeline).toBe('mypipeline'); + expect(config.params.payload_base_url).toBe('https://example.com'); + expect(config.params.payload_content_type).toBe('json'); }); it('checks fields with array constraint', () => { @@ -211,7 +226,10 @@ describe('validate.input', () => { operation: 'create', events: [], pipeline: '', - pipeline_instance_vars: {} + pipeline_instance_vars: {}, + payload_base_url: '', + payload_content_type: 'json', + payload_secret: '' } }; @@ -243,7 +261,10 @@ describe('validate.input', () => { operation: 'create', events: [], pipeline: '', - pipeline_instance_vars: {} + pipeline_instance_vars: {}, + payload_base_url: '', + payload_content_type: 'json', + payload_secret: '' } };