├── .eslintrc ├── .gitignore ├── .taskcluster.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── assets ├── error.svg ├── failure.svg ├── newrepo.svg ├── pending.svg └── success.svg ├── config.yml ├── docs ├── intro.md ├── taskcluster-yml-v0.md └── taskcluster-yml-v1.md ├── package.json ├── schemas ├── constants.yml └── v1 │ ├── build-list.yml │ ├── create-comment.yml │ ├── create-status.yml │ ├── github-pull-request-message.yml │ ├── github-push-message.yml │ ├── github-release-message.yml │ ├── repository.yml │ ├── task-group-creation-requested.yml │ ├── taskcluster-github-config.v1.yml │ └── taskcluster-github-config.yml ├── src ├── api.js ├── data.js ├── exchanges.js ├── github-auth.js ├── handlers.js ├── intree.js ├── main.js ├── pr-allowed.js └── tc-yaml.js ├── test ├── api_test.js ├── checkStaging.js ├── data │ ├── configs │ │ ├── taskcluster.branchlimited.v0.yml │ │ ├── taskcluster.branchlimited.v1.yml │ │ ├── taskcluster.exclude-error.yml │ │ ├── taskcluster.exclude.yml │ │ ├── taskcluster.non-github.v0.yml │ │ ├── taskcluster.non-github.v1.yml │ │ ├── taskcluster.pull_with_exclude.yml │ │ ├── taskcluster.push_pull_release.v0.yml │ │ ├── taskcluster.push_pull_release.v1.yml │ │ ├── taskcluster.release_single.v0.yml │ │ ├── taskcluster.release_single.v1.yml │ │ ├── taskcluster.single.v0.yml │ │ ├── taskcluster.single.v1.yml │ │ ├── taskcluster.star.yml │ │ ├── taskcluster.tag.branchlimited.v0.yml │ │ ├── taskcluster.tag.branchlimited.v1.yml │ │ ├── taskcluster.tag_single.v0.yml │ │ └── taskcluster.tag_single.v1.yml │ └── webhooks │ │ ├── webhook.pull_request.close.json │ │ ├── webhook.pull_request.open.json │ │ ├── webhook.push.bad_secret.json │ │ ├── webhook.push.json │ │ ├── webhook.push.no_secret.json │ │ ├── webhook.push.offbranch.json │ │ ├── webhook.push.unicode.json │ │ ├── webhook.release.bad_secret.json │ │ ├── webhook.release.json │ │ ├── webhook.tag_push.json │ │ └── webhook.unknown_event.json ├── fake_github_test.js ├── github-auth.js ├── handler_test.js ├── helper.js ├── intree_test.js ├── invalid-yaml.json ├── mocha.opts ├── pr-allowed_test.js ├── pulse_test.js ├── references_test.js ├── sync_test.js ├── tc-yaml_test.js ├── valid-yaml.json └── webhook_test.js ├── user-config-example.yml └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | 'extends': 'eslint-config-taskcluster' 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | config/development.js 3 | *.sw[a-z] 4 | taskcluster-github.conf.json 5 | npm-debug.log 6 | .test/ 7 | lib/ 8 | user-config.yml 9 | yarn-error.log 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /.taskcluster.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | policy: 3 | pullRequests: public 4 | tasks: 5 | - $if: 'tasks_for == "github-pull-request" && event["action"] in ["opened", "reopened", "synchronize"]' 6 | then: 7 | taskId: {$eval: as_slugid("pr_task")} 8 | created: {$fromNow: ''} 9 | deadline: {$fromNow: '1 hour'} 10 | provisionerId: aws-provisioner-v1 11 | workerType: github-worker 12 | scopes: 13 | - secrets:get:project/taskcluster/testing/taskcluster-github 14 | payload: 15 | maxRunTime: 600 16 | image: "node:8" 17 | env: 18 | DEBUG: "* -mocha* -nock* -express* -body-parser* -eslint*" 19 | features: 20 | taskclusterProxy: true 21 | command: 22 | - "/bin/bash" 23 | - "-lc" 24 | - "git clone ${event.pull_request.head.repo.git_url} repo && cd repo && git checkout ${event.pull_request.head.sha} && yarn && yarn test" 25 | metadata: 26 | name: "Taskcluster GitHub Tests" 27 | description: "All tests" 28 | owner: ${event.pull_request.user.login}@users.noreply.github.com 29 | source: ${event.repository.url} 30 | - $if: 'tasks_for == "github-push"' 31 | then: 32 | taskId: {$eval: as_slugid("push_task")} 33 | created: {$fromNow: ''} 34 | deadline: {$fromNow: '1 hour'} 35 | provisionerId: aws-provisioner-v1 36 | workerType: github-worker 37 | scopes: 38 | - secrets:get:project/taskcluster/testing/taskcluster-github 39 | payload: 40 | maxRunTime: 600 41 | image: "node:8" 42 | env: 43 | DEBUG: "* -mocha* -nock* -express* -body-parser* -eslint*" 44 | NO_TEST_SKIP: "true" 45 | features: 46 | taskclusterProxy: true 47 | command: 48 | - "/bin/bash" 49 | - "-lc" 50 | - "git clone ${event.repository.url} repo && cd repo && git checkout ${event.after} && yarn && yarn test" 51 | metadata: 52 | name: "Taskcluster GitHub Tests" 53 | description: "All tests" 54 | owner: ${event.pusher.name}@users.noreply.github.com 55 | source: ${event.repository.url} 56 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Mozilla Community Participation Guidelines 2 | 3 | The most recent version of the Mozilla Community Participation Guideline can always be found here: https://www.mozilla.org/en-US/about/governance/policies/participation/ 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We welcome pull requests from everyone. We do expect everyone to adhere to the [Mozilla Community Participation Guidelines][participation]. 4 | 5 | If you're trying to figure out what to work on, here are some places to find suitable projects: 6 | * [Good first bugs][goodfirstbug]: these are scoped to make it easy for first-time contributors to get their feet wet with Taskcluster code. 7 | * [Mentored bugs][bugsahoy]: these are slightly more involved projects that may require insight or guidance from someone on the Taskcluster team. 8 | * [Full list of open issues][issues]: everything else 9 | 10 | If the project you're interested in working on isn't covered by a bug or issue, or you're unsure about how to proceed on an existing issue, it's a good idea to talk to someone on the Taskcluster team before you go too far down a particular path. You can find us in the #taskcluster channel on [Mozilla's IRC server][irc] to discuss. You can also simply add a comment to the issue or bug. 11 | 12 | Once you've found an issue to work on and written a patch, submit a pull request. Some things that will increase the chance that your pull request is accepted: 13 | 14 | * Follow our [best practices][bestpractices]. 15 | * This includes [writing or updating tests][testing]. 16 | * Write a [good commit message][commit]. 17 | 18 | Welcome to the team! 19 | 20 | [participation]: https://www.mozilla.org/en-US/about/governance/policies/participation/ 21 | [issues]: ../../issues 22 | [bugsahoy]: https://www.joshmatthews.net/bugsahoy/?taskcluster=1 23 | [goodfirstbug]: http://www.joshmatthews.net/bugsahoy/?taskcluster=1&simple=1 24 | [irc]: https://wiki.mozilla.org/IRC 25 | [bestpractices]: https://docs.taskcluster.net/docs/manual/design/devel/best-practices 26 | [testing]: https://docs.taskcluster.net/docs/manual/design/devel/best-practices/testing 27 | [commit]: https://docs.taskcluster.net/docs/manual/design/devel/best-practices/commits 28 | 29 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node src/main server 2 | worker: node src/main worker 3 | sync: node src/main syncInstallations 4 | write-docs: node src/main writeDocs 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moved to Monorepo 2 | 3 | This project has been moved into the Taskcluster "monorepo" at https://github.com/taskcluster/taskcluster. 4 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taskcluster-github", 3 | "scripts": { 4 | }, 5 | "env": { 6 | "AZURE_ACCOUNT_NAME": { 7 | "required": true 8 | }, 9 | "DEBUG": { 10 | "required": true 11 | }, 12 | "GITHUB_OAUTH_TOKEN": { 13 | "required": true 14 | }, 15 | "NODE_ENV": { 16 | "required": true 17 | }, 18 | "PULSE_PASSWORD": { 19 | "required": true 20 | }, 21 | "PULSE_USERNAME": { 22 | "required": true 23 | }, 24 | "TASKCLUSTER_ACCESS_TOKEN": { 25 | "required": true 26 | }, 27 | "TASKCLUSTER_CLIENT_ID": { 28 | "required": true 29 | }, 30 | "WEBHOOK_SECRET": { 31 | "required": true 32 | } 33 | }, 34 | "formation": { 35 | }, 36 | "addons": [ 37 | 38 | ], 39 | "buildpacks": [ 40 | { 41 | "url": "heroku/nodejs" 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /assets/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 35 | 38 | 42 | 43 | 46 | 50 | 51 | 55 | 58 | 61 | 64 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /assets/failure.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 35 | 38 | 42 | 50 | 54 | 55 | 59 | 63 | 67 | 70 | 73 | 76 | 79 | 82 | 85 | 88 | 91 | 94 | 97 | 100 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /assets/newrepo.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 35 | 38 | 42 | 43 | 46 | 50 | 51 | 54 | 58 | 59 | 62 | 66 | 67 | 70 | 74 | 75 | 78 | 82 | 83 | 87 | 90 | 93 | 96 | 99 | 102 | 105 | 108 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /assets/success.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 35 | 38 | 42 | 49 | 53 | 54 | 58 | 61 | 64 | 67 | 70 | 73 | 76 | 79 | 82 | 85 | 88 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | defaults: 3 | app: 4 | jobQueue: null 5 | deprecatedResultStatusQueue: null 6 | deprecatedInitialStatusQueue: null 7 | resultStatusQueue: null 8 | initialStatusQueue: null 9 | buildsTableName: 'TaskclusterGithubBuilds' 10 | ownersDirectoryTableName: 'TaskclusterIntegrationOwners' 11 | checkRunsTableName: 'TaskclusterCheckRuns' 12 | checkTaskRoute: 'checks' 13 | statusTaskRoute: 'statuses' 14 | publishMetaData: !env:bool PUBLISH_METADATA 15 | statusContext: 'Taskcluster' 16 | botName: !env BOT_USERNAME 17 | 18 | taskcluster: 19 | rootUrl: !env TASKCLUSTER_ROOT_URL 20 | credentials: 21 | clientId: !env TASKCLUSTER_CLIENT_ID 22 | accessToken: !env TASKCLUSTER_ACCESS_TOKEN 23 | 24 | github: 25 | credentials: 26 | privatePEM: !env GITHUB_PRIVATE_PEM 27 | integrationId: !env GITHUB_INTEGRATION_ID 28 | 29 | webhook: 30 | secret: !env:list WEBHOOK_SECRET 31 | 32 | intree: 33 | provisionerId: !env PROVISIONER_ID 34 | workerType: !env WORKER_TYPE 35 | 36 | azure: 37 | accountId: !env AZURE_ACCOUNT_NAME 38 | 39 | monitoring: 40 | project: !env MONITORING_PROJECT 41 | enable: !env:bool MONITORING_ENABLE 42 | 43 | server: 44 | port: !env:number PORT 45 | env: 'development' 46 | forceSSL: false 47 | trustProxy: false 48 | 49 | pulse: 50 | hostname: !env PULSE_HOSTNAME 51 | username: !env PULSE_USERNAME 52 | password: !env PULSE_PASSWORD 53 | vhost: !env PULSE_VHOST 54 | namespace: !env PULSE_NAMESPACE 55 | 56 | aws: 57 | accessKeyId: !env AWS_ACCESS_KEY 58 | secretAccessKey: !env AWS_SECRET_KEY 59 | region: 'us-west-2' 60 | apiVersion: '2014-01-01' 61 | 62 | production: 63 | app: 64 | jobQueue: 'jobs' 65 | deprecatedResultStatusQueue: 'stat-result' 66 | deprecatedInitialStatusQueue: 'stat-init' 67 | resultStatusQueue: 'ch-result' 68 | initialStatusQueue: 'ch-init' 69 | botName: 'taskcluster[bot]' 70 | 71 | intree: 72 | provisionerId: 'aws-provisioner-v1' 73 | workerType: 'github-worker' 74 | 75 | server: 76 | env: 'production' 77 | forceSSL: !env:bool FORCE_SSL 78 | trustProxy: !env:bool TRUST_PROXY 79 | 80 | taskcluster: 81 | schedulerId: taskcluster-github 82 | 83 | pulse: 84 | namespace: taskcluster-github 85 | 86 | staging: 87 | app: 88 | jobQueue: 'jobs' 89 | deprecatedResultStatusQueue: 'deprecated-statuses' 90 | deprecatedInitialStatusQueue: 'deprecated-tasks' 91 | resultStatusQueue: 'checks-statuses' 92 | initialStatusQueue: 'checks-tasks' 93 | buildsTableName: 'TaskclusterGithubBuildsStaging' 94 | ownersDirectoryTableName: 'TaskclusterIntegrationOwnersStaging' 95 | name: 'tc-github-staging' 96 | statusContext: 'Taskcluster-Staging' 97 | botName: 'taskcluster-staging[bot]' 98 | 99 | intree: 100 | provisionerId: 'aws-provisioner-v1' 101 | workerType: 'github-worker' 102 | 103 | server: 104 | env: 'production' 105 | forceSSL: true 106 | # We trust the proxy on heroku, as the SSL end-point provided by heroku 107 | # is a proxy, so we have to trust it. 108 | trustProxy: true 109 | 110 | taskcluster: 111 | schedulerId: tc-gh-staging 112 | 113 | test: 114 | app: 115 | botName: 'magicalTCspirit' 116 | jobQueue: 'test-jobs' 117 | deprecatedResultStatusQueue: 'deprecated-statuses' 118 | deprecatedInitialStatusQueue: 'deprecated-tasks' 119 | resultStatusQueue: 'checks-statuses' 120 | initialStatusQueue: 'checks-tasks' 121 | 122 | webhook: 123 | secret: ['abc123', 'def456'] 124 | 125 | azure: 126 | accountId: 'jungle' 127 | 128 | monitoring: 129 | enable: false 130 | 131 | server: 132 | port: 60415 133 | 134 | intree: 135 | provisionerId: 'dummy-provisioner' 136 | workerType: 'dummy-worker' 137 | 138 | taskcluster: 139 | schedulerId: tc-gh-devel 140 | 141 | pulse: 142 | namespace: 'taskcluster-fake' 143 | hostname: 'test-hostname' 144 | username: 'username' 145 | password: 'password' 146 | vhost: 'test-vhost' 147 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using Taskcluster for Github Projects 3 | order: 10 4 | --- 5 | 6 | Taskcluster is easy to set up for simple CI cases and very expressive 7 | and powerful for more complex cases. It should fit just about any 8 | use case you can think of for CI on a Github project. It is used for 9 | projects as simple to test as calling `npm test` all the way up to 10 | the very complex set of tasks to perform in order to test and build 11 | the Firefox browser. 12 | 13 | The syntax offers an enormous amount of flexibility. [The quickstart tool](https://tools.taskcluster.net/quickstart/) should get you going quickly. 14 | 15 | The eventual goal of this project is to support all platforms and allow users to define workflows for testing, shipping, and landing patches from within their configurations. Currently, we offer mostly Linux support and have Windows available. 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taskcluster-github", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "heroku-prebuild": "echo $SOURCE_VERSION > .git-version", 7 | "lint": "eslint src/*.js test/*.js", 8 | "test": "mocha test/*_test.js", 9 | "pretest": "yarn lint", 10 | "checkStaging": "node test/checkStaging.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/taskcluster/taskcluster-github.git" 15 | }, 16 | "dependencies": { 17 | "@octokit/rest": "^16.0.5", 18 | "ajv": "^6.5.0", 19 | "azure-entities": "^5.1.0", 20 | "bluebird": "^3.5.1", 21 | "debug": "^3.1.0", 22 | "eslint-config-taskcluster": "^3.2.0", 23 | "js-yaml": "^3.10.0", 24 | "json-e": "^2.5.0", 25 | "json-parameterization": "^0.2.0", 26 | "jsonwebtoken": "^8.1.0", 27 | "lodash": "^4.11.1", 28 | "slugid": "^1.1.0", 29 | "taskcluster-client": "^11.0.0", 30 | "taskcluster-lib-api": "12.6.0", 31 | "taskcluster-lib-app": "^10.0.0", 32 | "taskcluster-lib-azure": "^10.0.0", 33 | "taskcluster-lib-docs": "^10.0.0", 34 | "taskcluster-lib-loader": "^10.0.0", 35 | "taskcluster-lib-monitor": "^11.1.1", 36 | "taskcluster-lib-pulse": "^11.1.0", 37 | "taskcluster-lib-urls": "^12.0.0", 38 | "taskcluster-lib-validate": "^12.0.0", 39 | "typed-env-config": "^2.0.0" 40 | }, 41 | "engine-strict": true, 42 | "engines": { 43 | "node": "^8.0.0", 44 | "yarn": "^1.0.0" 45 | }, 46 | "devDependencies": { 47 | "fs-extra": "^4.0.2", 48 | "got": "^8.0.0", 49 | "mocha": "^4.0.1", 50 | "sinon": "^4.1.2", 51 | "taskcluster-lib-references": "^1.3.0", 52 | "taskcluster-lib-testing": "^12.1.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /schemas/constants.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Identifier patterns, min and max length, these limitations are applied to 3 | # all common identifiers. It's not personal, it's just that without these 4 | # limitation, the identifiers won't be useful as routing keys in RabbitMQ 5 | # topic exchanges. Specifically, the length limitation and the fact that 6 | # identifiers can't contain dots `.` is critical. 7 | github-identifier-pattern: "^([a-zA-Z0-9-_%]*)$" 8 | github-identifier-min-length: 1 9 | github-identifier-max-length: 100 10 | github-guid-pattern: "^[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}$" 11 | 12 | identifier-pattern: "^([a-zA-Z0-9-_]*)$" 13 | identifier-min-length: 1 14 | identifier-max-length: 22 15 | 16 | github-installation-minimum: 0 17 | github-installation-maximum: 10000000000 18 | 19 | # Slugid pattern, for when-ever that is useful 20 | # Currently allow all v4 slugs, although we only generate nice slugs 21 | # See https://www.npmjs.com/package/slugid for more info 22 | slugid-pattern: "^[A-Za-z0-9_-]{8}[Q-T][A-Za-z0-9_-][CGKOSWaeimquy26-][A-Za-z0-9_-]{10}[AQgw]$" 23 | 24 | # Message version numbers 25 | message-version: 26 | description: "Message version" 27 | enum: [1] 28 | 29 | # Creation time of tasks 30 | created: 31 | title: "Created" 32 | description: "Creation time of task" 33 | type: "string" 34 | default: "{{ $fromNow }}" 35 | 36 | # Deadline of task 37 | deadline: 38 | title: "Deadline" 39 | description: "Deadline of the task, `pending` and `running` runs are resolved as **failed** if not resolved by other means before the deadline" 40 | type: "string" 41 | default: "{{ '1 day' | $fromNow }}" 42 | -------------------------------------------------------------------------------- /schemas/v1/build-list.yml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-06/schema# 2 | title: "Builds Response" 3 | description: | 4 | A paginated list of builds 5 | type: object 6 | properties: 7 | continuationToken: 8 | type: string 9 | description: Passed back from Azure to allow us to page through long result sets. 10 | builds: 11 | type: array 12 | description: | 13 | A simple list of builds. 14 | items: 15 | title: Build 16 | type: object 17 | properties: 18 | organization: 19 | type: string 20 | minLength: {$const: github-identifier-min-length} 21 | maxLength: {$const: github-identifier-max-length} 22 | pattern: {$const: github-identifier-pattern} 23 | description: Github organization associated with the build. 24 | repository: 25 | type: string 26 | minLength: {$const: github-identifier-min-length} 27 | maxLength: {$const: github-identifier-max-length} 28 | pattern: {$const: github-identifier-pattern} 29 | description: Github repository associated with the build. 30 | sha: 31 | type: string 32 | minLength: 40 33 | maxLength: 40 34 | description: Github revision associated with the build. 35 | state: 36 | type: string 37 | enum: ['pending', 'success', 'error', 'failure'] 38 | description: Github status associated with the build. 39 | taskGroupId: 40 | type: string 41 | pattern: {$const: slugid-pattern} 42 | description: Taskcluster task-group associated with the build. 43 | eventType: 44 | type: string 45 | description: Type of Github event that triggered the build (i.e. push, pull_request.opened). 46 | eventId: 47 | type: string 48 | description: | 49 | The GitHub webhook deliveryId. Extracted from the header 'X-GitHub-Delivery' 50 | oneOf: 51 | - pattern: {$const: github-guid-pattern} 52 | type: string 53 | title: Github GUID 54 | - enum: [Unknown] 55 | type: string 56 | title: Unknown Github GUID 57 | created: 58 | type: string 59 | format: date-time 60 | description: | 61 | The initial creation time of the build. This is when it became pending. 62 | updated: 63 | type: string 64 | format: date-time 65 | description: | 66 | The last updated of the build. If it is done, this is when it finished. 67 | additionalProperties: false 68 | required: 69 | - organization 70 | - repository 71 | - sha 72 | - state 73 | - taskGroupId 74 | - eventType 75 | - eventId 76 | - created 77 | - updated 78 | additionalProperties: false 79 | required: 80 | - builds 81 | -------------------------------------------------------------------------------- /schemas/v1/create-comment.yml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-06/schema# 2 | title: "Create Comment Request" 3 | description: | 4 | Write a new comment on a GitHub Issue or Pull Request. 5 | Full specification on [GitHub docs](https://developer.github.com/v3/issues/comments/#create-a-comment) 6 | type: object 7 | properties: 8 | body: 9 | type: string 10 | description: The contents of the comment. 11 | additionalProperties: false 12 | required: 13 | - body 14 | -------------------------------------------------------------------------------- /schemas/v1/create-status.yml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-06/schema# 2 | title: "Create Status Request" 3 | description: | 4 | Create a commit status on GitHub. 5 | Full specification on [GitHub docs](https://developer.github.com/v3/repos/statuses/#create-a-status) 6 | type: object 7 | properties: 8 | state: 9 | type: string 10 | enum: ['pending', 'success', 'error', 'failure'] 11 | description: The state of the status. 12 | target_url: 13 | type: string 14 | description: The target URL to associate with this status. This URL will be linked from the GitHub UI to allow users to easily see the 'source' of the Status. 15 | description: 16 | type: string 17 | description: A short description of the status. 18 | context: 19 | type: string 20 | description: A string label to differentiate this status from the status of other systems. 21 | additionalProperties: false 22 | required: 23 | - state 24 | -------------------------------------------------------------------------------- /schemas/v1/github-pull-request-message.yml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-06/schema# 2 | title: "GitHub Pull Request Message" 3 | description: | 4 | Message reporting that a GitHub pull request has occurred 5 | type: object 6 | properties: 7 | version: {$const: message-version} 8 | organization: 9 | description: | 10 | The GitHub `organization` which had an event. 11 | type: string 12 | minLength: {$const: github-identifier-min-length} 13 | maxLength: {$const: github-identifier-max-length} 14 | pattern: {$const: github-identifier-pattern} 15 | repository: 16 | description: | 17 | The GitHub `repository` which had an event. 18 | type: string 19 | minLength: {$const: github-identifier-min-length} 20 | maxLength: {$const: github-identifier-max-length} 21 | pattern: {$const: github-identifier-pattern} 22 | installationId: 23 | description: | 24 | The installation which had an event. 25 | type: integer 26 | minimum: {$const: github-installation-minimum} 27 | maximum: {$const: github-installation-maximum} 28 | action: 29 | description: | 30 | The GitHub `action` which triggered an event. 31 | enum: [assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, review_requested, review_request_removed] 32 | eventId: 33 | type: string 34 | description: | 35 | The GitHub webhook deliveryId. Extracted from the header 'X-GitHub-Delivery' 36 | pattern: {$const: github-guid-pattern} 37 | details: 38 | type: object 39 | description: | 40 | Metadata describing the pull request (for version 0) 41 | body: 42 | type: object 43 | description: | 44 | The raw body of github event (for version 1) 45 | tasks_for: 46 | type: string 47 | description: | 48 | The type of the event (for version 1) 49 | branch: 50 | type: string 51 | description: | 52 | The head ref of the event (for version 1) 53 | additionalProperties: false 54 | required: 55 | - version 56 | - organization 57 | - repository 58 | - action 59 | - installationId 60 | - eventId 61 | - body 62 | - tasks_for 63 | - branch 64 | -------------------------------------------------------------------------------- /schemas/v1/github-push-message.yml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-06/schema# 2 | title: "GitHub Push Message" 3 | description: | 4 | Message reporting that a GitHub push has occurred 5 | type: object 6 | properties: 7 | version: {$const: message-version} 8 | organization: 9 | description: | 10 | The GitHub `organization` which had an event. 11 | type: string 12 | minLength: {$const: github-identifier-min-length} 13 | maxLength: {$const: github-identifier-max-length} 14 | pattern: {$const: github-identifier-pattern} 15 | repository: 16 | description: | 17 | The GitHub `repository` which had an event. 18 | type: string 19 | minLength: {$const: github-identifier-min-length} 20 | maxLength: {$const: github-identifier-max-length} 21 | pattern: {$const: github-identifier-pattern} 22 | installationId: 23 | description: | 24 | The installation which had an event. 25 | type: integer 26 | minLength: {$const: github-installation-minimum} 27 | maxLength: {$const: github-installation-maximum} 28 | eventId: 29 | type: string 30 | description: | 31 | The GitHub webhook deliveryId. Extracted from the header 'X-GitHub-Delivery' 32 | pattern: {$const: github-guid-pattern} 33 | details: 34 | type: object 35 | description: | 36 | Metadata describing the push (for version 0) 37 | body: 38 | type: object 39 | description: | 40 | The raw body of github event (for version 1) 41 | tasks_for: 42 | type: string 43 | description: | 44 | The type of the event (for version 1) 45 | branch: 46 | type: string 47 | description: | 48 | The head ref of the event (for version 1) 49 | additionalProperties: false 50 | required: 51 | - version 52 | - organization 53 | - repository 54 | - installationId 55 | - eventId 56 | - body 57 | - tasks_for 58 | - branch 59 | -------------------------------------------------------------------------------- /schemas/v1/github-release-message.yml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-06/schema# 2 | title: "GitHub Release Message" 3 | description: | 4 | Message reporting that a GitHub release has occurred 5 | type: object 6 | properties: 7 | version: {$const: message-version} 8 | organization: 9 | description: | 10 | The GitHub `organization` which had an event. 11 | type: string 12 | minLength: {$const: github-identifier-min-length} 13 | maxLength: {$const: github-identifier-max-length} 14 | pattern: {$const: github-identifier-pattern} 15 | repository: 16 | description: | 17 | The GitHub `repository` which had an event. 18 | type: string 19 | minLength: {$const: github-identifier-min-length} 20 | maxLength: {$const: github-identifier-max-length} 21 | pattern: {$const: github-identifier-pattern} 22 | installationId: 23 | description: | 24 | The installation which had an event. 25 | type: integer 26 | minimum: {$const: github-installation-minimum} 27 | maximum: {$const: github-installation-maximum} 28 | eventId: 29 | type: string 30 | description: | 31 | The GitHub webhook deliveryId. Extracted from the header 'X-GitHub-Delivery' 32 | pattern: {$const: github-guid-pattern} 33 | details: 34 | type: object 35 | description: | 36 | Metadata describing the release (for version 0) 37 | body: 38 | type: object 39 | description: | 40 | The raw body of github event (for version 1) 41 | tasks_for: 42 | type: string 43 | description: | 44 | The type of the event (for version 1) 45 | branch: 46 | type: string 47 | description: | 48 | The head ref of the event (for version 1) 49 | additionalProperties: false 50 | required: 51 | - version 52 | - organization 53 | - repository 54 | - installationId 55 | - eventId 56 | - body 57 | - tasks_for 58 | - branch -------------------------------------------------------------------------------- /schemas/v1/repository.yml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-06/schema# 2 | title: "Repository Response" 3 | description: | 4 | Any Taskcluster-specific Github repository information. 5 | type: object 6 | properties: 7 | installed: 8 | type: boolean 9 | description: | 10 | True if integration is installed, False otherwise. 11 | additionalProperties: false 12 | required: 13 | - installed 14 | -------------------------------------------------------------------------------- /schemas/v1/task-group-creation-requested.yml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-06/schema# 2 | title: "Task Group Defined - Create Status" 3 | description: | 4 | Indicates that this service has created a new task group in response to a GitHub event. 5 | This message is for internal use only and should not be relied on for other purposes. 6 | Full specification on [GitHub docs](https://developer.github.com/v3/repos/statuses/#create-a-status) 7 | type: object 8 | properties: 9 | version: {$const: message-version} 10 | organization: 11 | description: | 12 | The GitHub `organization` which had an event. 13 | type: string 14 | minLength: {$const: github-identifier-min-length} 15 | maxLength: {$const: github-identifier-max-length} 16 | pattern: {$const: github-identifier-pattern} 17 | repository: 18 | description: | 19 | The GitHub `repository` which had an event. 20 | type: string 21 | minLength: {$const: github-identifier-min-length} 22 | maxLength: {$const: github-identifier-max-length} 23 | pattern: {$const: github-identifier-pattern} 24 | taskGroupId: 25 | type: string 26 | description: The id of the taskGroup that had been created. 27 | additionalProperties: false 28 | required: 29 | - taskGroupId 30 | - organization 31 | - repository 32 | - version 33 | -------------------------------------------------------------------------------- /schemas/v1/taskcluster-github-config.v1.yml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-06/schema# 2 | title: ".taskcluster.yml format" 3 | description: | 4 | Description of a taskcluster.yml file v1, which may be used to generate a taskgraph 5 | and tasks. 6 | type: object 7 | properties: 8 | version: 9 | description: Version of the format of this file; must be 1 10 | enum: [1] 11 | type: integer 12 | policy: 13 | pullRequests: 14 | description: | 15 | Policy for creating tasks for pull requests. The effective policy is found in this property 16 | in the `.taskcluster.yml` file in the repository's default branch. See the documentation for 17 | detailed definition of the options. 18 | type: string 19 | enum: 20 | - public 21 | - collaborators 22 | reporting: 23 | description: Policy for reporting status of PR or a commit. If absent, Github Statuses API is used 24 | type: string 25 | enum: 26 | - checks-v1 27 | tasks: 28 | title: Task definition template" 29 | description: | 30 | Definitions of tasks that can be scheduled. Rendered with JSON-e 31 | default: [] 32 | oneOf: 33 | - type: array 34 | description: Each element of this should evaluate to a task definition via json-e 35 | items: 36 | type: object 37 | additionalProperties: true 38 | - type: object 39 | description: This must evaluate to an array via json-e i.e. `$flatten` 40 | additionalProperties: true 41 | additionalProperties: false 42 | required: 43 | - version 44 | -------------------------------------------------------------------------------- /schemas/v1/taskcluster-github-config.yml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-06/schema# 2 | title: ".taskcluster.yml format" 3 | description: | 4 | Description of a taskcluster.yml file v0, which may be used to generate a taskgraph 5 | and tasks. 6 | type: object 7 | properties: 8 | version: 9 | description: ".taskcluster.yml version" 10 | enum: [0] 11 | type: integer 12 | metadata: 13 | description: "Required task graph metadata" 14 | type: object 15 | properties: 16 | name: 17 | title: "Name" 18 | description: | 19 | Human readable name of task, used to very briefly given an idea about 20 | what the task does. 21 | type: string 22 | maxLength: 255 23 | description: 24 | title: "Description" 25 | description: | 26 | Human readable description of the task, please **explain** what the 27 | task does. A few lines of documentation is not going to hurt you. 28 | type: string 29 | maxLength: 32768 30 | owner: 31 | title: "Owner" 32 | description: | 33 | E-mail of person who caused this task, e.g. the person who did 34 | `hg push`. The person we should contact to ask why this task is here. 35 | type: string 36 | maxLength: 255 37 | source: 38 | title: "Source" 39 | description: | 40 | Link to source of this task, should specify a file, revision and 41 | repository. This should be place someone can go an do a git/hg blame 42 | to who came up with recipe for this task. 43 | type: string 44 | maxLength: 4096 45 | additionalProperties: false 46 | required: 47 | - name 48 | - description 49 | - owner 50 | - source 51 | allowPullRequests: 52 | description: | 53 | Policy for creating tasks for pull requests. The effective policy is found in this property 54 | in the `.taskcluster.yml` file in the repository's default branch. See the documentation for 55 | detailed definition of the options. 56 | type: string 57 | enum: 58 | - public 59 | - collaborators 60 | tasks: 61 | type: array 62 | default: [] 63 | items: 64 | title: "Task Definition" 65 | description: | 66 | Definition of a task that can be scheduled 67 | type: object 68 | properties: 69 | created: {$const: created} 70 | deadline: {$const: deadline} 71 | additionalProperties: true 72 | additionalProperties: false 73 | required: 74 | - version 75 | -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | const Entity = require('azure-entities'); 2 | 3 | /** 4 | * Entity for tracking which task-groups are associated 5 | * with which org/repo/sha, etc. 6 | * 7 | */ 8 | module.exports.Builds = Entity.configure({ 9 | version: 1, 10 | partitionKey: Entity.keys.StringKey('taskGroupId'), 11 | rowKey: Entity.keys.ConstantKey('taskGroupId'), 12 | properties: { 13 | organization: Entity.types.String, 14 | repository: Entity.types.String, 15 | sha: Entity.types.String, 16 | taskGroupId: Entity.types.String, 17 | state: Entity.types.String, 18 | created: Entity.types.Date, 19 | updated: Entity.types.Date, 20 | }, 21 | }).configure({ 22 | version: 2, 23 | properties: { 24 | organization: Entity.types.String, 25 | repository: Entity.types.String, 26 | sha: Entity.types.String, 27 | taskGroupId: Entity.types.String, 28 | state: Entity.types.String, 29 | created: Entity.types.Date, 30 | updated: Entity.types.Date, 31 | // GitHub installation ID that comes from the webhook 32 | // Needed for authentication in statusHandler 33 | installationId: Entity.types.Number, 34 | }, 35 | migrate: function(item) { 36 | item.installationId = 0; 37 | return item; 38 | }, 39 | }).configure({ 40 | version: 3, 41 | properties: { 42 | organization: Entity.types.String, 43 | repository: Entity.types.String, 44 | sha: Entity.types.String, 45 | taskGroupId: Entity.types.String, 46 | state: Entity.types.String, 47 | created: Entity.types.Date, 48 | updated: Entity.types.Date, 49 | installationId: Entity.types.Number, 50 | eventType: Entity.types.String, 51 | }, 52 | migrate: function(item) { 53 | item.eventType = 'Unknown Event'; 54 | return item; 55 | }, 56 | }).configure({ 57 | version: 4, 58 | properties: { 59 | organization: Entity.types.String, 60 | repository: Entity.types.String, 61 | sha: Entity.types.String, 62 | taskGroupId: Entity.types.String, 63 | state: Entity.types.String, 64 | created: Entity.types.Date, 65 | updated: Entity.types.Date, 66 | installationId: Entity.types.Number, 67 | eventType: Entity.types.String, 68 | eventId: Entity.types.String, 69 | }, 70 | migrate: function(item) { 71 | item.Id = 'Unknown'; 72 | return item; 73 | }, 74 | }).configure({ 75 | version: 5, 76 | properties: { 77 | organization: Entity.types.String, 78 | repository: Entity.types.String, 79 | sha: Entity.types.String, 80 | taskGroupId: Entity.types.String, 81 | state: Entity.types.String, 82 | created: Entity.types.Date, 83 | updated: Entity.types.Date, 84 | installationId: Entity.types.Number, 85 | eventType: Entity.types.String, 86 | eventId: Entity.types.String, 87 | }, 88 | migrate: function(item) { 89 | // Delete Id because it was mistakenly set in the last migration 90 | delete item.Id; // Just in case the migrations are chained 91 | item.eventId = item.eventId || 'Unknown'; 92 | return item; 93 | }, 94 | }); 95 | 96 | module.exports.OwnersDirectory = Entity.configure({ 97 | version: 1, 98 | partitionKey: Entity.keys.StringKey('owner'), 99 | rowKey: Entity.keys.ConstantKey('someConstant'), 100 | properties: { 101 | installationId: Entity.types.Number, 102 | owner: Entity.types.String, 103 | }, 104 | }); 105 | 106 | module.exports.CheckRuns = Entity.configure({ 107 | version: 1, 108 | partitionKey: Entity.keys.StringKey('taskGroupId'), 109 | rowKey: Entity.keys.StringKey('taskId'), 110 | properties: { 111 | taskGroupId: Entity.types.String, 112 | taskId: Entity.types.String, 113 | checkSuiteId: Entity.types.String, 114 | checkRunId: Entity.types.String, 115 | }, 116 | }); 117 | -------------------------------------------------------------------------------- /src/exchanges.js: -------------------------------------------------------------------------------- 1 | const {Exchanges} = require('taskcluster-lib-pulse'); 2 | const _ = require('lodash'); 3 | const assert = require('assert'); 4 | 5 | /** Build common routing key construct for `exchanges.declare` */ 6 | const commonRoutingKey = function(options) { 7 | options = options || {}; 8 | let routingKey = [ 9 | { 10 | name: 'routingKeyKind', 11 | summary: 'Identifier for the routing-key kind. This is ' + 12 | 'always `"primary"` for the formalized routing key.', 13 | constant: 'primary', 14 | required: true, 15 | }, { 16 | name: 'organization', 17 | summary: 'The GitHub `organization` which had an event. ' + 18 | 'All periods have been replaced by % - such that ' + 19 | 'foo.bar becomes foo%bar - and all other special ' + 20 | 'characters aside from - and _ have been stripped.', 21 | maxSize: 100, 22 | required: true, 23 | }, { 24 | name: 'repository', 25 | summary: 'The GitHub `repository` which had an event.' + 26 | 'All periods have been replaced by % - such that ' + 27 | 'foo.bar becomes foo%bar - and all other special ' + 28 | 'characters aside from - and _ have been stripped.', 29 | maxSize: 100, 30 | required: true, 31 | }, 32 | ]; 33 | if (options.hasActions) { 34 | routingKey.push({ 35 | name: 'action', 36 | summary: 'The GitHub `action` which triggered an event. ' + 37 | 'See for possible values see the payload actions ' + 38 | 'property.', 39 | maxSize: 22, 40 | required: true, 41 | }); 42 | } 43 | return routingKey; 44 | }; 45 | 46 | const commonMessageBuilder = function(msg) { 47 | msg.version = 1; 48 | return msg; 49 | }; 50 | 51 | /** Build list of routing keys to CC */ 52 | const commonCCBuilder = (message, routes) => { 53 | assert(Array.isArray(routes), 'Routes must be an array'); 54 | return routes.map(route => 'route.' + route); 55 | }; 56 | 57 | /** Declaration of exchanges offered by the github */ 58 | let exchanges = new Exchanges({ 59 | serviceName: 'github', 60 | projectName: 'taskcluster-github', 61 | apiVersion: 'v1', 62 | title: 'Taskcluster-Github Exchanges', 63 | description: [ 64 | 'The github service publishes a pulse', 65 | 'message for supported github events, translating Github webhook', 66 | 'events into pulse messages.', 67 | '', 68 | 'This document describes the exchange offered by the taskcluster', 69 | 'github service', 70 | ].join('\n'), 71 | }); 72 | 73 | /** pull request exchange */ 74 | exchanges.declare({ 75 | exchange: 'pull-request', 76 | name: 'pullRequest', 77 | title: 'GitHub Pull Request Event', 78 | description: [ 79 | 'When a GitHub pull request event is posted it will be broadcast on this', 80 | 'exchange with the designated `organization` and `repository`', 81 | 'in the routing-key along with event specific metadata in the payload.', 82 | ].join('\n'), 83 | routingKey: commonRoutingKey({hasActions: true}), 84 | schema: 'github-pull-request-message.yml', 85 | messageBuilder: commonMessageBuilder, 86 | routingKeyBuilder: msg => _.pick(msg, 'organization', 'repository', 'action'), 87 | CCBuilder: () => [], 88 | }); 89 | 90 | /** push exchange */ 91 | exchanges.declare({ 92 | exchange: 'push', 93 | name: 'push', 94 | title: 'GitHub push Event', 95 | description: [ 96 | 'When a GitHub push event is posted it will be broadcast on this', 97 | 'exchange with the designated `organization` and `repository`', 98 | 'in the routing-key along with event specific metadata in the payload.', 99 | ].join('\n'), 100 | routingKey: commonRoutingKey(), 101 | schema: 'github-push-message.yml', 102 | messageBuilder: commonMessageBuilder, 103 | routingKeyBuilder: msg => _.pick(msg, 'organization', 'repository'), 104 | CCBuilder: () => [], 105 | }); 106 | 107 | /** release exchange */ 108 | exchanges.declare({ 109 | exchange: 'release', 110 | name: 'release', 111 | title: 'GitHub release Event', 112 | description: [ 113 | 'When a GitHub release event is posted it will be broadcast on this', 114 | 'exchange with the designated `organization` and `repository`', 115 | 'in the routing-key along with event specific metadata in the payload.', 116 | ].join('\n'), 117 | routingKey: commonRoutingKey(), 118 | schema: 'github-release-message.yml', 119 | messageBuilder: commonMessageBuilder, 120 | routingKeyBuilder: msg => _.pick(msg, 'organization', 'repository'), 121 | CCBuilder: () => [], 122 | }); 123 | 124 | /** task group exchange */ 125 | exchanges.declare({ 126 | exchange: 'task-group-creation-requested', 127 | name: 'taskGroupCreationRequested', 128 | title: 'tc-gh requested the Queue service to create all the tasks in a group', 129 | description: [ 130 | 'supposed to signal that taskCreate API has been called for every task in the task group', 131 | 'for this particular repo and this particular organization', 132 | 'currently used for creating initial status indicators in GitHub UI using Statuses API.', 133 | 'This particular exchange can also be bound to RabbitMQ queues by custom routes - for that,', 134 | 'Pass in the array of routes as a second argument to the publish method. Currently, we do', 135 | 'use the statuses routes to bind the handler that creates the initial status.', 136 | ].join('\n'), 137 | routingKey: commonRoutingKey(), 138 | schema: 'task-group-creation-requested.yml', 139 | messageBuilder: commonMessageBuilder, 140 | routingKeyBuilder: msg => _.pick(msg, 'organization', 'repository'), 141 | CCBuilder: commonCCBuilder, 142 | }); 143 | 144 | // Export exchanges 145 | module.exports = exchanges; 146 | -------------------------------------------------------------------------------- /src/github-auth.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('taskcluster-github:github-auth'); 2 | const Promise = require('bluebird'); 3 | const jwt = require('jsonwebtoken'); 4 | 5 | const retryPlugin = (octokit, options) => { 6 | 7 | const retries = 7; 8 | const baseBackoff = 100; 9 | const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout)); 10 | 11 | octokit.hook.wrap('request', async (request, options) => { 12 | let response; 13 | for (let attempt = 1; attempt <= retries; attempt++) { 14 | try { 15 | return await request(options); 16 | } catch (err) { 17 | if (attempt === retries || err.name !== 'HttpError' || err.status !== 404) { 18 | throw err; 19 | } 20 | debug(`404 getting retried for eventual consistency. attempt: ${attempt}`); 21 | await sleep(baseBackoff * Math.pow(2, attempt)); 22 | } 23 | } 24 | }); 25 | }; 26 | const Github = require('@octokit/rest').plugin([retryPlugin]); 27 | 28 | module.exports = async ({cfg}) => { 29 | let github = new Github({ 30 | promise: Promise, 31 | }); 32 | 33 | let setupToken = _ => { 34 | let inteToken = jwt.sign( 35 | {iss: cfg.github.credentials.integrationId}, 36 | cfg.github.credentials.privatePEM, 37 | {algorithm: 'RS256', expiresIn: '1m'}, 38 | ); 39 | try { 40 | github.authenticate({type: 'app', token: inteToken}); 41 | } catch (e) { 42 | debug('Authentication as app failed!'); 43 | throw e; 44 | } 45 | return github; 46 | }; 47 | 48 | // This object insures that the authentication is delayed until we need it. 49 | // Also, the authentication happens not just once in the beginning, but for each request. 50 | return { 51 | getIntegrationGithub: async _ => { 52 | setupToken(); 53 | return github; 54 | }, 55 | getInstallationGithub: async (inst_id) => { 56 | setupToken(); 57 | // Authenticating as installation 58 | var instaToken = (await github.apps.createInstallationToken({ 59 | installation_id: inst_id, 60 | })).data; 61 | let gh = new Github({promise: Promise}); 62 | try { 63 | gh.authenticate({type: 'token', token: instaToken.token}); 64 | debug(`Authenticated as installation: ${inst_id}`); 65 | } catch (e) { 66 | debug('Authentication as app failed!'); 67 | throw e; 68 | } 69 | return gh; 70 | }, 71 | }; 72 | }; 73 | -------------------------------------------------------------------------------- /src/intree.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('taskcluster-github:intree'); 2 | const yaml = require('js-yaml'); 3 | const TcYaml = require('./tc-yaml'); 4 | 5 | module.exports = {}; 6 | 7 | /** 8 | * Returns a function that merges an existing taskcluster github config with 9 | * a pull request message's payload to generate a full task graph config. 10 | * params { 11 | * config: '...', A yaml string 12 | * payload: {}, GitHub WebHook message payload 13 | * schema: url, Url to the taskcluster config schema 14 | * } 15 | **/ 16 | module.exports.setup = async function({cfg, schemaset}) { 17 | const validate = await schemaset.validator(cfg.taskcluster.rootUrl); 18 | 19 | return function({config, payload, schema}) { 20 | config = yaml.safeLoad(config); 21 | const version = config.version; 22 | 23 | const errors = validate(config, schema[version]); 24 | if (errors) { 25 | throw new Error(errors); 26 | } 27 | debug(`intree config for ${payload.organization}/${payload.repository} matches valid schema.`); 28 | 29 | // We need to toss out the config version number; it's the only 30 | // field that's not also in graph/task definitions 31 | delete config.version; 32 | 33 | const tcyaml = TcYaml.instantiate(version); 34 | 35 | // Perform parameter substitutions. This happens after verification 36 | // because templating may change with schema version, and parameter 37 | // functions are used as default values for some fields. 38 | config = tcyaml.substituteParameters(config, cfg, payload); 39 | 40 | try { 41 | // Compile individual tasks, filtering any that are not intended 42 | // for the current github event type. Append taskGroupId while 43 | // we're at it. 44 | return tcyaml.compileTasks(config, cfg, payload, new Date().toJSON()); 45 | } catch (e) { 46 | debug('Error processing tasks!'); 47 | throw e; 48 | } 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | const builder = require('./api'); 2 | const exchanges = require('./exchanges'); 3 | const Handlers = require('./handlers'); 4 | const Intree = require('./intree'); 5 | const data = require('./data'); 6 | const Promise = require('bluebird'); 7 | const Ajv = require('ajv'); 8 | const config = require('typed-env-config'); 9 | const monitor = require('taskcluster-lib-monitor'); 10 | const SchemaSet = require('taskcluster-lib-validate'); 11 | const loader = require('taskcluster-lib-loader'); 12 | const docs = require('taskcluster-lib-docs'); 13 | const App = require('taskcluster-lib-app'); 14 | const {sasCredentials} = require('taskcluster-lib-azure'); 15 | const githubAuth = require('./github-auth'); 16 | const {Client, pulseCredentials} = require('taskcluster-lib-pulse'); 17 | 18 | const load = loader({ 19 | cfg: { 20 | requires: ['profile'], 21 | setup: ({profile}) => config({profile}), 22 | }, 23 | 24 | monitor: { 25 | requires: ['process', 'profile', 'cfg'], 26 | setup: ({process, profile, cfg}) => monitor({ 27 | rootUrl: cfg.taskcluster.rootUrl, 28 | projectName: cfg.monitoring.project || 'taskcluster-github', 29 | enable: cfg.monitoring.enable, 30 | credentials: cfg.taskcluster.credentials, 31 | mock: profile === 'test', 32 | process, 33 | }), 34 | }, 35 | 36 | schemaset: { 37 | requires: ['cfg'], 38 | setup: ({cfg}) => new SchemaSet({ 39 | serviceName: 'github', 40 | publish: cfg.app.publishMetaData, 41 | aws: cfg.aws, 42 | }), 43 | }, 44 | 45 | reference: { 46 | requires: [], 47 | setup: () => exchanges.reference(), 48 | }, 49 | 50 | ajv: { 51 | requires: [], 52 | setup: () => new Ajv(), 53 | }, 54 | 55 | docs: { 56 | requires: ['cfg', 'schemaset', 'reference'], 57 | setup: ({cfg, schemaset, reference}) => docs.documenter({ 58 | credentials: cfg.taskcluster.credentials, 59 | tier: 'integrations', 60 | schemaset: schemaset, 61 | publish: cfg.app.publishMetaData, 62 | references: [ 63 | {name: 'api', reference: builder.reference()}, 64 | {name: 'events', reference: reference}, 65 | ], 66 | }), 67 | }, 68 | 69 | writeDocs: { 70 | requires: ['docs'], 71 | setup: ({docs}) => docs.write({docsDir: process.env['DOCS_OUTPUT_DIR']}), 72 | }, 73 | 74 | pulseClient: { 75 | requires: ['cfg', 'monitor'], 76 | setup: ({cfg, monitor}) => { 77 | return new Client({ 78 | namespace: cfg.pulse.namespace, 79 | monitor, 80 | credentials: pulseCredentials(cfg.pulse), 81 | }); 82 | }, 83 | }, 84 | 85 | publisher: { 86 | requires: ['cfg', 'schemaset', 'pulseClient'], 87 | setup: async ({cfg, pulseClient, schemaset}) => await exchanges.publisher({ 88 | rootUrl: cfg.taskcluster.rootUrl, 89 | schemaset, 90 | client: pulseClient, 91 | publish: cfg.app.publishMetaData, 92 | aws: cfg.aws, 93 | }), 94 | }, 95 | 96 | github: { 97 | requires: ['cfg'], 98 | setup: ({cfg}) => githubAuth({cfg}), 99 | }, 100 | 101 | intree: { 102 | requires: ['cfg', 'schemaset'], 103 | setup: ({cfg, schemaset}) => Intree.setup({cfg, schemaset}), 104 | }, 105 | 106 | Builds: { 107 | requires: ['cfg', 'monitor'], 108 | setup: async ({cfg, monitor}) => data.Builds.setup({ 109 | tableName: cfg.app.buildsTableName, 110 | credentials: sasCredentials({ 111 | accountId: cfg.azure.accountId, 112 | tableName: cfg.app.buildsTableName, 113 | rootUrl: cfg.taskcluster.rootUrl, 114 | credentials: cfg.taskcluster.credentials, 115 | }), 116 | monitor: monitor.prefix('table.builds'), 117 | }), 118 | }, 119 | 120 | OwnersDirectory: { 121 | requires: ['cfg', 'monitor'], 122 | setup: async ({cfg, monitor}) => data.OwnersDirectory.setup({ 123 | tableName: cfg.app.ownersDirectoryTableName, 124 | credentials: sasCredentials({ 125 | accountId: cfg.azure.accountId, 126 | tableName: cfg.app.ownersDirectoryTableName, 127 | rootUrl: cfg.taskcluster.rootUrl, 128 | credentials: cfg.taskcluster.credentials, 129 | }), 130 | monitor: monitor.prefix('table.ownersdirectory'), 131 | }), 132 | }, 133 | 134 | CheckRuns: { 135 | requires: ['cfg', 'monitor'], 136 | setup: async ({cfg, monitor}) => data.CheckRuns.setup({ 137 | tableName: cfg.app.checkRunsTableName, 138 | credentials: sasCredentials({ 139 | accountId: cfg.azure.accountId, 140 | tableName: cfg.app.checkRunsTableName, 141 | rootUrl: cfg.taskcluster.rootUrl, 142 | credentials: cfg.taskcluster.credentials, 143 | }), 144 | monitor: monitor.prefix('table.checkruns'), 145 | }), 146 | }, 147 | 148 | api: { 149 | requires: [ 150 | 'cfg', 'monitor', 'schemaset', 'github', 'publisher', 'Builds', 151 | 'OwnersDirectory', 'ajv'], 152 | setup: ({cfg, monitor, schemaset, github, publisher, Builds, 153 | OwnersDirectory, ajv}) => builder.build({ 154 | rootUrl: cfg.taskcluster.rootUrl, 155 | context: { 156 | publisher, 157 | cfg, 158 | github, 159 | Builds, 160 | OwnersDirectory, 161 | ajv, 162 | monitor: monitor.prefix('api-context'), 163 | }, 164 | publish: cfg.app.publishMetaData, 165 | aws: cfg.aws, 166 | monitor: monitor.prefix('api'), 167 | schemaset, 168 | }), 169 | }, 170 | 171 | server: { 172 | requires: ['cfg', 'api', 'docs'], 173 | setup: ({cfg, api, docs}) => App({ 174 | port: cfg.server.port, 175 | env: cfg.server.env, 176 | forceSSL: cfg.server.forceSSL, 177 | trustProxy: cfg.server.trustProxy, 178 | apis: [api], 179 | }), 180 | }, 181 | 182 | syncInstallations: { 183 | requires: ['github', 'OwnersDirectory', 'monitor'], 184 | setup: ({github, OwnersDirectory, monitor}) => { 185 | return monitor.oneShot('syncInstallations', async () => { 186 | const gh = await github.getIntegrationGithub(); 187 | const installations = (await gh.apps.getInstallations({})).data; 188 | await Promise.map(installations, inst => { 189 | return OwnersDirectory.create({ 190 | installationId: inst.id, 191 | owner: inst.account.login, 192 | }, true); 193 | }); 194 | }); 195 | }, 196 | }, 197 | 198 | handlers: { 199 | requires: [ 200 | 'cfg', 201 | 'github', 202 | 'monitor', 203 | 'intree', 204 | 'schemaset', 205 | 'reference', 206 | 'Builds', 207 | 'pulseClient', 208 | 'publisher', 209 | 'CheckRuns', 210 | ], 211 | setup: async ({cfg, github, monitor, intree, schemaset, reference, Builds, pulseClient, publisher, CheckRuns}) => 212 | new Handlers({ 213 | rootUrl: cfg.taskcluster.rootUrl, 214 | credentials: cfg.pulse, 215 | monitor: monitor.prefix('handlers'), 216 | intree, 217 | reference, 218 | jobQueueName: cfg.app.jobQueue, 219 | deprecatedResultStatusQueueName: cfg.app.deprecatedResultStatusQueue, 220 | deprecatedInitialStatusQueueName: cfg.app.deprecatedInitialStatusQueue, 221 | resultStatusQueueName: cfg.app.resultStatusQueue, 222 | initialStatusQueueName: cfg.app.initialStatusQueue, 223 | context: {cfg, github, schemaset, Builds, CheckRuns, publisher}, 224 | pulseClient, 225 | }), 226 | }, 227 | 228 | worker: { 229 | requires: ['handlers'], 230 | setup: async ({handlers}) => handlers.setup(), 231 | }, 232 | }, ['profile', 'process']); 233 | 234 | if (!module.parent) { 235 | load(process.argv[2], { 236 | process: process.argv[2], 237 | profile: process.env.NODE_ENV, 238 | }).catch(err => { 239 | console.log(err.stack || err); 240 | process.exit(1); 241 | }); 242 | } 243 | 244 | // Export load for tests 245 | module.exports = load; 246 | -------------------------------------------------------------------------------- /src/pr-allowed.js: -------------------------------------------------------------------------------- 1 | const yaml = require('js-yaml'); 2 | 3 | const DEFAULT_POLICY = 'collaborators'; 4 | 5 | async function prAllowed(options) { 6 | switch (await getRepoPolicy(options)) { 7 | case 'collaborators': 8 | return await isCollaborator(options); 9 | 10 | case 'public': 11 | return true; 12 | 13 | default: 14 | return false; 15 | } 16 | } 17 | 18 | /** 19 | * Get the repo's "policy" on pull requests, by fetching .taskcluster.yml from the default 20 | * branch, parsing it, and looking at its `allowPullRequests`. 21 | */ 22 | async function getRepoPolicy({login, organization, repository, instGithub, debug}) { 23 | // first, get the repository's default branch 24 | let repoInfo = (await instGithub.repos.get({owner: organization, repo: repository})).data; 25 | let branch = repoInfo.default_branch; 26 | 27 | // load .taskcluster.yml from that branch 28 | let taskclusterYml; 29 | try { 30 | let content = await instGithub.repos.getContents({ 31 | owner: organization, 32 | repo: repository, 33 | path: '.taskcluster.yml', 34 | ref: branch, 35 | }); 36 | taskclusterYml = yaml.safeLoad(new Buffer(content.data.content, 'base64').toString()); 37 | } catch (e) { 38 | if (e.code === 404) { 39 | return DEFAULT_POLICY; 40 | } 41 | throw e; 42 | } 43 | 44 | if (!taskclusterYml.version || taskclusterYml.version === 0) { 45 | // consult its `allowPullRequests` field 46 | return taskclusterYml['allowPullRequests'] || DEFAULT_POLICY; 47 | } else if (taskclusterYml.version === 1) { 48 | if (taskclusterYml.policy) { 49 | return taskclusterYml.policy.pullRequests || DEFAULT_POLICY; 50 | } 51 | } 52 | 53 | return DEFAULT_POLICY; 54 | } 55 | 56 | async function isCollaborator({login, organization, repository, sha, instGithub, debug}) { 57 | // GithubAPI's collaborator check returns an error if a user isn't 58 | // listed as a collaborator. 59 | try { 60 | await instGithub.repos.checkCollaborator({ 61 | owner: organization, 62 | repo: repository, 63 | username: login, 64 | }); 65 | // No error, the user is a collaborator 66 | debug(`Checking collaborator: ${login} is a collaborator on ${organization}/${repository}: True!`); 67 | return true; 68 | } catch (e) { 69 | // a 404 means the user is not a collaborator 70 | if (e.code !== 404) { 71 | throw e; 72 | } 73 | } 74 | return false; 75 | } 76 | 77 | module.exports = prAllowed; 78 | 79 | // for testing.. 80 | module.exports.getRepoPolicy = getRepoPolicy; 81 | module.exports.isCollaborator = isCollaborator; 82 | -------------------------------------------------------------------------------- /test/api_test.js: -------------------------------------------------------------------------------- 1 | const helper = require('./helper'); 2 | const assert = require('assert'); 3 | const _ = require('lodash'); 4 | const got = require('got'); 5 | 6 | /** 7 | * Tests of endpoints in the api _other than_ 8 | * the github webhook endpoint which is tested 9 | * in webhook_test.js 10 | */ 11 | helper.secrets.mockSuite('api_test.js', ['taskcluster'], function(mock, skipping) { 12 | helper.withEntities(mock, skipping); 13 | helper.withFakeGithub(mock, skipping); 14 | helper.withServer(mock, skipping); 15 | 16 | suiteSetup(async function() { 17 | await helper.Builds.create({ 18 | organization: 'abc123', 19 | repository: 'def456', 20 | sha: '7650871208002a13ba35cf232c0e30d2c3d64783', 21 | state: 'pending', 22 | taskGroupId: 'biizERCQQwi9ZS_WkCSjXQ', 23 | created: new Date(), 24 | updated: new Date(), 25 | installationId: 1, 26 | eventType: 'push', 27 | eventId: '26370a80-ed65-11e6-8f4c-80082678482d', 28 | }); 29 | await helper.Builds.create({ 30 | organization: 'ghi789', 31 | repository: 'jkl101112', 32 | sha: '8650871208002a13ba35cf232c0e30d2c3d64783', 33 | state: 'success', 34 | taskGroupId: 'aiizERCQQwi9ZS_WkCSjXQ', 35 | created: new Date(), 36 | updated: new Date(), 37 | installationId: 1, 38 | eventType: 'push', 39 | eventId: '26370a80-ed65-11e6-8f4c-80082678482d', 40 | }); 41 | await helper.Builds.create({ 42 | organization: 'abc123', 43 | repository: 'xyz', 44 | sha: 'x650871208002a13ba35cf232c0e30d2c3d64783', 45 | state: 'pending', 46 | taskGroupId: 'qiizERCQQwi9ZS_WkCSjXQ', 47 | created: new Date(), 48 | updated: new Date(), 49 | installationId: 1, 50 | eventType: 'push', 51 | eventId: '26370a80-ed65-11e6-8f4c-80082678482d', 52 | }); 53 | await helper.Builds.create({ 54 | organization: 'abc123', 55 | repository: 'xyz', 56 | sha: 'y650871208002a13ba35cf232c0e30d2c3d64783', 57 | state: 'pending', 58 | taskGroupId: 'ziizERCQQwi9ZS_WkCSjXQ', 59 | created: new Date(), 60 | updated: new Date(), 61 | installationId: 1, 62 | eventType: 'push', 63 | eventId: 'Unknown', 64 | }); 65 | 66 | await helper.OwnersDirectory.create({ 67 | installationId: 9090, 68 | owner: 'abc123', 69 | }); 70 | 71 | await helper.OwnersDirectory.create({ 72 | installationId: 9091, 73 | owner: 'qwerty', 74 | }); 75 | }); 76 | 77 | setup(async function() { 78 | github = await helper.load('github'); 79 | github.inst(9090).setRepositories('coolRepo', 'anotherCoolRepo', 'awesomeRepo', 'nonTCGHRepo'); 80 | github.inst(9090).setStatuses({ 81 | owner: 'abc123', 82 | repo: 'coolRepo', 83 | ref: 'master', 84 | info: [{creator: {id: 12345}, state: 'success'}, {creator: {id: 55555}, state: 'failure'}], 85 | }); 86 | github.inst(9090).setStatuses({ 87 | owner: 'abc123', 88 | repo: 'awesomeRepo', 89 | ref: 'master', 90 | info: [ 91 | {creator: {id: 12345}, state: 'success'}, 92 | {creator: {id: 55555}, state: 'success', target_url: 'Wonderland'}, 93 | ], 94 | }); 95 | github.inst(9090).setStatuses({ 96 | owner: 'abc123', 97 | repo: 'nonTCGHRepo', 98 | ref: 'master', 99 | info: [{creator: {id: 123345}, state: 'success'}], 100 | }); 101 | github.inst(9090).setUser({id: 55555, email: 'noreply@github.com', username: 'magicalTCspirit'}); 102 | }); 103 | 104 | test('all builds', async function() { 105 | let builds = await helper.apiClient.builds(); 106 | assert.equal(builds.builds.length, 4); 107 | builds.builds = _.orderBy(builds.builds, ['organization', 'repository']); 108 | assert.equal(builds.builds[0].organization, 'abc123'); 109 | assert.equal(builds.builds[1].organization, 'abc123'); 110 | assert.equal(builds.builds[2].organization, 'abc123'); 111 | assert.equal(builds.builds[3].organization, 'ghi789'); 112 | }); 113 | 114 | test('org builds', async function() { 115 | let builds = await helper.apiClient.builds({organization: 'abc123'}); 116 | assert.equal(builds.builds.length, 3); 117 | builds.builds = _.orderBy(builds.builds, ['organization', 'repository']); 118 | assert.equal(builds.builds[0].organization, 'abc123'); 119 | }); 120 | 121 | test('repo builds', async function() { 122 | let builds = await helper.apiClient.builds({organization: 'abc123', repository: 'xyz'}); 123 | assert.equal(builds.builds.length, 2); 124 | builds.builds = _.orderBy(builds.builds, ['organization', 'repository']); 125 | assert.equal(builds.builds[0].organization, 'abc123'); 126 | assert.equal(builds.builds[0].repository, 'xyz'); 127 | }); 128 | 129 | test('sha builds', async function() { 130 | let builds = await helper.apiClient.builds({ 131 | organization: 'abc123', 132 | repository: 'xyz', 133 | sha: 'y650871208002a13ba35cf232c0e30d2c3d64783', 134 | }); 135 | assert.equal(builds.builds.length, 1); 136 | builds.builds = _.orderBy(builds.builds, ['organization', 'repository']); 137 | assert.equal(builds.builds[0].organization, 'abc123'); 138 | assert.equal(builds.builds[0].repository, 'xyz'); 139 | assert.equal(builds.builds[0].sha, 'y650871208002a13ba35cf232c0e30d2c3d64783'); 140 | }); 141 | 142 | test('integration installation', async function() { 143 | let result = await helper.apiClient.repository('abc123', 'coolRepo'); 144 | assert.deepEqual(result, {installed: true}); 145 | result = await helper.apiClient.repository('abc123', 'unknownRepo'); 146 | assert.deepEqual(result, {installed: false}); 147 | result = await helper.apiClient.repository('unknownOwner', 'unknownRepo'); 148 | assert.deepEqual(result, {installed: false}); 149 | }); 150 | 151 | test('build badges', async function() { 152 | var res; 153 | 154 | // status: failure 155 | res = await got(helper.apiClient.buildUrl(helper.apiClient.badge, 'abc123', 'coolRepo', 'master')); 156 | assert.equal(res.headers['content-length'], 8615); 157 | 158 | // status: success 159 | res = await got(helper.apiClient.buildUrl(helper.apiClient.badge, 'abc123', 'awesomeRepo', 'master')); 160 | assert.equal(res.headers['content-length'], 9189); 161 | 162 | // error 163 | res = await got(helper.apiClient.buildUrl(helper.apiClient.badge, 'abc123', 'unknownRepo', 'master')); 164 | assert.equal(res.headers['content-length'], 4268); 165 | 166 | // new repo (no info yet) 167 | res = await got(helper.apiClient.buildUrl(helper.apiClient.badge, 'abc123', 'nonTCGHRepo', 'master')); 168 | assert.equal(res.headers['content-length'], 7873); 169 | }); 170 | 171 | test('link for clickable badges', async function() { 172 | var res; 173 | 174 | // check if the function returns a correct link 175 | try { 176 | res = await got( 177 | helper.apiClient.buildUrl(helper.apiClient.latest, 'abc123', 'awesomeRepo', 'master'), 178 | {followRedirect: false}); 179 | } catch (e) { 180 | console.log(`Test for redirecting to correct page failed. Error: ${JSON.stringify(e)}`); 181 | } 182 | assert.equal(res.body, 'Found. Redirecting to Wonderland'); 183 | }); 184 | 185 | test('simple status creation', async function() { 186 | await helper.apiClient.createStatus('abc123', 'awesomeRepo', 'master', { 187 | state: 'error', 188 | }); 189 | 190 | let status = github.inst(9090).listStatusesForRef({ 191 | owner: 'abc123', 192 | repo: 'awesomeRepo', 193 | ref: 'master', 194 | }).data.pop(); 195 | assert.equal(status.state, 'error'); 196 | assert.equal(status.target_url, undefined); 197 | assert.equal(status.description, undefined); 198 | assert.equal(status.context, 'default'); 199 | }); 200 | 201 | test('advanced status creation', async function() { 202 | await helper.apiClient.createStatus('abc123', 'awesomeRepo', 'master', { 203 | state: 'failure', 204 | target_url: 'http://test.com', 205 | description: 'Status title', 206 | context: 'customContext', 207 | }); 208 | 209 | let status = github.inst(9090).listStatusesForRef({ 210 | owner: 'abc123', 211 | repo: 'awesomeRepo', 212 | ref: 'master', 213 | }).data.pop(); 214 | assert.equal(status.state, 'failure'); 215 | assert.equal(status.target_url, 'http://test.com'); 216 | assert.equal(status.description, 'Status title'); 217 | assert.equal(status.context, 'customContext'); 218 | }); 219 | 220 | test('status creation where integraiton lacks permission', async function() { 221 | try { 222 | await helper.apiClient.createStatus('abc123', 'no-permission', 'master', { 223 | state: 'failure', 224 | target_url: 'http://test.com', 225 | description: 'Status title', 226 | context: 'customContext', 227 | }); 228 | } catch (e) { 229 | assert.equal(e.statusCode, 403); 230 | return; // passed 231 | } 232 | throw new Error('endpoint should have failed'); 233 | }); 234 | test('pull request comment', async function() { 235 | await helper.apiClient.createComment('abc123', 'awesomeRepo', 1, { 236 | body: 'Task failed here', 237 | }); 238 | 239 | let comment = github.inst(9090).getComments({ 240 | owner: 'abc123', 241 | repo: 'awesomeRepo', 242 | number: 1, 243 | }).data.pop(); 244 | assert.equal(comment.body, 'Task failed here'); 245 | }); 246 | 247 | test('pull request comment where integration lacks permission', async function() { 248 | try { 249 | await helper.apiClient.createComment('abc123', 'no-permission', 1, {body: 'x'}); 250 | } catch (e) { 251 | assert.equal(e.statusCode, 403); 252 | return; // passed 253 | } 254 | throw new Error('endpoint should have failed'); 255 | }); 256 | }); 257 | -------------------------------------------------------------------------------- /test/checkStaging.js: -------------------------------------------------------------------------------- 1 | const Github = require('@octokit/rest'); 2 | const child_process = require('child_process'); 3 | const fs = require('fs-extra'); 4 | const _ = require('lodash'); 5 | 6 | const runCommand = (args) => { 7 | return new Promise((resolve, reject) => { 8 | console.log(`Running: ${args.join(' ')}`); 9 | const proc = child_process.spawn(args.shift(), args, {stdio: ['ignore', 'pipe', 2]}); 10 | const output = []; 11 | 12 | proc.stdout.on('data', data => { 13 | console.error(data.toString()); 14 | output.push(data); 15 | }); 16 | 17 | proc.on('close', code => { 18 | if (code === 0) { 19 | resolve(output.join('').toString()); 20 | } else { 21 | reject(new Error('git command failed')); 22 | } 23 | }); 24 | }); 25 | }; 26 | 27 | const checkStaging = async () => { 28 | let github = new Github(); 29 | 30 | // push a commit to the repo and get its sha 31 | let sha = await pushCommit(); 32 | 33 | const update = msg => { 34 | console.log(`** ${msg}`); 35 | }; 36 | 37 | update(`polling integration status of https://github.com/taskcluster/taskcluster-github-testing/commit/${sha}`); 38 | 39 | let done = false; 40 | while (!done) { 41 | await new Promise(resolve => setTimeout(resolve, 2000)); 42 | 43 | let statuses = await github.repos.listStatusesForRef({ 44 | owner: 'taskcluster', 45 | repo: 'taskcluster-github-testing', 46 | ref: sha, 47 | }); 48 | 49 | let status = _.find(statuses.data, {context: 'Taskcluster-Staging (push)'}); 50 | if (!status) { 51 | update('no Taskcluster-Staging (push) status'); 52 | continue; 53 | } 54 | 55 | update(`state: ${status.state}`); 56 | switch (status.state) { 57 | case 'pending': 58 | break; 59 | 60 | case 'failed': 61 | throw new Error('task run failed'); 62 | break; 63 | 64 | case 'success': 65 | done = true; 66 | update('PASSED'); 67 | break; 68 | 69 | default: 70 | throw new Error(`unexpected status state ${status.state}`); 71 | break; 72 | } 73 | } 74 | }; 75 | 76 | // push a dummy comit to taskcluster-github-testing 77 | const pushCommit = async () => { 78 | let tempdir = fs.mkdtempSync('/tmp/tc-gh-checkStaging-'); 79 | try { 80 | process.chdir(tempdir); 81 | await runCommand(['git', 'clone', 'git@github.com:taskcluster/taskcluster-github-testing.git', 'testing']); 82 | process.chdir('testing'); 83 | fs.writeFileSync('README.md', 84 | 'This repository is used to support `npm run checkStaging` in taskcluster-github\n\n' + 85 | `Last run: ${new Date()}`); 86 | await runCommand(['git', 'add', 'README.md']); 87 | await runCommand(['git', 'commit', '-m', 'checkStaging run']); 88 | await runCommand(['git', 'push']); 89 | return (await runCommand(['git', 'log', '-1', '--pretty=format:%H'])).trim(); 90 | } finally { 91 | console.error(`removing ${tempdir}`); 92 | fs.removeSync(tempdir); 93 | } 94 | }; 95 | 96 | checkStaging().then(() => process.exit(0)).catch((err) => { 97 | console.error(err); 98 | process.exit(1); 99 | }); 100 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.branchlimited.v0.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - push 15 | branches: 16 | - master 17 | payload: 18 | image: "ubuntu:latest" 19 | command: 20 | - test 21 | metadata: 22 | name: "name" 23 | description: "description" 24 | owner: "test@test.com" 25 | source: "http://mrrrgn.com" 26 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.branchlimited.v1.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | reporting: 'checks-v1' 3 | tasks: 4 | $match: 5 | 'tasks_for == "github-push" && event.ref == "refs/heads/master"': 6 | taskId: {$eval: as_slugid('apple')} 7 | provisionerId: aprovisioner 8 | workerType: worker 9 | payload: 10 | image: "ubuntu:latest" 11 | command: 12 | - test 13 | metadata: 14 | name: "name" 15 | description: "description" 16 | owner: "test@test.com" 17 | source: "http://mrrrgn.com" 18 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.exclude-error.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - push 15 | branches: 16 | - master 17 | excludeBranches: 18 | - foobar 19 | - master 20 | 21 | payload: 22 | image: "ubuntu:latest" 23 | command: 24 | - test 25 | metadata: 26 | name: "name" 27 | description: "description" 28 | owner: "test@test.com" 29 | source: "http://mrrrgn.com" 30 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.exclude.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - push 15 | excludeBranches: 16 | - foobar 17 | 18 | payload: 19 | image: "ubuntu:latest" 20 | command: 21 | - test 22 | metadata: 23 | name: "name" 24 | description: "description" 25 | owner: "test@test.com" 26 | source: "http://mrrrgn.com" 27 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.non-github.v0.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | payload: 11 | image: "ubuntu:latest" 12 | command: 13 | - test 14 | metadata: 15 | name: "name" 16 | description: "description" 17 | owner: "test@test.com" 18 | source: "http://mrrrgn.com" 19 | - provisionerId: aprovisioner 20 | workerType: worker 21 | extra: 22 | mercurial: true 23 | payload: 24 | image: "ubuntu:latest" 25 | command: 26 | - test 27 | metadata: 28 | name: "name" 29 | description: "description" 30 | owner: "test@test.com" 31 | source: "http://mrrrgn.com" 32 | 33 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.non-github.v1.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | tasks: 3 | - taskId: {$eval: as_slugid('😉')} 4 | provisionerId: aprovisioner 5 | workerType: worker 6 | created: {$eval: 'now'} 7 | deadline: {$fromNow: '1 hour'} 8 | payload: 9 | image: "ubuntu:latest" 10 | command: 11 | - test 12 | metadata: 13 | name: "name" 14 | description: "description" 15 | owner: "test@test.com" 16 | source: "http://mrrrgn.com" 17 | - $if: ' tasks_for == "mercurial-push" ' 18 | then: 19 | taskId: {$eval: as_slugid('mercurial')} 20 | provisionerId: aprovisioner 21 | workerType: worker 22 | created: {$eval: 'now'} 23 | deadline: {$fromNow: '1 hour'} 24 | payload: 25 | image: "ubuntu:latest" 26 | command: 27 | - test 28 | metadata: 29 | name: "name" 30 | description: "description" 31 | owner: "test@test.com" 32 | source: "http://mrrrgn.com" 33 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.pull_with_exclude.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - pull_request.* 15 | - push 16 | excludeBranches: 17 | - master 18 | payload: 19 | image: "ubuntu:latest" 20 | command: 21 | - test 22 | metadata: 23 | name: "PR Task" 24 | description: "description" 25 | owner: "test@test.com" 26 | source: "http://mrrrgn.com" 27 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.push_pull_release.v0.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - push 15 | payload: 16 | image: "ubuntu:latest" 17 | command: 18 | - test 19 | metadata: 20 | name: "Push Task" 21 | description: "description" 22 | owner: "test@test.com" 23 | source: "http://mrrrgn.com" 24 | - provisionerId: aprovisioner2 25 | workerType: worker2 26 | extra: 27 | github: 28 | env: true 29 | events: 30 | - pull_request.opened 31 | - pull_request.synchronize 32 | - pull_request.reopened 33 | payload: 34 | image: "ubuntu:latest" 35 | command: 36 | - test 37 | metadata: 38 | name: "PR task" 39 | description: "description2" 40 | owner: "test@test.com" 41 | source: "http://mrrrgn.com" 42 | - provisionerId: aprovisioner3 43 | workerType: worker3 44 | extra: 45 | github: 46 | env: true 47 | events: 48 | - release 49 | payload: 50 | image: "ubuntu:latest" 51 | command: 52 | - test 53 | metadata: 54 | name: "Release task" 55 | description: "description3" 56 | owner: "test@test.com" 57 | source: "http://mrrrgn.com" 58 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.push_pull_release.v1.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | tasks: 3 | - $if: ' tasks_for == "github-push" ' 4 | then: 5 | taskId: {$eval: as_slugid('😄')} 6 | provisionerId: aprovisioner 7 | workerType: worker 8 | created: {$eval: 'now'} 9 | deadline: {$fromNow: '1 hour'} 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - push 15 | payload: 16 | image: "ubuntu:latest" 17 | command: 18 | - test 19 | metadata: 20 | name: "Push Task" 21 | description: "description" 22 | owner: "test@test.com" 23 | source: "http://mrrrgn.com" 24 | 25 | - $if: ' tasks_for == "github-pull-request" ' 26 | then: 27 | taskId: {$eval: as_slugid('pull')} 28 | provisionerId: aprovisioner2 29 | workerType: worker2 30 | created: {$eval: 'now'} 31 | deadline: {$fromNow: '1 hour'} 32 | payload: 33 | image: "ubuntu:latest" 34 | command: 35 | - test 36 | metadata: 37 | name: "PR task" 38 | description: "description2" 39 | owner: "test@test.com" 40 | source: "http://mrrrgn.com" 41 | 42 | - $if: ' tasks_for == "github-release" ' 43 | then: 44 | taskId: {$eval: as_slugid('release')} 45 | provisionerId: aprovisioner3 46 | workerType: worker3 47 | created: {$eval: 'now'} 48 | deadline: {$fromNow: '1 hour'} 49 | payload: 50 | image: "ubuntu:latest" 51 | command: 52 | - test 53 | metadata: 54 | name: "Release task" 55 | description: "description3" 56 | owner: "test@test.com" 57 | source: "http://mrrrgn.com" 58 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.release_single.v0.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - release 15 | payload: 16 | image: "ubuntu:latest" 17 | command: 18 | - test 19 | metadata: 20 | name: "name" 21 | description: "description" 22 | owner: "test@test.com" 23 | source: "http://mrrrgn.com" 24 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.release_single.v1.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | tasks: 3 | - $if: ' tasks_for == "github-release" ' 4 | then: 5 | taskId: {$eval: as_slugid('🤓')} 6 | provisionerId: aprovisioner 7 | workerType: worker 8 | created: {$eval: 'now'} 9 | deadline: {$fromNow: '1 hour'} 10 | payload: 11 | image: "ubuntu:latest" 12 | command: 13 | - test 14 | metadata: 15 | name: "name" 16 | description: "description" 17 | owner: "test@test.com" 18 | source: "http://mrrrgn.com" 19 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.single.v0.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - push 15 | 16 | payload: 17 | image: "ubuntu:latest" 18 | command: 19 | - test 20 | metadata: 21 | name: "name" 22 | description: "description" 23 | owner: "test@test.com" 24 | source: "http://mrrrgn.com" 25 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.single.v1.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | tasks: 3 | - $if: ' tasks_for == "github-push" ' 4 | then: 5 | taskId: {$eval: as_slugid('banana')} 6 | provisionerId: aprovisioner 7 | workerType: worker 8 | created: {$eval: 'now'} 9 | deadline: {$fromNow: '1 hour'} 10 | payload: 11 | image: "ubuntu:latest" 12 | command: 13 | - test 14 | metadata: 15 | name: "name" 16 | description: "description" 17 | owner: ${event.pusher.email} 18 | source: ${event.repository.url} -------------------------------------------------------------------------------- /test/data/configs/taskcluster.star.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - pull_request.* 15 | payload: 16 | image: "ubuntu:latest" 17 | command: 18 | - test 19 | metadata: 20 | name: "name" 21 | description: "description" 22 | owner: "test@test.com" 23 | source: "http://mrrrgn.com" 24 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.tag.branchlimited.v0.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - tag 15 | branches: 16 | - master 17 | payload: 18 | image: "ubuntu:latest" 19 | command: 20 | - test 21 | metadata: 22 | name: "name" 23 | description: "description" 24 | owner: "test@test.com" 25 | source: "http://mrrrgn.com" 26 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.tag.branchlimited.v1.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | tasks: 3 | - $if: ' (tasks_for == "github-push") && event.ref != "refs/heads/master" ' 4 | then: 5 | taskId: {$eval: as_slugid('😊')} 6 | provisionerId: aprovisioner 7 | workerType: worker 8 | created: {$eval: 'now'} 9 | deadline: {$fromNow: '1 hour'} 10 | payload: 11 | image: "ubuntu:latest" 12 | command: 13 | - test 14 | metadata: 15 | name: "name" 16 | description: "description" 17 | owner: "test@test.com" 18 | source: "http://mrrrgn.com" -------------------------------------------------------------------------------- /test/data/configs/taskcluster.tag_single.v0.yml: -------------------------------------------------------------------------------- 1 | version: 0 2 | metadata: 3 | name: "name" 4 | description: "description" 5 | owner: "test@test.com" 6 | source: "http://mrrrgn.com" 7 | tasks: 8 | - provisionerId: aprovisioner 9 | workerType: worker 10 | extra: 11 | github: 12 | env: true 13 | events: 14 | - tag 15 | payload: 16 | image: "ubuntu:latest" 17 | command: 18 | - test 19 | metadata: 20 | name: "name" 21 | description: "description" 22 | owner: "test@test.com" 23 | source: "http://mrrrgn.com" 24 | -------------------------------------------------------------------------------- /test/data/configs/taskcluster.tag_single.v1.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | tasks: 3 | - $if: ' tasks_for == "github-push" ' 4 | then: 5 | taskId: {$eval: as_slugid('pineapple')} 6 | provisionerId: aprovisioner 7 | workerType: worker 8 | created: {$eval: 'now'} 9 | deadline: {$fromNow: '1 hour'} 10 | payload: 11 | image: "ubuntu:latest" 12 | command: 13 | - test 14 | metadata: 15 | name: "name" 16 | description: "description" 17 | owner: "test@test.com" 18 | source: "http://mrrrgn.com" 19 | -------------------------------------------------------------------------------- /test/data/webhooks/webhook.push.bad_secret.json: -------------------------------------------------------------------------------- 1 | {"headers": {"Host": "requestb.in", 2 | "Accept": "*/*", 3 | "User-Agent": "GitHub-Hookshot/f5d5ca1", 4 | "X-GitHub-Event": "push", 5 | "X-GitHub-Delivery": "736b3900-7f8a-11e6-8ec8-b75aea82a0ef", 6 | "content-type": "application/json", 7 | "X-Hub-Signature": "sha1=7afa92aab27dbb6ee39fe95138d05e3b2ce47601", 8 | "Content-Length": 6800}, "body": 9 | 10 | 11 | {"ref":"refs/heads/master","before":"efd1d89119e819a71a645c95c2590c0b3e7d0c83","after":"1edbd696be128a1ac187fac97e6a209fe9df3745","created":false,"deleted":false,"forced":false,"base_ref":null,"compare":"https://github.com/TaskClusterRobot/hooks-testing/compare/efd1d89119e8...1edbd696be12","commits":[{"id":"1edbd696be128a1ac187fac97e6a209fe9df3745","tree_id":"e9923f58e4cd74dc850443bc096784f9aaf69265","distinct":true,"message":"Update README.md","timestamp":"2016-09-20T16:32:10-07:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/1edbd696be128a1ac187fac97e6a209fe9df3745","author":{"name":"Lisa Lionheart","email":"taskcluster-accounts@mozilla.com","username":"TaskClusterRobot"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]}],"head_commit":{"id":"1edbd696be128a1ac187fac97e6a209fe9df3745","tree_id":"e9923f58e4cd74dc850443bc096784f9aaf69265","distinct":true,"message":"Update README.md","timestamp":"2016-09-20T16:32:10-07:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/1edbd696be128a1ac187fac97e6a209fe9df3745","author":{"name":"Lisa Lionheart","email":"taskcluster-accounts@mozilla.com","username":"TaskClusterRobot"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]},"repository":{"id":68761376,"name":"hooks-testing","full_name":"TaskClusterRobot/hooks-testing","owner":{"name":"TaskClusterRobot","email":"taskcluster-accounts@mozilla.com"},"private":false,"html_url":"https://github.com/TaskClusterRobot/hooks-testing","description":"for testing taskcluster-github","fork":false,"url":"https://github.com/TaskClusterRobot/hooks-testing","forks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/forks","keys_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/teams","hooks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/hooks","issue_events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/events{/number}","events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/events","assignees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/assignees{/user}","branches_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/branches{/branch}","tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tags","blobs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/refs{/sha}","trees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/statuses/{sha}","languages_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/languages","stargazers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/stargazers","contributors_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contributors","subscribers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscribers","subscription_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscription","commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/commits{/sha}","git_commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/commits{/sha}","comments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/comments{/number}","issue_comment_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/comments{/number}","contents_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contents/{+path}","compare_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/merges","archive_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/downloads","issues_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues{/number}","pulls_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/pulls{/number}","milestones_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/milestones{/number}","notifications_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/labels{/name}","releases_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases{/id}","deployments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/deployments","created_at":1474414120,"updated_at":"2016-09-20T23:28:40Z","pushed_at":1474414330,"git_url":"git://github.com/TaskClusterRobot/hooks-testing.git","ssh_url":"git@github.com:TaskClusterRobot/hooks-testing.git","clone_url":"https://github.com/TaskClusterRobot/hooks-testing.git","svn_url":"https://github.com/TaskClusterRobot/hooks-testing","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master","stargazers":0,"master_branch":"master"},"pusher":{"name":"TaskClusterRobot","email":"taskcluster-accounts@mozilla.com"},"sender":{"login":"TaskClusterRobot","id":14795478,"avatar_url":"https://avatars.githubusercontent.com/u/14795478?v=3","gravatar_id":"","url":"https://api.github.com/users/TaskClusterRobot","html_url":"https://github.com/TaskClusterRobot","followers_url":"https://api.github.com/users/TaskClusterRobot/followers","following_url":"https://api.github.com/users/TaskClusterRobot/following{/other_user}","gists_url":"https://api.github.com/users/TaskClusterRobot/gists{/gist_id}","starred_url":"https://api.github.com/users/TaskClusterRobot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TaskClusterRobot/subscriptions","organizations_url":"https://api.github.com/users/TaskClusterRobot/orgs","repos_url":"https://api.github.com/users/TaskClusterRobot/repos","events_url":"https://api.github.com/users/TaskClusterRobot/events{/privacy}","received_events_url":"https://api.github.com/users/TaskClusterRobot/received_events","type":"User","site_admin":false}}} 12 | -------------------------------------------------------------------------------- /test/data/webhooks/webhook.push.json: -------------------------------------------------------------------------------- 1 | {"headers": {"Host": "requestb.in", 2 | "Accept": "*/*", 3 | "User-Agent": "GitHub-Hookshot/6f33334", 4 | "X-GitHub-Event": "push", 5 | "X-GitHub-Delivery": "9637a980-d8fb-11e6-9830-1244ca57c95f", 6 | "content-type": "application/json", 7 | "X-Hub-Signature": "sha1=ceb38686a1f85978be5cfdcfeccdd3585c16e2bc", 8 | "Content-Length": 6705}, "body": 9 | 10 | {"ref":"refs/heads/master","before":"7a257a6d139708a3188bf2e0cd1f15e466a88d0e","after":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","created":false,"deleted":false,"forced":false,"base_ref":null,"compare":"https://github.com/TaskClusterRobot/hooks-testing/compare/7a257a6d1397...b79ce60be819","commits":[{"id":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","tree_id":"1e853705688e2088b1d25c7d9c3e521c80ba10cf","distinct":true,"message":"Update README.md","timestamp":"2017-01-12T11:16:14-08:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/b79ce60be819cdc482c9c6a84dc3c457959aa66f","author":{"name":"Irene","email":null,"username":"owlishDeveloper"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]}],"head_commit":{"id":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","tree_id":"1e853705688e2088b1d25c7d9c3e521c80ba10cf","distinct":true,"message":"Update README.md","timestamp":"2017-01-12T11:16:14-08:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/b79ce60be819cdc482c9c6a84dc3c457959aa66f","author":{"name":"Irene","email":null,"username":"owlishDeveloper"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]},"repository":{"id":68761376,"name":"hooks-testing","full_name":"TaskClusterRobot/hooks-testing","owner":{"name":"TaskClusterRobot","email":"taskcluster-accounts@mozilla.com"},"private":false,"html_url":"https://github.com/TaskClusterRobot/hooks-testing","description":"for testing taskcluster-github","fork":false,"url":"https://github.com/TaskClusterRobot/hooks-testing","forks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/forks","keys_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/teams","hooks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/hooks","issue_events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/events{/number}","events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/events","assignees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/assignees{/user}","branches_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/branches{/branch}","tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tags","blobs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/refs{/sha}","trees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/statuses/{sha}","languages_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/languages","stargazers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/stargazers","contributors_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contributors","subscribers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscribers","subscription_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscription","commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/commits{/sha}","git_commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/commits{/sha}","comments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/comments{/number}","issue_comment_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/comments{/number}","contents_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contents/{+path}","compare_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/merges","archive_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/downloads","issues_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues{/number}","pulls_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/pulls{/number}","milestones_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/milestones{/number}","notifications_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/labels{/name}","releases_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases{/id}","deployments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/deployments","created_at":1474414120,"updated_at":"2017-01-03T18:58:56Z","pushed_at":1484248575,"git_url":"git://github.com/TaskClusterRobot/hooks-testing.git","ssh_url":"git@github.com:TaskClusterRobot/hooks-testing.git","clone_url":"https://github.com/TaskClusterRobot/hooks-testing.git","svn_url":"https://github.com/TaskClusterRobot/hooks-testing","homepage":null,"size":24,"stargazers_count":1,"watchers_count":1,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":1,"default_branch":"master","stargazers":1,"master_branch":"master"},"pusher":{"name":"owlishDeveloper","email":null},"sender":{"login":"owlishDeveloper","id":18102552,"avatar_url":"https://avatars.githubusercontent.com/u/18102552?v=3","gravatar_id":"","url":"https://api.github.com/users/owlishDeveloper","html_url":"https://github.com/owlishDeveloper","followers_url":"https://api.github.com/users/owlishDeveloper/followers","following_url":"https://api.github.com/users/owlishDeveloper/following{/other_user}","gists_url":"https://api.github.com/users/owlishDeveloper/gists{/gist_id}","starred_url":"https://api.github.com/users/owlishDeveloper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/owlishDeveloper/subscriptions","organizations_url":"https://api.github.com/users/owlishDeveloper/orgs","repos_url":"https://api.github.com/users/owlishDeveloper/repos","events_url":"https://api.github.com/users/owlishDeveloper/events{/privacy}","received_events_url":"https://api.github.com/users/owlishDeveloper/received_events","type":"User","site_admin":false},"installation":{"id":5808}}} 11 | -------------------------------------------------------------------------------- /test/data/webhooks/webhook.push.no_secret.json: -------------------------------------------------------------------------------- 1 | {"headers": {"Host": "requestb.in", 2 | "Accept": "*/*", 3 | "User-Agent": "GitHub-Hookshot/f5d5ca1", 4 | "X-GitHub-Event": "push", 5 | "X-GitHub-Delivery": "736b3900-7f8a-11e6-8ec8-b75aea82a0ef", 6 | "content-type": "application/json", 7 | "Content-Length": 6800}, "body": 8 | 9 | 10 | {"ref":"refs/heads/master","before":"efd1d89119e819a71a645c95c2590c0b3e7d0c83","after":"1edbd696be128a1ac187fac97e6a209fe9df3745","created":false,"deleted":false,"forced":false,"base_ref":null,"compare":"https://github.com/TaskClusterRobot/hooks-testing/compare/efd1d89119e8...1edbd696be12","commits":[{"id":"1edbd696be128a1ac187fac97e6a209fe9df3745","tree_id":"e9923f58e4cd74dc850443bc096784f9aaf69265","distinct":true,"message":"Update README.md","timestamp":"2016-09-20T16:32:10-07:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/1edbd696be128a1ac187fac97e6a209fe9df3745","author":{"name":"Lisa Lionheart","email":"taskcluster-accounts@mozilla.com","username":"TaskClusterRobot"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]}],"head_commit":{"id":"1edbd696be128a1ac187fac97e6a209fe9df3745","tree_id":"e9923f58e4cd74dc850443bc096784f9aaf69265","distinct":true,"message":"Update README.md","timestamp":"2016-09-20T16:32:10-07:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/1edbd696be128a1ac187fac97e6a209fe9df3745","author":{"name":"Lisa Lionheart","email":"taskcluster-accounts@mozilla.com","username":"TaskClusterRobot"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]},"repository":{"id":68761376,"name":"hooks-testing","full_name":"TaskClusterRobot/hooks-testing","owner":{"name":"TaskClusterRobot","email":"taskcluster-accounts@mozilla.com"},"private":false,"html_url":"https://github.com/TaskClusterRobot/hooks-testing","description":"for testing taskcluster-github","fork":false,"url":"https://github.com/TaskClusterRobot/hooks-testing","forks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/forks","keys_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/teams","hooks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/hooks","issue_events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/events{/number}","events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/events","assignees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/assignees{/user}","branches_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/branches{/branch}","tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tags","blobs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/refs{/sha}","trees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/statuses/{sha}","languages_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/languages","stargazers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/stargazers","contributors_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contributors","subscribers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscribers","subscription_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscription","commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/commits{/sha}","git_commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/commits{/sha}","comments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/comments{/number}","issue_comment_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/comments{/number}","contents_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contents/{+path}","compare_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/merges","archive_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/downloads","issues_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues{/number}","pulls_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/pulls{/number}","milestones_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/milestones{/number}","notifications_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/labels{/name}","releases_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases{/id}","deployments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/deployments","created_at":1474414120,"updated_at":"2016-09-20T23:28:40Z","pushed_at":1474414330,"git_url":"git://github.com/TaskClusterRobot/hooks-testing.git","ssh_url":"git@github.com:TaskClusterRobot/hooks-testing.git","clone_url":"https://github.com/TaskClusterRobot/hooks-testing.git","svn_url":"https://github.com/TaskClusterRobot/hooks-testing","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master","stargazers":0,"master_branch":"master"},"pusher":{"name":"TaskClusterRobot","email":"taskcluster-accounts@mozilla.com"},"sender":{"login":"TaskClusterRobot","id":14795478,"avatar_url":"https://avatars.githubusercontent.com/u/14795478?v=3","gravatar_id":"","url":"https://api.github.com/users/TaskClusterRobot","html_url":"https://github.com/TaskClusterRobot","followers_url":"https://api.github.com/users/TaskClusterRobot/followers","following_url":"https://api.github.com/users/TaskClusterRobot/following{/other_user}","gists_url":"https://api.github.com/users/TaskClusterRobot/gists{/gist_id}","starred_url":"https://api.github.com/users/TaskClusterRobot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TaskClusterRobot/subscriptions","organizations_url":"https://api.github.com/users/TaskClusterRobot/orgs","repos_url":"https://api.github.com/users/TaskClusterRobot/repos","events_url":"https://api.github.com/users/TaskClusterRobot/events{/privacy}","received_events_url":"https://api.github.com/users/TaskClusterRobot/received_events","type":"User","site_admin":false}}} 11 | -------------------------------------------------------------------------------- /test/data/webhooks/webhook.push.offbranch.json: -------------------------------------------------------------------------------- 1 | {"headers": {"Host": "requestb.in", 2 | "Accept": "*/*", 3 | "User-Agent": "GitHub-Hookshot/6f33334", 4 | "X-GitHub-Event": "push", 5 | "X-GitHub-Delivery": "9637a980-d8fb-11e6-9830-1244ca57c95f", 6 | "content-type": "application/json", 7 | "X-Hub-Signature": "sha1=ceb38686a1f85978be5cfdcfeccdd3585c16e2bc", 8 | "Content-Length": 6705}, "body": 9 | 10 | {"ref":"refs/heads/foobar","before":"7a257a6d139708a3188bf2e0cd1f15e466a88d0e","after":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","created":false,"deleted":false,"forced":false,"base_ref":null,"compare":"https://github.com/TaskClusterRobot/hooks-testing/compare/7a257a6d1397...b79ce60be819","commits":[{"id":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","tree_id":"1e853705688e2088b1d25c7d9c3e521c80ba10cf","distinct":true,"message":"Update README.md","timestamp":"2017-01-12T11:16:14-08:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/b79ce60be819cdc482c9c6a84dc3c457959aa66f","author":{"name":"Irene","email":null,"username":"owlishDeveloper"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]}],"head_commit":{"id":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","tree_id":"1e853705688e2088b1d25c7d9c3e521c80ba10cf","distinct":true,"message":"Update README.md","timestamp":"2017-01-12T11:16:14-08:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/b79ce60be819cdc482c9c6a84dc3c457959aa66f","author":{"name":"Irene","email":null,"username":"owlishDeveloper"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]},"repository":{"id":68761376,"name":"hooks-testing","full_name":"TaskClusterRobot/hooks-testing","owner":{"name":"TaskClusterRobot","email":"taskcluster-accounts@mozilla.com"},"private":false,"html_url":"https://github.com/TaskClusterRobot/hooks-testing","description":"for testing taskcluster-github","fork":false,"url":"https://github.com/TaskClusterRobot/hooks-testing","forks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/forks","keys_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/teams","hooks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/hooks","issue_events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/events{/number}","events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/events","assignees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/assignees{/user}","branches_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/branches{/branch}","tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tags","blobs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/refs{/sha}","trees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/statuses/{sha}","languages_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/languages","stargazers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/stargazers","contributors_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contributors","subscribers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscribers","subscription_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscription","commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/commits{/sha}","git_commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/commits{/sha}","comments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/comments{/number}","issue_comment_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/comments{/number}","contents_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contents/{+path}","compare_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/merges","archive_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/downloads","issues_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues{/number}","pulls_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/pulls{/number}","milestones_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/milestones{/number}","notifications_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/labels{/name}","releases_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases{/id}","deployments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/deployments","created_at":1474414120,"updated_at":"2017-01-03T18:58:56Z","pushed_at":1484248575,"git_url":"git://github.com/TaskClusterRobot/hooks-testing.git","ssh_url":"git@github.com:TaskClusterRobot/hooks-testing.git","clone_url":"https://github.com/TaskClusterRobot/hooks-testing.git","svn_url":"https://github.com/TaskClusterRobot/hooks-testing","homepage":null,"size":24,"stargazers_count":1,"watchers_count":1,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":1,"default_branch":"master","stargazers":1,"master_branch":"master"},"pusher":{"name":"owlishDeveloper","email":null},"sender":{"login":"owlishDeveloper","id":18102552,"avatar_url":"https://avatars.githubusercontent.com/u/18102552?v=3","gravatar_id":"","url":"https://api.github.com/users/owlishDeveloper","html_url":"https://github.com/owlishDeveloper","followers_url":"https://api.github.com/users/owlishDeveloper/followers","following_url":"https://api.github.com/users/owlishDeveloper/following{/other_user}","gists_url":"https://api.github.com/users/owlishDeveloper/gists{/gist_id}","starred_url":"https://api.github.com/users/owlishDeveloper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/owlishDeveloper/subscriptions","organizations_url":"https://api.github.com/users/owlishDeveloper/orgs","repos_url":"https://api.github.com/users/owlishDeveloper/repos","events_url":"https://api.github.com/users/owlishDeveloper/events{/privacy}","received_events_url":"https://api.github.com/users/owlishDeveloper/received_events","type":"User","site_admin":false},"installation":{"id":5808}}} 11 | -------------------------------------------------------------------------------- /test/data/webhooks/webhook.push.unicode.json: -------------------------------------------------------------------------------- 1 | {"headers": {"Host": "requestb.in", 2 | "Accept": "*/*", 3 | "User-Agent": "GitHub-Hookshot/6f33334", 4 | "X-GitHub-Event": "push", 5 | "X-GitHub-Delivery": "9637a980-d8fb-11e6-9830-1244ca57c95f", 6 | "content-type": "application/json", 7 | "X-Hub-Signature": "sha1=ceb38686a1f85978be5cfdcfeccdd3585c16e2bc", 8 | "Content-Length": 6705}, "body": 9 | 10 | {"ref":"refs/heads/🌱","before":"7a257a6d139708a3188bf2e0cd1f15e466a88d0e","after":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","created":false,"deleted":false,"forced":false,"base_ref":null,"compare":"https://github.com/TaskClusterRobot/hooks-testing/compare/7a257a6d1397...b79ce60be819","commits":[{"id":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","tree_id":"1e853705688e2088b1d25c7d9c3e521c80ba10cf","distinct":true,"message":"Update README.md","timestamp":"2017-01-12T11:16:14-08:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/b79ce60be819cdc482c9c6a84dc3c457959aa66f","author":{"name":"Irene","email":null,"username":"owlishDeveloper"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]}],"head_commit":{"id":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","tree_id":"1e853705688e2088b1d25c7d9c3e521c80ba10cf","distinct":true,"message":"Update README.md","timestamp":"2017-01-12T11:16:14-08:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/b79ce60be819cdc482c9c6a84dc3c457959aa66f","author":{"name":"Irene","email":null,"username":"owlishDeveloper"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]},"repository":{"id":68761376,"name":"hooks-testing","full_name":"TaskClusterRobot/hooks-testing","owner":{"name":"TaskClusterRobot","email":"taskcluster-accounts@mozilla.com"},"private":false,"html_url":"https://github.com/TaskClusterRobot/hooks-testing","description":"for testing taskcluster-github","fork":false,"url":"https://github.com/TaskClusterRobot/hooks-testing","forks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/forks","keys_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/teams","hooks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/hooks","issue_events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/events{/number}","events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/events","assignees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/assignees{/user}","branches_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/branches{/branch}","tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tags","blobs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/refs{/sha}","trees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/statuses/{sha}","languages_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/languages","stargazers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/stargazers","contributors_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contributors","subscribers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscribers","subscription_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscription","commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/commits{/sha}","git_commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/commits{/sha}","comments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/comments{/number}","issue_comment_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/comments{/number}","contents_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contents/{+path}","compare_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/merges","archive_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/downloads","issues_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues{/number}","pulls_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/pulls{/number}","milestones_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/milestones{/number}","notifications_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/labels{/name}","releases_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases{/id}","deployments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/deployments","created_at":1474414120,"updated_at":"2017-01-03T18:58:56Z","pushed_at":1484248575,"git_url":"git://github.com/TaskClusterRobot/hooks-testing.git","ssh_url":"git@github.com:TaskClusterRobot/hooks-testing.git","clone_url":"https://github.com/TaskClusterRobot/hooks-testing.git","svn_url":"https://github.com/TaskClusterRobot/hooks-testing","homepage":null,"size":24,"stargazers_count":1,"watchers_count":1,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":1,"default_branch":"master","stargazers":1,"master_branch":"master"},"pusher":{"name":"owlishDeveloper","email":null},"sender":{"login":"owlishDeveloper","id":18102552,"avatar_url":"https://avatars.githubusercontent.com/u/18102552?v=3","gravatar_id":"","url":"https://api.github.com/users/owlishDeveloper","html_url":"https://github.com/owlishDeveloper","followers_url":"https://api.github.com/users/owlishDeveloper/followers","following_url":"https://api.github.com/users/owlishDeveloper/following{/other_user}","gists_url":"https://api.github.com/users/owlishDeveloper/gists{/gist_id}","starred_url":"https://api.github.com/users/owlishDeveloper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/owlishDeveloper/subscriptions","organizations_url":"https://api.github.com/users/owlishDeveloper/orgs","repos_url":"https://api.github.com/users/owlishDeveloper/repos","events_url":"https://api.github.com/users/owlishDeveloper/events{/privacy}","received_events_url":"https://api.github.com/users/owlishDeveloper/received_events","type":"User","site_admin":false},"installation":{"id":5808}}} 11 | -------------------------------------------------------------------------------- /test/data/webhooks/webhook.release.bad_secret.json: -------------------------------------------------------------------------------- 1 | {"headers": {"Host": "requestb.in", 2 | "Accept": "*/*", 3 | "User-Agent": "GitHub-Hookshot/7676889", 4 | "X-GitHub-Event": "release", 5 | "X-GitHub-Delivery": "2c81a200-cd36-11e6-9106-ad0d7be0e22e", 6 | "content-type": "application/json", 7 | "X-Hub-Signature": "sha1=66c244cee32d3a55f7cd0db47d5cbcf952ed5453", 8 | "Content-Length": 8055}, "body": 9 | 10 | 11 | {"action":"published","release":{"url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases/5027516","assets_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases/5027516/assets","upload_url":"https://uploads.github.com/repos/TaskClusterRobot/hooks-testing/releases/5027516/assets{?name,label}","html_url":"https://github.com/TaskClusterRobot/hooks-testing/releases/tag/testing-789","id":5027516,"tag_name":"testing-789","target_commitish":"master","name":"Testing 123","draft":false,"author":{"login":"TaskClusterRobot","id":14795478,"avatar_url":"https://avatars.githubusercontent.com/u/14795478?v=3","gravatar_id":"","url":"https://api.github.com/users/TaskClusterRobot","html_url":"https://github.com/TaskClusterRobot","followers_url":"https://api.github.com/users/TaskClusterRobot/followers","following_url":"https://api.github.com/users/TaskClusterRobot/following{/other_user}","gists_url":"https://api.github.com/users/TaskClusterRobot/gists{/gist_id}","starred_url":"https://api.github.com/users/TaskClusterRobot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TaskClusterRobot/subscriptions","organizations_url":"https://api.github.com/users/TaskClusterRobot/orgs","repos_url":"https://api.github.com/users/TaskClusterRobot/repos","events_url":"https://api.github.com/users/TaskClusterRobot/events{/privacy}","received_events_url":"https://api.github.com/users/TaskClusterRobot/received_events","type":"User","site_admin":false},"prerelease":false,"created_at":"2016-12-28T19:39:49Z","published_at":"2016-12-28T19:45:24Z","assets":[],"tarball_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tarball/testing-789","zipball_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/zipball/testing-789","body":"Testing 456"},"repository":{"id":68761376,"name":"hooks-testing","full_name":"TaskClusterRobot/hooks-testing","owner":{"login":"TaskClusterRobot","id":14795478,"avatar_url":"https://avatars.githubusercontent.com/u/14795478?v=3","gravatar_id":"","url":"https://api.github.com/users/TaskClusterRobot","html_url":"https://github.com/TaskClusterRobot","followers_url":"https://api.github.com/users/TaskClusterRobot/followers","following_url":"https://api.github.com/users/TaskClusterRobot/following{/other_user}","gists_url":"https://api.github.com/users/TaskClusterRobot/gists{/gist_id}","starred_url":"https://api.github.com/users/TaskClusterRobot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TaskClusterRobot/subscriptions","organizations_url":"https://api.github.com/users/TaskClusterRobot/orgs","repos_url":"https://api.github.com/users/TaskClusterRobot/repos","events_url":"https://api.github.com/users/TaskClusterRobot/events{/privacy}","received_events_url":"https://api.github.com/users/TaskClusterRobot/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/TaskClusterRobot/hooks-testing","description":"for testing taskcluster-github","fork":false,"url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing","forks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/forks","keys_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/teams","hooks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/hooks","issue_events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/events{/number}","events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/events","assignees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/assignees{/user}","branches_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/branches{/branch}","tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tags","blobs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/refs{/sha}","trees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/statuses/{sha}","languages_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/languages","stargazers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/stargazers","contributors_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contributors","subscribers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscribers","subscription_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscription","commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/commits{/sha}","git_commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/commits{/sha}","comments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/comments{/number}","issue_comment_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/comments{/number}","contents_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contents/{+path}","compare_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/merges","archive_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/downloads","issues_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues{/number}","pulls_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/pulls{/number}","milestones_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/milestones{/number}","notifications_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/labels{/name}","releases_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases{/id}","deployments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/deployments","created_at":"2016-09-20T23:28:40Z","updated_at":"2016-09-20T23:28:40Z","pushed_at":"2016-12-28T19:39:53Z","git_url":"git://github.com/TaskClusterRobot/hooks-testing.git","ssh_url":"git@github.com:TaskClusterRobot/hooks-testing.git","clone_url":"https://github.com/TaskClusterRobot/hooks-testing.git","svn_url":"https://github.com/TaskClusterRobot/hooks-testing","homepage":null,"size":20,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":0,"default_branch":"master"},"sender":{"login":"TaskClusterRobot","id":14795478,"avatar_url":"https://avatars.githubusercontent.com/u/14795478?v=3","gravatar_id":"","url":"https://api.github.com/users/TaskClusterRobot","html_url":"https://github.com/TaskClusterRobot","followers_url":"https://api.github.com/users/TaskClusterRobot/followers","following_url":"https://api.github.com/users/TaskClusterRobot/following{/other_user}","gists_url":"https://api.github.com/users/TaskClusterRobot/gists{/gist_id}","starred_url":"https://api.github.com/users/TaskClusterRobot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TaskClusterRobot/subscriptions","organizations_url":"https://api.github.com/users/TaskClusterRobot/orgs","repos_url":"https://api.github.com/users/TaskClusterRobot/repos","events_url":"https://api.github.com/users/TaskClusterRobot/events{/privacy}","received_events_url":"https://api.github.com/users/TaskClusterRobot/received_events","type": "User","site_admin":false}}} 12 | -------------------------------------------------------------------------------- /test/data/webhooks/webhook.release.json: -------------------------------------------------------------------------------- 1 | {"headers": {"Host": "requestb.in", 2 | "Accept": "*/*", 3 | "User-Agent": "GitHub-Hookshot/7676889", 4 | "X-GitHub-Event": "release", 5 | "X-GitHub-Delivery": "2c81a200-cd36-11e6-9106-ad0d7be0e22e", 6 | "content-type": "application/json", 7 | "X-Hub-Signature": "sha1=4081d9cd2caa4323adf3d1308b1cddcfce4c0868", 8 | "Content-Length": 8082}, "body": 9 | 10 | 11 | {"action":"published","release":{"url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases/5027516","assets_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases/5027516/assets","upload_url":"https://uploads.github.com/repos/TaskClusterRobot/hooks-testing/releases/5027516/assets{?name,label}","html_url":"https://github.com/TaskClusterRobot/hooks-testing/releases/tag/testing-789","id":5027516,"tag_name":"testing-789","target_commitish":"master","name":"Testing 123","draft":false,"author":{"login":"TaskClusterRobot","id":14795478,"avatar_url":"https://avatars.githubusercontent.com/u/14795478?v=3","gravatar_id":"","url":"https://api.github.com/users/TaskClusterRobot","html_url":"https://github.com/TaskClusterRobot","followers_url":"https://api.github.com/users/TaskClusterRobot/followers","following_url":"https://api.github.com/users/TaskClusterRobot/following{/other_user}","gists_url":"https://api.github.com/users/TaskClusterRobot/gists{/gist_id}","starred_url":"https://api.github.com/users/TaskClusterRobot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TaskClusterRobot/subscriptions","organizations_url":"https://api.github.com/users/TaskClusterRobot/orgs","repos_url":"https://api.github.com/users/TaskClusterRobot/repos","events_url":"https://api.github.com/users/TaskClusterRobot/events{/privacy}","received_events_url":"https://api.github.com/users/TaskClusterRobot/received_events","type":"User","site_admin":false},"prerelease":false,"created_at":"2016-12-28T19:39:49Z","published_at":"2016-12-28T19:45:24Z","assets":[],"tarball_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tarball/testing-789","zipball_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/zipball/testing-789","body":"Testing 456"},"repository":{"id":68761376,"name":"hooks-testing","full_name":"TaskClusterRobot/hooks-testing","owner":{"login":"TaskClusterRobot","id":14795478,"avatar_url":"https://avatars.githubusercontent.com/u/14795478?v=3","gravatar_id":"","url":"https://api.github.com/users/TaskClusterRobot","html_url":"https://github.com/TaskClusterRobot","followers_url":"https://api.github.com/users/TaskClusterRobot/followers","following_url":"https://api.github.com/users/TaskClusterRobot/following{/other_user}","gists_url":"https://api.github.com/users/TaskClusterRobot/gists{/gist_id}","starred_url":"https://api.github.com/users/TaskClusterRobot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TaskClusterRobot/subscriptions","organizations_url":"https://api.github.com/users/TaskClusterRobot/orgs","repos_url":"https://api.github.com/users/TaskClusterRobot/repos","events_url":"https://api.github.com/users/TaskClusterRobot/events{/privacy}","received_events_url":"https://api.github.com/users/TaskClusterRobot/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/TaskClusterRobot/hooks-testing","description":"for testing taskcluster-github","fork":false,"url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing","forks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/forks","keys_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/teams","hooks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/hooks","issue_events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/events{/number}","events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/events","assignees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/assignees{/user}","branches_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/branches{/branch}","tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tags","blobs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/refs{/sha}","trees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/statuses/{sha}","languages_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/languages","stargazers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/stargazers","contributors_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contributors","subscribers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscribers","subscription_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscription","commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/commits{/sha}","git_commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/commits{/sha}","comments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/comments{/number}","issue_comment_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/comments{/number}","contents_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contents/{+path}","compare_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/merges","archive_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/downloads","issues_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues{/number}","pulls_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/pulls{/number}","milestones_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/milestones{/number}","notifications_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/labels{/name}","releases_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases{/id}","deployments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/deployments","created_at":"2016-09-20T23:28:40Z","updated_at":"2016-09-20T23:28:40Z","pushed_at":"2016-12-28T19:39:53Z","git_url":"git://github.com/TaskClusterRobot/hooks-testing.git","ssh_url":"git@github.com:TaskClusterRobot/hooks-testing.git","clone_url":"https://github.com/TaskClusterRobot/hooks-testing.git","svn_url":"https://github.com/TaskClusterRobot/hooks-testing","homepage":null,"size":20,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":0,"default_branch":"master"},"sender":{"login":"TaskClusterRobot","id":14795478,"avatar_url":"https://avatars.githubusercontent.com/u/14795478?v=3","gravatar_id":"","url":"https://api.github.com/users/TaskClusterRobot","html_url":"https://github.com/TaskClusterRobot","followers_url":"https://api.github.com/users/TaskClusterRobot/followers","following_url":"https://api.github.com/users/TaskClusterRobot/following{/other_user}","gists_url":"https://api.github.com/users/TaskClusterRobot/gists{/gist_id}","starred_url":"https://api.github.com/users/TaskClusterRobot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TaskClusterRobot/subscriptions","organizations_url":"https://api.github.com/users/TaskClusterRobot/orgs","repos_url":"https://api.github.com/users/TaskClusterRobot/repos","events_url":"https://api.github.com/users/TaskClusterRobot/events{/privacy}","received_events_url":"https://api.github.com/users/TaskClusterRobot/received_events","type":"User","site_admin":false},"installation":{"id":5808}}} 12 | -------------------------------------------------------------------------------- /test/data/webhooks/webhook.tag_push.json: -------------------------------------------------------------------------------- 1 | {"headers": {"Host": "requestb.in", 2 | "Accept": "*/*", 3 | "User-Agent": "GitHub-Hookshot/6f33334", 4 | "X-GitHub-Event": "push", 5 | "X-GitHub-Delivery": "9637a980-d8fb-11e6-9830-1244ca57c95f", 6 | "content-type": "application/json", 7 | "X-Hub-Signature": "sha1=ecbb00f92f620b2d7bc2773a642d2192c83db96b", 8 | "Content-Length": 6161}, "body": 9 | 10 | {"ref":"refs/tags/v1.0.2","before":"0000000000000000000000000000000000000000","after":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","created":true,"deleted":false,"forced":false,"base_ref":null,"compare":"https://github.com/TaskClusterRobot/hooks-testing/compare/v1.0.2","commits":[],"head_commit":{"id":"b79ce60be819cdc482c9c6a84dc3c457959aa66f","tree_id":"1e853705688e2088b1d25c7d9c3e521c80ba10cf","distinct":true,"message":"Update README.md","timestamp":"2017-01-12T11:16:14-08:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/b79ce60be819cdc482c9c6a84dc3c457959aa66f","author":{"name":"Irene","email":null,"username":"owlishDeveloper"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]},"repository":{"id":68761376,"name":"hooks-testing","full_name":"TaskClusterRobot/hooks-testing","owner":{"name":"TaskClusterRobot","email":"taskcluster-accounts@mozilla.com"},"private":false,"html_url":"https://github.com/TaskClusterRobot/hooks-testing","description":"","fork":false,"url":"https://github.com/TaskClusterRobot/hooks-testing","forks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/forks","keys_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/teams","hooks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/hooks","issue_events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/events{/number}","events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/events","assignees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/assignees{/user}","branches_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/branches{/branch}","tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tags","blobs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/refs{/sha}","trees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/statuses/{sha}","languages_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/languages","stargazers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/stargazers","contributors_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contributors","subscribers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscribers","subscription_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscription","commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/commits{/sha}","git_commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/commits{/sha}","comments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/comments{/number}","issue_comment_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/comments{/number}","contents_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contents/{+path}","compare_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/merges","archive_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/downloads","issues_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues{/number}","pulls_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/pulls{/number}","milestones_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/milestones{/number}","notifications_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/labels{/name}","releases_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases{/id}","deployments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/deployments","created_at":1474414120,"updated_at":"2017-01-03T18:58:56Z","pushed_at":1484248575,"git_url":"git://github.com/TaskClusterRobot/hooks-testing.git","ssh_url":"git@github.com:TaskClusterRobot/hooks-testing.git","clone_url":"https://github.com/TaskClusterRobot/hooks-testing.git","svn_url":"https://github.com/TaskClusterRobot/hooks-testing","homepage":null,"size":24,"stargazers_count":1,"watchers_count":1,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":1,"default_branch":"master","stargazers":1,"master_branch":"master"},"pusher":{"name":"owlishDeveloper","email":null},"sender":{"login":"owlishDeveloper","id":18102552,"avatar_url":"https://avatars.githubusercontent.com/u/18102552?v=3","gravatar_id":"","url":"https://api.github.com/users/owlishDeveloper","html_url":"https://github.com/owlishDeveloper","followers_url":"https://api.github.com/users/owlishDeveloper/followers","following_url":"https://api.github.com/users/owlishDeveloper/following{/other_user}","gists_url":"https://api.github.com/users/owlishDeveloper/gists{/gist_id}","starred_url":"https://api.github.com/users/owlishDeveloper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/owlishDeveloper/subscriptions","organizations_url":"https://api.github.com/users/owlishDeveloper/orgs","repos_url":"https://api.github.com/users/owlishDeveloper/repos","events_url":"https://api.github.com/users/owlishDeveloper/events{/privacy}","received_events_url":"https://api.github.com/users/owlishDeveloper/received_events","type":"User","site_admin":false},"installation":{"id":5808}}} 11 | -------------------------------------------------------------------------------- /test/data/webhooks/webhook.unknown_event.json: -------------------------------------------------------------------------------- 1 | {"headers": {"Host": "requestb.in", 2 | "Accept": "*/*", 3 | "User-Agent": "GitHub-Hookshot/f5d5ca1", 4 | "X-GitHub-Event": "unknown-events-are-fun", 5 | "X-GitHub-Delivery": "736b3900-7f8a-11e6-8ec8-b75aea82a0ef", 6 | "content-type": "application/json", 7 | "X-Hub-Signature": "sha1=79fa92aab27dbb6ee39fe95138d05e3b2ce47601", 8 | "Content-Length": 6800}, "body": 9 | 10 | 11 | {"ref":"refs/heads/master","before":"efd1d89119e819a71a645c95c2590c0b3e7d0c83","after":"1edbd696be128a1ac187fac97e6a209fe9df3745","created":false,"deleted":false,"forced":false,"base_ref":null,"compare":"https://github.com/TaskClusterRobot/hooks-testing/compare/efd1d89119e8...1edbd696be12","commits":[{"id":"1edbd696be128a1ac187fac97e6a209fe9df3745","tree_id":"e9923f58e4cd74dc850443bc096784f9aaf69265","distinct":true,"message":"Update README.md","timestamp":"2016-09-20T16:32:10-07:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/1edbd696be128a1ac187fac97e6a209fe9df3745","author":{"name":"Lisa Lionheart","email":"taskcluster-accounts@mozilla.com","username":"TaskClusterRobot"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]}],"head_commit":{"id":"1edbd696be128a1ac187fac97e6a209fe9df3745","tree_id":"e9923f58e4cd74dc850443bc096784f9aaf69265","distinct":true,"message":"Update README.md","timestamp":"2016-09-20T16:32:10-07:00","url":"https://github.com/TaskClusterRobot/hooks-testing/commit/1edbd696be128a1ac187fac97e6a209fe9df3745","author":{"name":"Lisa Lionheart","email":"taskcluster-accounts@mozilla.com","username":"TaskClusterRobot"},"committer":{"name":"GitHub","email":"noreply@github.com","username":"web-flow"},"added":[],"removed":[],"modified":["README.md"]},"repository":{"id":68761376,"name":"hooks-testing","full_name":"TaskClusterRobot/hooks-testing","owner":{"name":"TaskClusterRobot","email":"taskcluster-accounts@mozilla.com"},"private":false,"html_url":"https://github.com/TaskClusterRobot/hooks-testing","description":"for testing taskcluster-github","fork":false,"url":"https://github.com/TaskClusterRobot/hooks-testing","forks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/forks","keys_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/teams","hooks_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/hooks","issue_events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/events{/number}","events_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/events","assignees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/assignees{/user}","branches_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/branches{/branch}","tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/tags","blobs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/refs{/sha}","trees_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/statuses/{sha}","languages_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/languages","stargazers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/stargazers","contributors_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contributors","subscribers_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscribers","subscription_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/subscription","commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/commits{/sha}","git_commits_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/git/commits{/sha}","comments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/comments{/number}","issue_comment_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues/comments{/number}","contents_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/contents/{+path}","compare_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/merges","archive_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/downloads","issues_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/issues{/number}","pulls_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/pulls{/number}","milestones_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/milestones{/number}","notifications_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/labels{/name}","releases_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases{/id}","deployments_url":"https://api.github.com/repos/TaskClusterRobot/hooks-testing/deployments","created_at":1474414120,"updated_at":"2016-09-20T23:28:40Z","pushed_at":1474414330,"git_url":"git://github.com/TaskClusterRobot/hooks-testing.git","ssh_url":"git@github.com:TaskClusterRobot/hooks-testing.git","clone_url":"https://github.com/TaskClusterRobot/hooks-testing.git","svn_url":"https://github.com/TaskClusterRobot/hooks-testing","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master","stargazers":0,"master_branch":"master"},"pusher":{"name":"TaskClusterRobot","email":"taskcluster-accounts@mozilla.com"},"sender":{"login":"TaskClusterRobot","id":14795478,"avatar_url":"https://avatars.githubusercontent.com/u/14795478?v=3","gravatar_id":"","url":"https://api.github.com/users/TaskClusterRobot","html_url":"https://github.com/TaskClusterRobot","followers_url":"https://api.github.com/users/TaskClusterRobot/followers","following_url":"https://api.github.com/users/TaskClusterRobot/following{/other_user}","gists_url":"https://api.github.com/users/TaskClusterRobot/gists{/gist_id}","starred_url":"https://api.github.com/users/TaskClusterRobot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TaskClusterRobot/subscriptions","organizations_url":"https://api.github.com/users/TaskClusterRobot/orgs","repos_url":"https://api.github.com/users/TaskClusterRobot/repos","events_url":"https://api.github.com/users/TaskClusterRobot/events{/privacy}","received_events_url":"https://api.github.com/users/TaskClusterRobot/received_events","type":"User","site_admin":false}}} 12 | -------------------------------------------------------------------------------- /test/fake_github_test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const _ = require('lodash'); 3 | const github = require('@octokit/rest'); 4 | const fakeGithubAuth = require('./github-auth'); 5 | 6 | suite('fake github', function() { 7 | 8 | function checkKeys(obj, platonic) { 9 | let ours = _.filter(Object.keys(obj), k => !k.startsWith('_')); 10 | let theirs = Object.keys(platonic); 11 | assert.deepEqual(_.difference(ours, theirs), []); 12 | _.forEach(ours, k => { 13 | if (_.isObject(obj[k]) && obj[k].isSinonProxy) { 14 | checkKeys(obj[k], platonic[k]); 15 | } 16 | }); 17 | } 18 | 19 | test('matches real lib', async function() { 20 | let inst = await fakeGithubAuth().getInstallationGithub(); 21 | checkKeys(inst, github()); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/github-auth.js: -------------------------------------------------------------------------------- 1 | const Debug = require('debug'); 2 | const sinon = require('sinon'); 3 | const _ = require('lodash'); 4 | const assert = require('assert'); 5 | 6 | class FakeGithub { 7 | constructor(installation_id) { 8 | this._installedOn = null; 9 | this._taskcluster_yml_files = {}; 10 | this._org_membership = {}; 11 | this._repo_collaborators = {}; 12 | this._github_users = []; 13 | this._repo_info = {}; 14 | this._repositories = {}; 15 | this._statuses = {}; 16 | this._comments = {}; 17 | 18 | const throwError = code => { 19 | let err = new Error(); 20 | err.code = code; 21 | throw err; 22 | }; 23 | 24 | const stubs = { 25 | 'repos.createStatus': ({owner, repo, sha, state, target_url, description, context}) => { 26 | if (repo === 'no-permission') { 27 | throwError(403); 28 | } 29 | const key = `${owner}/${repo}@${sha}`; 30 | const info = { 31 | state, 32 | target_url, 33 | description, 34 | context, 35 | }; 36 | if (!this._statuses[key]) { 37 | this._statuses[key] = []; 38 | } 39 | this._statuses[key].push(info); 40 | }, 41 | 'issues.createComment': ({owner, repo, number, body}) => { 42 | if (repo === 'no-permission') { 43 | throwError(403); 44 | } 45 | const key = `${owner}/${repo}@${number}`; 46 | const info = { 47 | body, 48 | }; 49 | if (!this._comments[key]) { 50 | this._comments[key]=[]; 51 | } 52 | this._comments[key].push(info); 53 | }, 54 | 'repos.createCommitComment': () => {}, 55 | 'orgs.checkMembership': async ({org, username}) => { 56 | if (this._org_membership[org] && this._org_membership[org].has(username)) { 57 | return {}; 58 | } else { 59 | throwError(404); 60 | } 61 | }, 62 | 'repos.checkCollaborator': async ({owner, repo, username}) => { 63 | const key = `${owner}/${repo}`; 64 | if (this._repo_collaborators[key] && this._repo_collaborators[key].has(username)) { 65 | return {}; 66 | } else { 67 | throwError(404); 68 | } 69 | }, 70 | 'repos.get': async ({owner, repo}) => { 71 | const key = `${owner}/${repo}`; 72 | if (this._repo_info[key]) { 73 | return {data: this._repo_info[key]}; 74 | } else { 75 | throwError(404); 76 | } 77 | }, 78 | 'repos.getContents': async ({owner, repo, path, ref}) => { 79 | assert.equal(path, '.taskcluster.yml'); 80 | const key = `${owner}/${repo}@${ref}`; 81 | if (this._taskcluster_yml_files[key]) { 82 | return {data: {content: new Buffer( 83 | JSON.stringify(this._taskcluster_yml_files[key]) 84 | ).toString('base64')}}; 85 | } else { 86 | let err = new Error(); 87 | err.code = 404; 88 | throw err; 89 | } 90 | }, 91 | 'users.getByUsername': async ({username}) => { 92 | let user = _.find(this._github_users, {username}); 93 | if (user) { 94 | return {data: user}; 95 | } else { 96 | throwError(404); 97 | } 98 | }, 99 | 'apps.listRepos': async () => { 100 | return {data: this._repositories}; 101 | }, 102 | 'repos.listStatusesForRef': async ({owner, repo, ref}) => { 103 | const key = `${owner}/${repo}@${ref}`; 104 | if (this._statuses[key]) { 105 | return {data: this._statuses[key]}; 106 | } else { 107 | throwError(404); 108 | } 109 | }, 110 | 'checks.create': async ({owner, repo, name, head_sha, output, details_url, actions, status, conclusion}) => { 111 | if (repo === 'no-permission') { 112 | throwError(403); 113 | } 114 | 115 | const check_run_id = Math.floor(Math.random()*(9999-1000)) + 1000; 116 | 117 | return { 118 | data: { 119 | id: check_run_id, 120 | check_suite: {id: 5555}, 121 | }, 122 | }; 123 | }, 124 | 'checks.update': async ({repo}) => { 125 | if (repo === 'no-permission') { 126 | throwError(403); 127 | } 128 | return {}; 129 | }, 130 | }; 131 | 132 | const debug = Debug('FakeGithub'); 133 | _.forEach(stubs, (implementation, name) => { 134 | let atoms = name.split(/\./); 135 | let obj = this; // eslint-disable-line consistent-this 136 | while (atoms.length > 1) { 137 | const atom = atoms.shift(); 138 | if (!obj[atom]) { 139 | obj[atom] = {}; 140 | } 141 | obj = obj[atom]; 142 | } 143 | 144 | const atom = atoms.shift(); 145 | obj[atom] = sinon.spy(async (options) => { 146 | debug(`inst(${installation_id}).${name}(${JSON.stringify(options)})`); 147 | return await (implementation || (() => {}))(options); 148 | }); 149 | }); 150 | } 151 | 152 | setTaskclusterYml({owner, repo, ref, content}) { 153 | const key = `${owner}/${repo}@${ref}`; 154 | this._taskcluster_yml_files[key] = content; 155 | } 156 | 157 | setOrgMember({org, member}) { 158 | if (!this._org_membership[org]) { 159 | this._org_membership[org] = new Set(); 160 | } 161 | this._org_membership[org].add(member); 162 | } 163 | 164 | setRepoCollaborator({owner, repo, username}) { 165 | const key = `${owner}/${repo}`; 166 | if (!this._repo_collaborators[key]) { 167 | this._repo_collaborators[key] = new Set(); 168 | } 169 | this._repo_collaborators[key].add(username); 170 | } 171 | 172 | setRepoInfo({owner, repo, info}) { 173 | const key = `${owner}/${repo}`; 174 | this._repo_info[key] = info; 175 | } 176 | 177 | setUser({id, email, username}) { 178 | assert(id, 'must provide id to setUser'); 179 | assert(email, 'must provide email to setUser'); 180 | assert(username, 'must provide username to setUser'); 181 | this._github_users.push({id, email, username}); 182 | } 183 | 184 | setRepositories(...repoNames) { 185 | // This function accepts 1 to n strings 186 | this._repositories.repositories = [...repoNames].map(repo => {return {name: repo};}); 187 | this._repositories.total_count = this._repositories.repositories.length; 188 | } 189 | 190 | setStatuses({owner, repo, ref, info}) { 191 | const key = `${owner}/${repo}@${ref}`; 192 | this._statuses[key] = info; 193 | } 194 | 195 | listStatusesForRef({owner, repo, ref}) { 196 | const key = `${owner}/${repo}@${ref}`; 197 | return {data: this._statuses[key]}; 198 | } 199 | 200 | getComments({owner, repo, number}) { 201 | const key = `${owner}/${repo}@${number}`; 202 | return {data: this._comments[key]}; 203 | } 204 | 205 | hasNextPage() { 206 | return false; 207 | } 208 | } 209 | 210 | class FakeGithubAuth { 211 | constructor() { 212 | this.installations = {}; 213 | } 214 | 215 | resetStubs() { 216 | this.installations = {}; 217 | } 218 | 219 | async getInstallationGithub(installation_id) { 220 | return this.inst(installation_id); 221 | } 222 | 223 | // sync shorthand to getInstallationGithub for use in test scripts 224 | inst(installation_id) { 225 | if (!(installation_id in this.installations)) { 226 | this.installations[installation_id] = new FakeGithub(installation_id); 227 | } 228 | return this.installations[installation_id]; 229 | } 230 | 231 | // For testing purposes, insert a new install 232 | createInstall(installation_id, owner, repos) { 233 | let installation = new FakeGithub(installation_id); 234 | installation._installedOn = owner; 235 | installation.setRepositories(...repos); 236 | this.installations[installation_id] = installation; 237 | } 238 | 239 | async getIntegrationGithub() { 240 | return { 241 | apps: { 242 | getInstallations: async () => { 243 | return {data: _.map(this.installations, (install, id) => ({ 244 | id: parseInt(id, 10), 245 | account: {login: install._installedOn}, 246 | }))}; 247 | }, 248 | }, 249 | }; 250 | } 251 | } 252 | 253 | module.exports = () => new FakeGithubAuth(); 254 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const fs = require('fs'); 3 | const _ = require('lodash'); 4 | const slugid = require('slugid'); 5 | const builder = require('../src/api'); 6 | const taskcluster = require('taskcluster-client'); 7 | const load = require('../src/main'); 8 | const fakeGithubAuth = require('./github-auth'); 9 | const data = require('../src/data'); 10 | const libUrls = require('taskcluster-lib-urls'); 11 | const {fakeauth, stickyLoader, Secrets} = require('taskcluster-lib-testing'); 12 | const {FakeClient} = require('taskcluster-lib-pulse'); 13 | 14 | exports.load = stickyLoader(load); 15 | 16 | suiteSetup(async function() { 17 | exports.load.inject('profile', 'test'); 18 | exports.load.inject('process', 'test'); 19 | let fakePulseClient = new FakeClient(); 20 | fakePulseClient.namespace = 'taskcluster-fake'; 21 | exports.load.inject('pulseClient', fakePulseClient); 22 | }); 23 | 24 | // set up the testing secrets 25 | exports.secrets = new Secrets({ 26 | secretName: 'project/taskcluster/testing/taskcluster-github', 27 | secrets: { 28 | taskcluster: [ 29 | {env: 'TASKCLUSTER_ROOT_URL', cfg: 'taskcluster.rootUrl', name: 'rootUrl'}, 30 | {env: 'TASKCLUSTER_CLIENT_ID', cfg: 'taskcluster.credentials.clientId', name: 'clientId'}, 31 | {env: 'TASKCLUSTER_ACCESS_TOKEN', cfg: 'taskcluster.credentials.accessToken', name: 'accessToken'}, 32 | ], 33 | }, 34 | load: exports.load, 35 | }); 36 | 37 | // Build an http request from a json file with fields describing 38 | // headers and a body 39 | exports.jsonHttpRequest = function(jsonFile, options) { 40 | let defaultOptions = { 41 | hostname: 'localhost', 42 | port: 60415, 43 | path: '/api/github/v1/github', 44 | method: 'POST', 45 | }; 46 | options = _.defaultsDeep(options, defaultOptions); 47 | let jsonData = JSON.parse(fs.readFileSync(jsonFile)); 48 | options.headers = jsonData.headers; 49 | 50 | return new Promise (function(accept, reject) { 51 | try { 52 | let req = http.request(options, accept); 53 | req.write(JSON.stringify(jsonData.body)); 54 | req.end(); 55 | } catch (e) { 56 | reject(e); 57 | } 58 | }); 59 | }; 60 | 61 | /** 62 | * Set up a fake publisher. Call this before withServer to ensure the server 63 | * uses the same publisher. 64 | */ 65 | exports.withFakePublisher = (mock, skipping) => { 66 | suiteSetup(async function() { 67 | if (skipping()) { 68 | return; 69 | } 70 | exports.load.save(); 71 | 72 | exports.load.cfg('taskcluster.rootUrl', libUrls.testRootUrl()); 73 | await exports.load('publisher'); 74 | }); 75 | 76 | suiteTeardown(function() { 77 | if (skipping()) { 78 | return; 79 | } 80 | exports.load.restore(); 81 | }); 82 | }; 83 | 84 | /** 85 | * Set helper.Builds and helper.OwnersDirectory to fully-configured entity 86 | * objects, and inject them into the loader. These tables are cleared at 87 | * suiteSetup, but not between test cases. 88 | */ 89 | exports.withEntities = (mock, skipping) => { 90 | suiteSetup(async function() { 91 | if (skipping()) { 92 | return; 93 | } 94 | 95 | // Need to generate these each time so that pushes and PRs can run at the same time 96 | // Maybe at some point we should cook up a real solution to this. 97 | const tableVersion = slugid.nice().replace(/[_-]/g, ''); 98 | exports.buildsTableName = `TaskclusterGithubBuildsV${tableVersion}`; 99 | exports.ownersTableName = `TaskclusterIntegrationOwnersV${tableVersion}`; 100 | exports.checkRunsTableName = `TaskclusterCheckRunsV${tableVersion}`; 101 | exports.load.cfg('app.buildsTableName', exports.buildsTableName); 102 | exports.load.cfg('app.ownersDirectoryTableName', exports.ownersTableName); 103 | exports.load.cfg('app.checkRunsTableName', exports.checkRunsTableName); 104 | 105 | if (mock) { 106 | const cfg = await exports.load('cfg'); 107 | exports.load.inject('Builds', data.Builds.setup({ 108 | tableName: 'Builds', 109 | credentials: 'inMemory', 110 | })); 111 | exports.load.inject('OwnersDirectory', data.OwnersDirectory.setup({ 112 | tableName: 'OwnersDirectory', 113 | credentials: 'inMemory', 114 | })); 115 | exports.load.inject('CheckRuns', data.CheckRuns.setup({ 116 | tableName: 'CheckRuns', 117 | credentials: 'inMemory', 118 | })); 119 | } 120 | 121 | exports.Builds = await exports.load('Builds'); 122 | await exports.Builds.ensureTable(); 123 | 124 | exports.OwnersDirectory = await exports.load('OwnersDirectory'); 125 | await exports.OwnersDirectory.ensureTable(); 126 | 127 | exports.CheckRuns = await exports.load('CheckRuns'); 128 | await exports.CheckRuns.ensureTable(); 129 | }); 130 | 131 | const cleanup = async () => { 132 | if (!skipping()) { 133 | await exports.Builds.scan({}, {handler: secret => secret.remove()}); 134 | await exports.OwnersDirectory.scan({}, {handler: secret => secret.remove()}); 135 | await exports.CheckRuns.scan({}, {handler: secret => secret.remove()}); 136 | } 137 | }; 138 | suiteSetup(cleanup); 139 | suiteTeardown(cleanup); 140 | }; 141 | 142 | /** 143 | * Set the `github` loader component to a fake version. 144 | * This is reset before each test. Call this before withServer. 145 | */ 146 | exports.withFakeGithub = (mock, skipping) => { 147 | suiteSetup(function() { 148 | exports.load.inject('github', fakeGithubAuth()); 149 | }); 150 | 151 | suiteTeardown(function() { 152 | exports.load.remove('github'); 153 | }); 154 | 155 | setup(async function() { 156 | let fakeGithub = await exports.load('github'); 157 | fakeGithub.resetStubs(); 158 | }); 159 | }; 160 | 161 | /** 162 | * Set up an API server. Call this after withEntities, so the server 163 | * uses the same entities classes. 164 | * 165 | * This also sets up helper.apiClient as a client of the service API. 166 | */ 167 | exports.withServer = (mock, skipping) => { 168 | let webServer; 169 | 170 | suiteSetup(async function() { 171 | if (skipping()) { 172 | return; 173 | } 174 | const cfg = await exports.load('cfg'); 175 | 176 | // even if we are using a "real" rootUrl for access to Azure, we use 177 | // a local rootUrl to test the API, including mocking auth on that 178 | // rootUrl. 179 | const rootUrl = 'http://localhost:60415'; 180 | exports.load.cfg('taskcluster.rootUrl', rootUrl); 181 | exports.load.cfg('taskcluster.clientId', null); 182 | exports.load.cfg('taskcluster.accessToken', null); 183 | 184 | fakeauth.start({'test-client': ['*']}, {rootUrl}); 185 | 186 | const GithubClient = taskcluster.createClient(builder.reference()); 187 | 188 | exports.apiClient = new GithubClient({ 189 | credentials: {clientId: 'test-client', accessToken: 'unused'}, 190 | rootUrl, 191 | }); 192 | 193 | webServer = await exports.load('server'); 194 | }); 195 | 196 | suiteTeardown(async function() { 197 | if (skipping()) { 198 | return; 199 | } 200 | if (webServer) { 201 | await webServer.terminate(); 202 | webServer = null; 203 | } 204 | fakeauth.stop(); 205 | }); 206 | }; 207 | -------------------------------------------------------------------------------- /test/invalid-yaml.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 0, 3 | "WHAT WHAT WHAT?": {}, 4 | "tasks": [ 5 | { 6 | "extra": { 7 | "github": { 8 | "env": true 9 | } 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --ui tdd 2 | --timeout 240s 3 | --reporter spec 4 | -------------------------------------------------------------------------------- /test/pr-allowed_test.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('test'); 2 | const helper = require('./helper'); 3 | const assert = require('assert'); 4 | const prAllowed = require('../src/pr-allowed'); 5 | 6 | suite('allowPullRequests', function() { 7 | helper.withFakeGithub(false, () => false); 8 | 9 | let github = null; 10 | 11 | setup(async function() { 12 | github = await helper.load('github'); 13 | }); 14 | 15 | suite('getRepoPolicy', function() { 16 | setup(function() { 17 | github.inst(9999).setRepoInfo({ 18 | owner: 'taskcluster', 19 | repo: 'testing', 20 | info: {default_branch: 'development'}, 21 | }); 22 | }); 23 | 24 | test('returns "collaborators" when no .taskcluster.yml exists', async function() { 25 | assert.equal(await prAllowed.getRepoPolicy({ 26 | organization: 'taskcluster', 27 | repository: 'testing', 28 | instGithub: github.inst(9999), 29 | debug, 30 | }), 'collaborators'); 31 | }); 32 | 33 | test('returns "collaborators" when .taskcluster.yml omits allowPullRequests', async function() { 34 | github.inst(9999).setTaskclusterYml({ 35 | owner: 'taskcluster', 36 | repo: 'testing', 37 | ref: 'development', 38 | content: {}, 39 | }); 40 | 41 | assert.equal(await prAllowed.getRepoPolicy({ 42 | organization: 'taskcluster', 43 | repository: 'testing', 44 | instGithub: github.inst(9999), 45 | debug, 46 | }), 'collaborators'); 47 | }); 48 | 49 | test('returns value of allowPullRequests from .taskcluster.yml on default branch', async function() { 50 | github.inst(9999).setTaskclusterYml({ 51 | owner: 'taskcluster', 52 | repo: 'testing', 53 | ref: 'development', 54 | content: {allowPullRequests: 'maybe'}, 55 | }); 56 | 57 | assert.equal(await prAllowed.getRepoPolicy({ 58 | organization: 'taskcluster', 59 | repository: 'testing', 60 | instGithub: github.inst(9999), 61 | debug, 62 | }), 'maybe'); 63 | }); 64 | 65 | test('looks at policy.pullRequests for v1', async function() { 66 | github.inst(9999).setTaskclusterYml({ 67 | owner: 'taskcluster', 68 | repo: 'testing', 69 | ref: 'development', 70 | content: {version: 1, policy: {pullRequests: 'maybe'}}, 71 | }); 72 | 73 | assert.equal(await prAllowed.getRepoPolicy({ 74 | organization: 'taskcluster', 75 | repository: 'testing', 76 | instGithub: github.inst(9999), 77 | debug, 78 | }), 'maybe'); 79 | }); 80 | 81 | test('v1: handles policy without pullRequests property', async function() { 82 | github.inst(9999).setTaskclusterYml({ 83 | owner: 'taskcluster', 84 | repo: 'testing', 85 | ref: 'development', 86 | content: {version: 1, policy: {otherPolicy: 'sure'}}, 87 | }); 88 | 89 | assert.equal(await prAllowed.getRepoPolicy({ 90 | organization: 'taskcluster', 91 | repository: 'testing', 92 | instGithub: github.inst(9999), 93 | debug, 94 | }), 'collaborators'); 95 | }); 96 | 97 | test('v1: handles tc.yml without policy property', async function() { 98 | github.inst(9999).setTaskclusterYml({ 99 | owner: 'taskcluster', 100 | repo: 'testing', 101 | ref: 'development', 102 | content: {version: 1, tasks: []}, 103 | }); 104 | 105 | assert.equal(await prAllowed.getRepoPolicy({ 106 | organization: 'taskcluster', 107 | repository: 'testing', 108 | instGithub: github.inst(9999), 109 | debug, 110 | }), 'collaborators'); 111 | }); 112 | }); 113 | 114 | suite('isCollaborator', function() { 115 | test('disallows the case where the login is an org member but not collaborator', async function() { 116 | // (this is a behavior change from old behavior; the code doesn't even call the github method) 117 | github.inst(9999).setOrgMember({org: 'buildbot', member: 'djmitche'}); 118 | assert.equal(await prAllowed.isCollaborator({ 119 | login: 'djmitche', 120 | organization: 'buildbot', 121 | repository: 'buildbot', 122 | sha: 'abcd', 123 | instGithub: github.inst(9999), 124 | debug, 125 | }), false); 126 | }); 127 | 128 | test('allows the case where the login is a repo collaborator but not org member', async function() { 129 | github.inst(9999).setRepoCollaborator({owner: 'buildbot', repo: 'bbdocs', username: 'djmitche'}); 130 | assert.equal(await prAllowed.isCollaborator({ 131 | login: 'djmitche', 132 | organization: 'buildbot', 133 | repository: 'bbdocs', 134 | sha: 'abcd', 135 | instGithub: github.inst(9999), 136 | debug, 137 | }), true); 138 | }); 139 | 140 | test('disallows the case where none of this is true', async function() { 141 | assert.equal(await prAllowed.isCollaborator({ 142 | login: 'djmitche', 143 | organization: 'buildbot', 144 | repository: 'bbdocs', 145 | sha: 'abcd', 146 | instGithub: github.inst(9999), 147 | debug, 148 | }), false); 149 | }); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /test/pulse_test.js: -------------------------------------------------------------------------------- 1 | const helper = require('./helper'); 2 | const assert = require('assert'); 3 | const libUrls = require('taskcluster-lib-urls'); 4 | 5 | helper.secrets.mockSuite('pulse', ['taskcluster'], function(mock, skipping) { 6 | helper.withEntities(mock, skipping); 7 | helper.withFakePublisher(mock, skipping); 8 | helper.withFakeGithub(mock, skipping); 9 | helper.withServer(mock, skipping); 10 | 11 | let github = null; 12 | let publisher = null; 13 | 14 | setup(async function() { 15 | await helper.load('cfg'); 16 | helper.load.cfg('taskcluster.rootUrl', libUrls.testRootUrl()); 17 | 18 | github = await helper.load('github'); 19 | github.inst(5808).setUser({id: 14795478, email: 'someuser@github.com', username: 'TaskClusterRobot'}); 20 | github.inst(5808).setUser({id: 18102552, email: 'anotheruser@github.com', username: 'owlishDeveloper'}); 21 | 22 | publisher = await helper.load('publisher'); 23 | }); 24 | 25 | /** 26 | * Run a test which verifies that pulse messages are being produced 27 | * for valid webhook requests. 28 | * params: { 29 | * testName: 'some test', 30 | * listenFor: 'some event type', 31 | * exchangeFunc: 'name of exchange function', 32 | * routingKey: {...}, a dict containing a pulse routing key 33 | * details: {...}, a dict of details we expect to seein the msg payload 34 | * jsonFile: 'data file' 35 | * tasks_for: 'the event type; for v1' 36 | * branch: 'the head branch name; for v1' 37 | **/ 38 | function pulseTest(params) { 39 | test(params.testName, async function() { 40 | let published = []; 41 | let fakePublish = event => { 42 | published.push(event); 43 | }; 44 | publisher.on('message', fakePublish); 45 | 46 | // Trigger a pull-request message 47 | try { 48 | let res = await helper.jsonHttpRequest('./test/data/webhooks/' + params.jsonFile); 49 | res.connection.destroy(); 50 | } finally { 51 | publisher.removeListener('message', fakePublish); 52 | } 53 | 54 | let expected = [{ 55 | exchange: `exchange/taskcluster-fake/v1/${params.listenFor}`, 56 | routingKey: params.routingKey, 57 | payload: { 58 | organization: 'TaskClusterRobot', 59 | details: params.details, 60 | installationId: 5808, 61 | repository: 'hooks-testing', 62 | eventId: params.eventId, 63 | version: 1, 64 | body: require('./data/webhooks/' + params.jsonFile).body, 65 | tasks_for: params.tasks_for, 66 | branch: params.branch, 67 | }, 68 | CCs: [], 69 | }]; 70 | if (params.action) { 71 | expected[0].payload.action = params.action; 72 | } 73 | assert.deepEqual(published, expected); 74 | }); 75 | } 76 | 77 | pulseTest({ 78 | testName: 'Publish Pull Request', 79 | listenFor: 'pull-request', 80 | action: 'opened', 81 | exchangeFunc: 'pullRequest', 82 | routingKey: 'primary.TaskClusterRobot.hooks-testing.opened', 83 | eventId: '81254e00-d9c1-11e6-8964-748a671f0cee', 84 | details: { 85 | 'event.base.ref': 'refs/heads/master', 86 | 'event.base.repo.branch': 'master', 87 | 'event.base.repo.name': 'hooks-testing', 88 | 'event.base.repo.url': 'https://github.com/TaskClusterRobot/hooks-testing.git', 89 | 'event.base.sha': '55e752e3a914db81eee3f90260f7eb69b7169ada', 90 | 'event.base.user.login': 'TaskClusterRobot', 91 | 'event.head.ref': 'refs/heads/owlishDeveloper-patch-2', 92 | 'event.head.repo.branch': 'owlishDeveloper-patch-2', 93 | 'event.head.repo.name': 'hooks-testing', 94 | 'event.head.repo.url': 'https://github.com/TaskClusterRobot/hooks-testing.git', 95 | 'event.head.sha': 'b12ead3c5f3499e34356c970e20d7858f1747542', 96 | 'event.head.user.login': 'owlishDeveloper', 97 | 'event.head.user.id': 18102552, 98 | 'event.pullNumber': 36, 99 | 'event.type': 'pull_request.opened', 100 | 'event.head.user.email': 'anotheruser@github.com', 101 | 'event.title': 'Update README.md', 102 | }, 103 | jsonFile: 'webhook.pull_request.open.json', 104 | tasks_for: 'github-pull-request', 105 | branch: 'owlishDeveloper-patch-2', 106 | }); 107 | 108 | pulseTest({ 109 | testName: 'Publish Push', 110 | listenFor: 'push', 111 | exchangeFunc: 'push', 112 | routingKey: 'primary.TaskClusterRobot.hooks-testing', 113 | eventId: '9637a980-d8fb-11e6-9830-1244ca57c95f', 114 | details: { 115 | 'event.base.ref': 'refs/heads/master', 116 | 'event.base.repo.branch': 'master', 117 | 'event.base.repo.name': 'hooks-testing', 118 | 'event.base.repo.url': 'https://github.com/TaskClusterRobot/hooks-testing.git', 119 | 'event.base.sha': '7a257a6d139708a3188bf2e0cd1f15e466a88d0e', 120 | 'event.base.user.login': 'owlishDeveloper', 121 | 'event.head.ref': 'refs/heads/master', 122 | 'event.head.repo.branch': 'master', 123 | 'event.head.repo.name': 'hooks-testing', 124 | 'event.head.repo.url': 'https://github.com/TaskClusterRobot/hooks-testing.git', 125 | 'event.head.sha': 'b79ce60be819cdc482c9c6a84dc3c457959aa66f', 126 | 'event.head.user.login': 'owlishDeveloper', 127 | 'event.head.user.id': 18102552, 128 | 'event.type': 'push', 129 | 'event.head.user.email': 'anotheruser@github.com', 130 | }, 131 | jsonFile: 'webhook.push.json', 132 | tasks_for: 'github-push', 133 | branch: 'master', 134 | }); 135 | 136 | pulseTest({ 137 | testName: 'Publish Release', 138 | listenFor: 'release', 139 | exchangeFunc: 'release', 140 | routingKey: 'primary.TaskClusterRobot.hooks-testing', 141 | eventId: '2c81a200-cd36-11e6-9106-ad0d7be0e22e', 142 | details: { 143 | 'event.type': 'release', 144 | 'event.base.repo.branch': 'master', 145 | 'event.head.user.login': 'TaskClusterRobot', 146 | 'event.head.user.id': 14795478, 147 | 'event.version': 'testing-789', 148 | 'event.name': 'Testing 123', 149 | 'event.head.repo.name': 'hooks-testing', 150 | 'event.head.repo.url': 'https://github.com/TaskClusterRobot/hooks-testing.git', 151 | 'event.release.url': 'https://api.github.com/repos/TaskClusterRobot/hooks-testing/releases/5027516', 152 | 'event.prerelease': false, 153 | 'event.draft': false, 154 | 'event.tar': 'https://api.github.com/repos/TaskClusterRobot/hooks-testing/tarball/testing-789', 155 | 'event.zip': 'https://api.github.com/repos/TaskClusterRobot/hooks-testing/zipball/testing-789', 156 | 'event.head.user.email': 'someuser@github.com', 157 | }, 158 | jsonFile: 'webhook.release.json', 159 | tasks_for: 'github-release', 160 | branch: 'master', 161 | }); 162 | 163 | pulseTest({ 164 | testName: 'Publish Tag Push', 165 | listenFor: 'push', 166 | exchangeFunc: 'push', 167 | routingKey: 'primary.TaskClusterRobot.hooks-testing', 168 | eventId: '9637a980-d8fb-11e6-9830-1244ca57c95f', 169 | details: { 170 | 'event.base.ref': 'refs/tags/v1.0.2', 171 | 'event.base.repo.name': 'hooks-testing', 172 | 'event.base.repo.url': 'https://github.com/TaskClusterRobot/hooks-testing.git', 173 | 'event.base.sha': '0000000000000000000000000000000000000000', 174 | 'event.base.user.login': 'owlishDeveloper', 175 | 'event.head.ref': 'refs/tags/v1.0.2', 176 | 'event.head.tag': 'v1.0.2', 177 | 'event.head.repo.name': 'hooks-testing', 178 | 'event.head.repo.url': 'https://github.com/TaskClusterRobot/hooks-testing.git', 179 | 'event.head.sha': 'b79ce60be819cdc482c9c6a84dc3c457959aa66f', 180 | 'event.head.user.login': 'owlishDeveloper', 181 | 'event.head.user.id': 18102552, 182 | 'event.type': 'tag', 183 | 'event.head.user.email': 'anotheruser@github.com', 184 | }, 185 | jsonFile: 'webhook.tag_push.json', 186 | tasks_for: 'github-push', 187 | branch: 'v1.0.2', 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /test/references_test.js: -------------------------------------------------------------------------------- 1 | const builder = require('../src/api'); 2 | const exchanges = require('../src/exchanges'); 3 | const helper = require('./helper'); 4 | const References = require('taskcluster-lib-references'); 5 | 6 | suite('references_test.js', function() { 7 | test('references validate', async function() { 8 | const schemaset = await helper.load('schemaset'); 9 | const references = References.fromService({schemaset, builder, exchanges}); 10 | references.validate(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/sync_test.js: -------------------------------------------------------------------------------- 1 | const helper = require('./helper'); 2 | const assert = require('assert'); 3 | 4 | /** 5 | * Tests of installation syncing 6 | */ 7 | helper.secrets.mockSuite('syncInstallations', ['taskcluster'], function(mock, skipping) { 8 | helper.withEntities(mock, skipping); 9 | helper.withFakeGithub(mock, skipping); 10 | helper.withServer(mock, skipping); 11 | 12 | let github; 13 | 14 | suiteSetup(async function() { 15 | await helper.OwnersDirectory.scan({}, { 16 | handler: owner => owner.remove(), 17 | }); 18 | 19 | github = await helper.load('github'); 20 | }); 21 | 22 | test('integration installation', async function() { 23 | let result = await helper.apiClient.repository('abc123', 'coolRepo'); 24 | assert.deepEqual(result, {installed: false}); 25 | github.createInstall(12345, 'abc123', ['coolRepo']); 26 | github.createInstall(12346, 'abc124', ['coolerRepo']); 27 | github.createInstall(12347, 'abc125', ['coolestRepo']); 28 | 29 | await helper.load('syncInstallations'); 30 | 31 | result = await helper.apiClient.repository('abc123', 'coolRepo'); 32 | result = await helper.apiClient.repository('abc124', 'coolerRepo'); 33 | result = await helper.apiClient.repository('abc125', 'coolestRepo'); 34 | assert.deepEqual(result, {installed: true}); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/tc-yaml_test.js: -------------------------------------------------------------------------------- 1 | const TcYaml = require('../src/tc-yaml'); 2 | const assume = require('assume'); 3 | 4 | suite('tc-yaml_test.js', function() { 5 | suite('VersionOne', function() { 6 | const tcyaml = TcYaml.instantiate(1); 7 | const cfg = { 8 | taskcluster: { 9 | schedulerId: 'test-sched', 10 | }, 11 | app: { 12 | checkTaskRoute: 'checks-queue', 13 | statusTaskRoute: 'statuses-queue', 14 | }, 15 | }; 16 | const now = new Date().toJSON(); 17 | 18 | test('compileTasks with no tasks', function() { 19 | const config = { 20 | tasks: [], 21 | }; 22 | tcyaml.compileTasks(config, cfg, {}, now); 23 | assume(config.tasks).to.deeply.equal([]); 24 | }); 25 | 26 | test('compileTasks with one task sets default properties', function() { 27 | const config = { 28 | tasks: [{}], 29 | }; 30 | 31 | tcyaml.compileTasks(config, cfg, {}, now); 32 | assume(config.tasks).to.deeply.equal([{ 33 | taskId: config.tasks[0].taskId, 34 | task: { 35 | created: now, 36 | taskGroupId: config.tasks[0].taskId, // matches taskId 37 | schedulerId: 'test-sched', 38 | routes: ['statuses-queue'], 39 | }, 40 | }]); 41 | }); 42 | 43 | test('compileTasks with one taskId sets taskGroupId', function() { 44 | const config = { 45 | tasks: [{ 46 | taskId: 'task-1', 47 | }], 48 | }; 49 | tcyaml.compileTasks(config, cfg, {}, now); 50 | assume(config.tasks).to.deeply.equal([{ 51 | taskId: 'task-1', 52 | task: { 53 | created: now, 54 | taskGroupId: 'task-1', 55 | schedulerId: 'test-sched', 56 | routes: ['statuses-queue'], 57 | }, 58 | }]); 59 | }); 60 | 61 | test('compileTasks with taskGroupId and one task sets taskId', function() { 62 | const config = { 63 | tasks: [{ 64 | taskGroupId: 'tgid-1', 65 | }], 66 | }; 67 | tcyaml.compileTasks(config, cfg, {}, now); 68 | assume(config.tasks).to.deeply.equal([{ 69 | taskId: 'tgid-1', 70 | task: { 71 | created: now, 72 | taskGroupId: 'tgid-1', 73 | schedulerId: 'test-sched', 74 | routes: ['statuses-queue'], 75 | }, 76 | }]); 77 | }); 78 | 79 | test('compileTasks with two tasks sets default properties', function() { 80 | const config = { 81 | tasks: [{}, {}], 82 | }; 83 | tcyaml.compileTasks(config, cfg, {}, now); 84 | // taskGroupIds match 85 | assume(config.tasks[0].task.taskGroupId).to.equal(config.tasks[1].task.taskGroupId); 86 | // taskIds don't 87 | assume(config.tasks[0].taskId).to.not.equal(config.tasks[1].taskId); 88 | // taskGroupId does not match any taskId 89 | assume(config.tasks[0].taskId).to.not.equal(config.tasks[0].task.taskGroupId); 90 | assume(config.tasks[1].taskId).to.not.equal(config.tasks[1].task.taskGroupId); 91 | }); 92 | 93 | test('compileTasks forces schedulerId, but uses user-supplied taskId/taskGroupId', function() { 94 | const config = { 95 | tasks: [{ 96 | taskId: 'task-1', 97 | taskGroupId: 'tgid-1', 98 | schedulerId: 'my-scheduler-id', 99 | }, { 100 | taskId: 'task-2', 101 | taskGroupId: 'tgid-2', 102 | schedulerId: 'my-scheduler-id', 103 | }], 104 | }; 105 | tcyaml.compileTasks(config, cfg, {}, now); 106 | assume(config.tasks).to.deeply.equal([{ 107 | taskId: 'task-1', 108 | task: { 109 | created: now, 110 | taskGroupId: 'tgid-1', 111 | schedulerId: 'test-sched', 112 | routes: ['statuses-queue'], 113 | }, 114 | }, { 115 | taskId: 'task-2', 116 | task: { 117 | created: now, 118 | taskGroupId: 'tgid-2', 119 | schedulerId: 'test-sched', 120 | routes: ['statuses-queue'], 121 | }, 122 | }]); 123 | }); 124 | 125 | test('compileTasks sets checks route if we have reporting in the YML', function() { 126 | const config = { 127 | tasks: [{ 128 | taskId: 'task-1', 129 | }], 130 | reporting: 'checks-v1', 131 | }; 132 | tcyaml.compileTasks(config, cfg, {}, now); 133 | assume(config.tasks).to.deeply.equal([{ 134 | taskId: 'task-1', 135 | task: { 136 | created: now, 137 | taskGroupId: 'task-1', 138 | schedulerId: 'test-sched', 139 | routes: ['checks-queue'], 140 | }, 141 | }]); 142 | }); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /test/valid-yaml.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 0, 3 | "metadata": { 4 | "name": "TaskCluster GitHub Tests", 5 | "description": "Tests for taskcluster github in production", 6 | "owner": "{{ event.head.user.email }}", 7 | "source": "{{ event.head.repo.url }}" 8 | }, 9 | "tasks": [ 10 | { 11 | "provisionerId": "dummy-provisioner", 12 | "workerType": "dummy-worker", 13 | "extra": { 14 | "github": { 15 | "env": true, 16 | "events": [ 17 | "pull_request.opened", 18 | "pull_request.synchronize", 19 | "pull_request.reopened", 20 | "push", 21 | "tag" 22 | ] 23 | } 24 | }, 25 | "payload": { 26 | "maxRunTime": 3600, 27 | "image": "node:5", 28 | "command": [ 29 | "/bin/bash", 30 | "-lc", 31 | "echo 'This works!'" 32 | ] 33 | }, 34 | "metadata": { 35 | "name": "TaskCluster GitHub Tests", 36 | "description": "Tests for taskcluster github in production", 37 | "owner": "{{ event.head.user.email }}", 38 | "source": "{{ event.head.repo.url }}" 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /test/webhook_test.js: -------------------------------------------------------------------------------- 1 | const helper = require('./helper'); 2 | const assert = require('assert'); 3 | 4 | helper.secrets.mockSuite('webhook', ['taskcluster'], function(mock, skipping) { 5 | helper.withEntities(mock, skipping); 6 | helper.withFakeGithub(mock, skipping); 7 | helper.withServer(mock, skipping); 8 | 9 | let github = null; 10 | 11 | setup(async function() { 12 | github = await helper.load('github'); 13 | github.inst(5808).setUser({id: 14795478, email: 'someuser@github.com', username: 'TaskClusterRobot'}); 14 | github.inst(5808).setUser({id: 18102552, email: 'anotheruser@github.com', username: 'owlishDeveloper'}); 15 | }); 16 | 17 | // Check the status code returned from a request containing some test data 18 | function statusTest(testName, jsonFile, statusCode) { 19 | test(testName, async function() { 20 | let response = await helper.jsonHttpRequest('./test/data/webhooks/' + jsonFile); 21 | assert.equal(response.statusCode, statusCode); 22 | response.connection.destroy(); 23 | }); 24 | } 25 | 26 | // Good data: should all return 200 responses 27 | statusTest('Pull Request Opened', 'webhook.pull_request.open.json', 204); 28 | statusTest('Pull Request Closed', 'webhook.pull_request.close.json', 204); 29 | statusTest('Push', 'webhook.push.json', 204); 30 | statusTest('Release', 'webhook.release.json', 204); 31 | statusTest('Tag', 'webhook.tag_push.json', 204); 32 | 33 | // Bad data: should all return 400 responses 34 | statusTest('Push without secret', 'webhook.push.no_secret.json', 400); 35 | statusTest('Unknown Event', 'webhook.unknown_event.json', 400); 36 | statusTest('Push with bad secret', 'webhook.push.bad_secret.json', 403); 37 | statusTest('Release with bad secret', 'webhook.release.bad_secret.json', 403); 38 | }); 39 | -------------------------------------------------------------------------------- /user-config-example.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Running the tests does not require any credentials (you need not 3 | # even create user-config.yml). 4 | --------------------------------------------------------------------------------