├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bin ├── check ├── common.sh ├── in └── out ├── ci └── pipelines │ └── main.yml └── test ├── all.sh ├── check └── simple.sh ├── in ├── missing-status.sh └── simple.sh └── out ├── default-target-url.sh ├── simple.sh ├── target-url-path.sh ├── validate-state.sh └── wait-for-status.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /ci/config/* 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | RUN apk --no-cache add curl ca-certificates gettext \ 3 | && curl -Ls https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 > /usr/bin/jq && chmod +x /usr/bin/jq 4 | ADD bin /opt/resource 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Danny Berger 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github-status-resource 2 | 3 | A [Concourse](http://concourse.ci/) [resource](http://concourse.ci/resources.html) to interact with the [GitHub Status](https://developer.github.com/v3/repos/statuses/) type. 4 | 5 | 6 | ## Configuration 7 | 8 | * **`repository`** - the owner and repository name, slash delimited (e.g. `dpb587/github-status-resource`) 9 | * **`access_token`** - GitHub API access token from a user with write access to the repository (minimum token scope of `repo:status`) 10 | * `branch` - the branch currently being monitored (default: `master`) 11 | * `context` - a label to differentiate this status from the status of other systems (default: `default`) 12 | * `endpoint` - GitHub API endpoint (default: `https://api.github.com`) 13 | * `skip_ssl_verification` - Disable certificate validation for GitHub API calls (default: `false`) 14 | 15 | 16 | ## Behavior 17 | 18 | 19 | ### `check` 20 | 21 | Triggers when the status of the branch for the configured context has been updated. 22 | 23 | 24 | ### `in` 25 | 26 | Lookup the state of a status. 27 | 28 | * `/commit` - the commit reference of the status 29 | * `/description` - a short description of the status 30 | * `/state` - the state of the status 31 | * `/target_url` - the target URL associated with the status 32 | * `/updated_at` - when the status was last updated 33 | 34 | 35 | ### `out` 36 | 37 | Update the status of a commit. Optionally include a description and target URL which will be referenced from GitHub. 38 | 39 | Parameters: 40 | 41 | * **`commit`** - specific commit sha affiliated with the status. Value must be either: path to an input git directory whose detached `HEAD` will be used; or path to an input file whose contents is the sha 42 | * **`state`** - the state of the status. Must be one of `pending`, `success`, `error`, or `failure` 43 | * `description` - a short description of the status 44 | * `description_path` - path to an input file whose data is the value of `description` 45 | * `target_url` - the target URL to associate with the status (default: concourse build link) 46 | 47 | 48 | ## Example 49 | 50 | A typical use case is to update the status of a commit as it traverses your pipeline. The following example marks the commit as pending before unit tests start. Once unit tests finish, the status is updated to either success or failure depending on how the task completes. 51 | 52 | --- 53 | jobs: 54 | - name: "unit-tests" 55 | plan: 56 | - get: "repo" 57 | trigger: true 58 | - put: "repo-status" # + 59 | params: { state: "pending", commit: "repo" } # + 60 | - task: "unit-tests" 61 | file: "repo/ci/unit-tests/task.yml" 62 | on_failure: 63 | - put: "repo-status" # + 64 | params: { state: "failure", commit: "repo" } # + 65 | - put: "repo-status" # + 66 | params: { state: "success", commit: "repo" } # + 67 | resources: 68 | - name: "repo" 69 | type: "git" 70 | source: 71 | uri: {{repo_uri}} 72 | branch: {{repo_branch}} 73 | - name: "repo-status" # + 74 | type: "github-status" # + 75 | source: # + 76 | repository: {{repo_github_path}} # + 77 | access_token: {{repo_github_token}} # + 78 | 79 | When testing pull requests, use the PR ref as the `branch`. For example, if testing PR #12345 to your repository, your resource might look like... 80 | 81 | name: "pr-status" 82 | type: "github-status" 83 | source: 84 | repository: {{repo_github_path}} 85 | access_token: {{repo_github_token}} 86 | branch: "pull/12345/head" # + 87 | 88 | For another pipeline example, see [`ci/pipelines/main.yml`](ci/pipelines/main.yml) which operates against this repository. 89 | 90 | 91 | ## Installation 92 | 93 | This resource is not included with the standard Concourse release. Use one of the following methods to make this resource available to your pipelines. 94 | 95 | 96 | ### Deployment-wide 97 | 98 | To install on all Concourse workers, update your deployment manifest properties to include a new `groundcrew.resource_types` entry... 99 | 100 | properties: 101 | groundcrew: 102 | additional_resource_types: 103 | - image: "docker:///dpb587/github-status-resource#master" # + 104 | type: "github-status" # + 105 | 106 | 107 | ### Pipeline-specific 108 | 109 | To use on a single pipeline, update your pipeline to include a new `resource_types` entry... 110 | 111 | resource_types: 112 | - name: "github-status" # + 113 | type: "docker-image" # + 114 | source: # + 115 | repository: "dpb587/github-status-resource" # + 116 | tag: "master" # + 117 | 118 | 119 | ## References 120 | 121 | * [Resources (concourse.ci)](https://concourse.ci/resources.html) 122 | * [Statuses | GitHub Developer Guide (developer.github.com)](https://developer.github.com/v3/repos/statuses/) 123 | * [Enabling required status checks (help.github.com)](https://help.github.com/articles/enabling-required-status-checks/) 124 | * [Personal Access Tokens (github.com)](https://github.com/settings/tokens) 125 | 126 | 127 | ## License 128 | 129 | [MIT License](./LICENSE) 130 | -------------------------------------------------------------------------------- /bin/check: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . $( dirname "$0" )/common.sh 4 | 5 | repipe 6 | load_source 7 | 8 | curlgh "$source_endpoint/repos/$source_repository/commits/$source_branch/status" \ 9 | | jq -c \ 10 | --arg context "$source_context" \ 11 | '.sha as $commit | .statuses | map(select( $context == .context )) | map({ "commit": $commit, "status": ( .id | tostring ) })' \ 12 | >&3 13 | -------------------------------------------------------------------------------- /bin/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | [ ! -e /tmp/build/* ] || cd /tmp/build/* 6 | 7 | REM () { 8 | /bin/echo $( date -u +"%Y-%m-%dT%H:%M:%SZ" ) "$@" 9 | } 10 | 11 | fatal () { 12 | echo "FATAL: $1" >&2 13 | exit 1 14 | } 15 | 16 | repipe () { 17 | exec 3>&1 18 | exec 1>&2 19 | cat > /tmp/stdin 20 | } 21 | 22 | load_source () { 23 | eval $( jq -r '{ 24 | "source_repository": .source.repository, 25 | "source_access_token": .source.access_token, 26 | "source_branch": ( .source.branch // "master" ), 27 | "source_context": ( .source.context // "default" ), 28 | "source_endpoint": ( .source.endpoint // "https://api.github.com" ), 29 | "skip_ssl_verification": ( .source.skip_ssl_verification // "false" ) 30 | } | to_entries[] | .key + "=" + @sh "\(.value)" 31 | ' < /tmp/stdin ) 32 | 33 | source_endpoint=$( echo "$source_endpoint" | sed 's#/$##' ) 34 | } 35 | 36 | buildtpl () { 37 | envsubst=$( which envsubst ) 38 | env -i \ 39 | BUILD_ID="${BUILD_ID:-}" \ 40 | BUILD_NAME="${BUILD_NAME:-}" \ 41 | BUILD_JOB_NAME="${BUILD_JOB_NAME:-}" \ 42 | BUILD_PIPELINE_NAME="${BUILD_PIPELINE_NAME:-}" \ 43 | ATC_EXTERNAL_URL="${ATC_EXTERNAL_URL:-}" \ 44 | $envsubst 45 | } 46 | 47 | curlgh () { 48 | if $skip_ssl_verification; then 49 | skip_verify_arg="-k" 50 | else 51 | skip_verify_arg="" 52 | fi 53 | curl $skip_verify_arg -s -H "Authorization: token $source_access_token" $@ 54 | } 55 | -------------------------------------------------------------------------------- /bin/in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . $( dirname "$0" )/common.sh 4 | 5 | repipe 6 | in_dir="$1" 7 | load_source 8 | 9 | eval $( jq -r '{ 10 | "version_commit": .version.commit, 11 | "version_status": .version.status 12 | } | to_entries[] | .key + "=" + @sh "\(.value)"' < /tmp/stdin ) 13 | 14 | 15 | # 16 | # lookup 17 | # 18 | 19 | curlgh "$source_endpoint/repos/$source_repository/commits/$version_commit/status" \ 20 | | jq -c \ 21 | --arg status "$version_status" \ 22 | '{ 23 | "sha": .sha, 24 | "status": ( .statuses | map(select( $status == ( .id | tostring ) )) | .[0] ) 25 | }' \ 26 | > /tmp/status 27 | 28 | 29 | # 30 | # validate 31 | # 32 | 33 | jq -e '.status' < /tmp/status > /dev/null \ 34 | || fatal "Status not found on $( jq -r '.sha' < /tmp/status )" 35 | 36 | 37 | # 38 | # concourse 39 | # 40 | 41 | jq -j -r '.sha' < /tmp/status > "$in_dir/commit" 42 | jq -j -r '.status.description // ""' < /tmp/status > "$in_dir/description" 43 | jq -j -r '.status.state' < /tmp/status > "$in_dir/state" 44 | jq -j -r '.status.target_url // ""' < /tmp/status > "$in_dir/target_url" 45 | jq -j -r '.status.updated_at' < /tmp/status > "$in_dir/updated_at" 46 | 47 | jq -c \ 48 | --arg commit "$version_commit" \ 49 | --arg status "$version_status" \ 50 | '{ 51 | "version": { 52 | "commit": ( $commit | tostring ), 53 | "status": ( $status | tostring ) 54 | }, 55 | "metadata": [ 56 | { 57 | "name": "created_at", 58 | "value": .status.created_at 59 | } 60 | ] 61 | }' \ 62 | < /tmp/status \ 63 | >&3 64 | -------------------------------------------------------------------------------- /bin/out: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . $( dirname "$0" )/common.sh 4 | 5 | repipe 6 | load_source 7 | 8 | eval $( jq -r '{ 9 | "params_commit": .params.commit, 10 | "params_state": .params.state, 11 | "params_description": ( .params.description // "" ), 12 | "params_description_path": ( .params.description_path // "" ), 13 | "params_target_url": ( .params.target_url // ""), 14 | "params_target_url_path": ( .params.target_url_path // "" ) 15 | } | to_entries[] | .key + "=" + @sh "\(.value)"' < /tmp/stdin ) 16 | 17 | 18 | # 19 | # validate 20 | # 21 | 22 | case "$params_state" in 23 | error) true ;; 24 | failure) true ;; 25 | pending) true ;; 26 | success) true ;; 27 | *) fatal "Invalid parameter: state: $params_state" 28 | esac 29 | 30 | 31 | # 32 | # commit 33 | # 34 | 35 | if [ -d "$params_commit" ] ; then 36 | commit=$( cat "$params_commit/.git/HEAD" ) 37 | elif [ -f "$params_commit" ] ; then 38 | commit=$( echo $( cat "$params_commit" ) ) 39 | else 40 | fatal "Invalid parameter: commit: $params_commit" 41 | fi 42 | 43 | 44 | # 45 | # description 46 | # 47 | 48 | description_path="/tmp/description" 49 | 50 | if [[ -n "$params_description" ]] ; then 51 | echo "$params_description" > "$description_path" 52 | elif [[ -n "$params_description_path" ]] ; then 53 | cp "$params_description_path" "$description_path" 54 | else 55 | description_path="/dev/null" 56 | fi 57 | 58 | 59 | # 60 | # target_url 61 | # 62 | 63 | target_url='$ATC_EXTERNAL_URL/builds/$BUILD_ID' 64 | 65 | if [[ -n "$params_target_url" ]] ; then 66 | target_url="$params_target_url" 67 | elif [[ -n "$params_target_url_path" ]] ; then 68 | target_url="$( cat "$params_target_url_path" )" 69 | fi 70 | 71 | 72 | # 73 | # execute 74 | # 75 | 76 | jq -c -n \ 77 | --arg state "$params_state" \ 78 | --arg target_url "$( echo "$target_url" | buildtpl )" \ 79 | --arg description "$( cat $description_path )" \ 80 | --arg context "$source_context" \ 81 | '{ 82 | "context": $context, 83 | "description": $description, 84 | "state": $state, 85 | "target_url": $target_url 86 | } | to_entries | map( select( 0 < ( .value | length ) ) ) | from_entries' \ 87 | | curlgh -d@- "$source_endpoint/repos/$source_repository/statuses/$commit" \ 88 | > /tmp/gh-result 89 | 90 | # 91 | # check retry counter 92 | # 93 | 94 | REMAINING_TRIES=5 95 | 96 | while [[ $REMAINING_TRIES -gt 0 ]]; do 97 | 98 | # 99 | # lookup 100 | # 101 | 102 | curlgh "$source_endpoint/repos/$source_repository/commits/$source_branch/status" \ 103 | | jq -c \ 104 | --arg ref "$(jq -r '.id | tostring' < /tmp/gh-result)" \ 105 | '{ 106 | "sha": .sha, 107 | "status": ( .statuses | map(select( $ref == ( .id | tostring ) )) | .[0] ) 108 | }' \ 109 | > /tmp/status 110 | 111 | # 112 | # validate 113 | # 114 | 115 | [[ -s /tmp/status ]] \ 116 | && jq -e '.status' < /tmp/status > /dev/null \ 117 | && break 118 | 119 | # 120 | # decrease retry counter and loop 121 | # 122 | 123 | REMAINING_TRIES=$(($REMAINING_TRIES - 1)) 124 | 125 | sleep 1 126 | 127 | done 128 | 129 | # 130 | # concourse 131 | # 132 | 133 | jq -c \ 134 | --arg commit "$commit" \ 135 | '{ 136 | "version": { 137 | "commit": $commit, 138 | "status": ( .id | tostring ) 139 | }, 140 | "metadata": [ 141 | { 142 | "name": "created_at", 143 | "value": .created_at 144 | }, 145 | { 146 | "name": "created_by", 147 | "value": .creator.login 148 | } 149 | ] 150 | }' \ 151 | < /tmp/gh-result \ 152 | >&3 153 | -------------------------------------------------------------------------------- /ci/pipelines/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | jobs: 3 | - name: "build-develop" 4 | serial: true 5 | plan: 6 | - get: "repo-develop" 7 | trigger: true 8 | - put: "resource-image-develop" 9 | params: 10 | build: "repo-develop" 11 | get_params: 12 | skip_download: true 13 | - name: "test-develop" 14 | plan: 15 | - aggregate: 16 | - get: "repo-develop" 17 | trigger: true 18 | passed: 19 | - "build-develop" 20 | - get: "resource-image-develop" 21 | passed: 22 | - "build-develop" 23 | - put: "repo-status-develop" 24 | params: 25 | commit: "repo-develop" 26 | state: "pending" 27 | - task: "validate-image" 28 | config: 29 | platform: "linux" 30 | image_resource: 31 | type: "docker-image" 32 | source: 33 | repository: {{images_repo}} 34 | tag: {{repo_branch_develop}} 35 | insecure_registries: [{{images_insecure_registries}}] 36 | run: 37 | path: "repo-develop/test/all.sh" 38 | inputs: 39 | - name: "repo-develop" 40 | on_failure: 41 | put: "repo-status-develop" 42 | params: 43 | commit: "repo-develop" 44 | state: "failure" 45 | - put: "repo-status-develop" 46 | params: 47 | commit: "repo-develop" 48 | state: "success" 49 | - name: "promote-master" 50 | serial: true 51 | plan: 52 | - aggregate: 53 | - get: "repo-develop" 54 | trigger: true 55 | passed: 56 | - "test-develop" 57 | - get: "resource-image-develop" 58 | passed: 59 | - "test-develop" 60 | params: 61 | rootfs: true 62 | - aggregate: 63 | - put: "repo" 64 | params: 65 | repository: "repo-develop" 66 | - put: "resource-image" 67 | params: 68 | import_file: "resource-image-develop/rootfs.tar" 69 | get_params: 70 | skip_download: true 71 | 72 | resources: 73 | - name: "repo-develop" 74 | type: "git" 75 | source: 76 | uri: {{repo_uri}} 77 | branch: {{repo_branch_develop}} 78 | private_key: {{repo_key}} 79 | - name: "resource-image-develop" 80 | type: "docker-image" 81 | source: 82 | repository: {{images_repo}} 83 | tag: {{repo_branch_develop}} 84 | email: {{images_email}} 85 | username: {{images_username}} 86 | password: {{images_password}} 87 | insecure_registries: [{{images_insecure_registries}}] 88 | - name: "repo-status-develop" 89 | type: "github-status" 90 | source: 91 | repository: {{repo_path}} 92 | access_token: {{github_api_token}} 93 | branch: {{repo_branch_develop}} 94 | context: "ci/main" 95 | 96 | - name: "repo" 97 | type: "git" 98 | source: 99 | uri: {{repo_uri}} 100 | branch: {{repo_branch}} 101 | private_key: {{repo_key}} 102 | - name: "resource-image" 103 | type: "docker-image" 104 | source: 105 | repository: {{images_repo}} 106 | tag: {{repo_branch}} 107 | email: {{images_email}} 108 | username: {{images_username}} 109 | password: {{images_password}} 110 | insecure_registries: [{{images_insecure_registries}}] 111 | 112 | resource_types: 113 | - name: "github-status" 114 | type: "docker-image" 115 | source: 116 | repository: {{images_repo}} 117 | tag: {{repo_branch_develop}} 118 | insecure_registries: [{{images_insecure_registries}}] 119 | -------------------------------------------------------------------------------- /test/all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | failed=false 4 | 5 | dir=$( dirname "$0" )/.. 6 | cd $dir 7 | 8 | export TMPDIR="${TMPDIR:-/tmp}" 9 | 10 | for test in $( find test -type f -perm +111 -print | grep -v 'test/all.sh' ) ; do 11 | echo "==> $test" 12 | $test 13 | result=$? 14 | 15 | echo 16 | 17 | [[ "0" == "$result" ]] || failed=true 18 | done 19 | 20 | [[ "false" == "$failed" ]] || exit 1 21 | -------------------------------------------------------------------------------- /test/check/simple.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | DIR=$( dirname "$0" )/../.. 6 | 7 | cat < $TMPDIR/http.req-$$ & 8 | HTTP/1.0 200 OK 9 | 10 | { 11 | "state": "success", 12 | "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", 13 | "total_count": 2, 14 | "statuses": [ 15 | { 16 | "created_at": "2012-07-20T01:19:13Z", 17 | "updated_at": "2012-07-20T01:19:13Z", 18 | "state": "success", 19 | "target_url": "https://ci.example.com/1000/output", 20 | "description": "Build has completed successfully", 21 | "id": 1, 22 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/1", 23 | "context": "continuous-integration/jenkins" 24 | }, 25 | { 26 | "created_at": "2012-08-20T01:19:13Z", 27 | "updated_at": "2012-08-20T02:19:13Z", 28 | "state": "success", 29 | "target_url": "https://ci.example.com/2000/output", 30 | "description": "Testing has completed successfully", 31 | "id": 2, 32 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/2", 33 | "context": "test-context" 34 | } 35 | ] 36 | } 37 | EOF 38 | 39 | $DIR/bin/check > $TMPDIR/resource-$$ < $TMPDIR/http.req-$$ & 8 | HTTP/1.0 200 OK 9 | 10 | { 11 | "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", 12 | "statuses": [] 13 | } 14 | EOF 15 | 16 | in_dir=$TMPDIR/status-$$ 17 | 18 | mkdir $in_dir 19 | 20 | set +e 21 | 22 | $DIR/bin/in "$in_dir" > $TMPDIR/resource-$$ 2>&1 < $TMPDIR/http.req-$$ & 8 | HTTP/1.0 200 OK 9 | 10 | { 11 | "state": "success", 12 | "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", 13 | "total_count": 2, 14 | "statuses": [ 15 | { 16 | "created_at": "2012-07-20T01:19:13Z", 17 | "updated_at": "2012-07-20T01:19:13Z", 18 | "state": "success", 19 | "target_url": "https://ci.example.com/1000/output", 20 | "description": "Build has completed successfully", 21 | "id": 1, 22 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/1", 23 | "context": "continuous-integration/jenkins" 24 | }, 25 | { 26 | "created_at": "2012-08-20T01:19:13Z", 27 | "updated_at": "2012-08-20T01:19:13Z", 28 | "state": "success", 29 | "target_url": "https://ci.example.com/2000/output", 30 | "description": "Testing has completed successfully", 31 | "id": 2, 32 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/2", 33 | "context": "security/brakeman" 34 | } 35 | ], 36 | "repository": {}, 37 | "commit_url": "https://api.github.com/repos/octocat/Hello-World/6dcb09b5b57875f334f61aebed695e2e4193db5e", 38 | "url": "https://api.github.com/repos/octocat/Hello-World/6dcb09b5b57875f334f61aebed695e2e4193db5e/status" 39 | } 40 | EOF 41 | 42 | in_dir=$TMPDIR/status-$$ 43 | 44 | mkdir $in_dir 45 | 46 | $DIR/bin/in "$in_dir" > $TMPDIR/resource-$$ < $TMPDIR/commit 8 | 9 | cat < $TMPDIR/http.req-$$ & 10 | HTTP/1.0 200 OK 11 | 12 | { 13 | "created_at": "2012-07-20T01:19:13Z", 14 | "updated_at": "2012-07-20T02:19:13Z", 15 | "state": "success", 16 | "target_url": "https://ci.example.com/1000/output", 17 | "description": "Build has completed successfully", 18 | "id": 1, 19 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/1", 20 | "context": "continuous-integration/jenkins", 21 | "creator": { 22 | "login": "octocat", 23 | "id": 1, 24 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 25 | "gravatar_id": "", 26 | "url": "https://api.github.com/users/octocat", 27 | "html_url": "https://github.com/octocat", 28 | "followers_url": "https://api.github.com/users/octocat/followers", 29 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 30 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 31 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 32 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 33 | "organizations_url": "https://api.github.com/users/octocat/orgs", 34 | "repos_url": "https://api.github.com/users/octocat/repos", 35 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 36 | "received_events_url": "https://api.github.com/users/octocat/received_events", 37 | "type": "User", 38 | "site_admin": false 39 | } 40 | } 41 | EOF 42 | 43 | ATC_EXTERNAL_URL=http://localhost BUILD_ID=123 $DIR/bin/out > $TMPDIR/resource-$$ < $TMPDIR/commit 8 | 9 | cat < $TMPDIR/http.req-$$ & 10 | HTTP/1.0 200 OK 11 | 12 | { 13 | "created_at": "2012-07-20T01:19:13Z", 14 | "updated_at": "2012-07-20T02:19:13Z", 15 | "state": "success", 16 | "target_url": "https://ci.example.com/1000/output", 17 | "description": "Build has completed successfully", 18 | "id": 1, 19 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/1", 20 | "context": "continuous-integration/jenkins", 21 | "creator": { 22 | "login": "octocat", 23 | "id": 1, 24 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 25 | "gravatar_id": "", 26 | "url": "https://api.github.com/users/octocat", 27 | "html_url": "https://github.com/octocat", 28 | "followers_url": "https://api.github.com/users/octocat/followers", 29 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 30 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 31 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 32 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 33 | "organizations_url": "https://api.github.com/users/octocat/orgs", 34 | "repos_url": "https://api.github.com/users/octocat/repos", 35 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 36 | "received_events_url": "https://api.github.com/users/octocat/received_events", 37 | "type": "User", 38 | "site_admin": false 39 | } 40 | } 41 | EOF 42 | 43 | BUILD_ID=123 $DIR/bin/out > $TMPDIR/resource-$$ < $TMPDIR/commit 8 | echo 'https://ci.example.com/$BUILD_ID/output-path' > $TMPDIR/target_url 9 | 10 | cat < $TMPDIR/http.req-$$ & 11 | HTTP/1.0 200 OK 12 | 13 | { 14 | "created_at": "2012-07-20T01:19:13Z", 15 | "updated_at": "2012-07-20T02:19:13Z", 16 | "state": "success", 17 | "target_url": "https://ci.example.com/1000/output", 18 | "description": "Build has completed successfully", 19 | "id": 1, 20 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/1", 21 | "context": "continuous-integration/jenkins", 22 | "creator": { 23 | "login": "octocat", 24 | "id": 1, 25 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 26 | "gravatar_id": "", 27 | "url": "https://api.github.com/users/octocat", 28 | "html_url": "https://github.com/octocat", 29 | "followers_url": "https://api.github.com/users/octocat/followers", 30 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 31 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 32 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 33 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 34 | "organizations_url": "https://api.github.com/users/octocat/orgs", 35 | "repos_url": "https://api.github.com/users/octocat/repos", 36 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 37 | "received_events_url": "https://api.github.com/users/octocat/received_events", 38 | "type": "User", 39 | "site_admin": false 40 | } 41 | } 42 | EOF 43 | 44 | BUILD_ID=123 $DIR/bin/out > $TMPDIR/resource-$$ < $TMPDIR/resource-$$ 2> $TMPDIR/resource-$$.out < $TMPDIR/http.req-$$ 6 | HTTP/1.0 200 OK 7 | 8 | { 9 | "created_at": "2012-07-20T01:19:13Z", 10 | "updated_at": "2012-07-20T02:19:13Z", 11 | "state": "success", 12 | "target_url": "https://ci.example.com/1000/output", 13 | "description": "Build has completed successfully", 14 | "id": 1, 15 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/1", 16 | "context": "continuous-integration/jenkins", 17 | "creator": { 18 | "login": "octocat", 19 | "id": 1, 20 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 21 | "gravatar_id": "", 22 | "url": "https://api.github.com/users/octocat", 23 | "html_url": "https://github.com/octocat", 24 | "followers_url": "https://api.github.com/users/octocat/followers", 25 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 26 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 27 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 28 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 29 | "organizations_url": "https://api.github.com/users/octocat/orgs", 30 | "repos_url": "https://api.github.com/users/octocat/repos", 31 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 32 | "received_events_url": "https://api.github.com/users/octocat/received_events", 33 | "type": "User", 34 | "site_admin": false 35 | } 36 | } 37 | EOF 38 | } 39 | 40 | response_with_status() { 41 | cat < /dev/null 42 | HTTP/1.0 200 OK 43 | 44 | { 45 | "state": "success", 46 | "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", 47 | "total_count": 2, 48 | "statuses": [ 49 | { 50 | "created_at": "2012-07-20T01:19:13Z", 51 | "updated_at": "2012-07-20T01:19:13Z", 52 | "state": "success", 53 | "target_url": "https://ci.example.com/1000/output", 54 | "description": "Build has completed successfully", 55 | "id": 1, 56 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/1", 57 | "context": "continuous-integration/jenkins" 58 | }, 59 | { 60 | "created_at": "2012-08-20T01:19:13Z", 61 | "updated_at": "2012-08-20T01:19:13Z", 62 | "state": "success", 63 | "target_url": "https://ci.example.com/2000/output", 64 | "description": "Testing has completed successfully", 65 | "id": 2, 66 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/2", 67 | "context": "security/brakeman" 68 | } 69 | ], 70 | "repository": {}, 71 | "commit_url": "https://api.github.com/repos/octocat/Hello-World/6dcb09b5b57875f334f61aebed695e2e4193db5e", 72 | "url": "https://api.github.com/repos/octocat/Hello-World/6dcb09b5b57875f334f61aebed695e2e4193db5e/status" 73 | } 74 | EOF 75 | } 76 | 77 | response_without_status() { 78 | cat < /dev/null 79 | HTTP/1.0 200 OK 80 | 81 | { 82 | "state": "success", 83 | "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", 84 | "total_count": 1, 85 | "statuses": [ 86 | { 87 | "created_at": "2012-08-20T01:19:13Z", 88 | "updated_at": "2012-08-20T01:19:13Z", 89 | "state": "success", 90 | "target_url": "https://ci.example.com/2000/output", 91 | "description": "Testing has completed successfully", 92 | "id": 2, 93 | "url": "https://api.github.com/repos/octocat/Hello-World/statuses/2", 94 | "context": "security/brakeman" 95 | } 96 | ], 97 | "repository": {}, 98 | "commit_url": "https://api.github.com/repos/octocat/Hello-World/6dcb09b5b57875f334f61aebed695e2e4193db5e", 99 | "url": "https://api.github.com/repos/octocat/Hello-World/6dcb09b5b57875f334f61aebed695e2e4193db5e/status" 100 | } 101 | EOF 102 | } 103 | 104 | sequential_responses() { 105 | response_after_creation 106 | response_without_status 107 | response_with_status 108 | } 109 | 110 | set -eu 111 | 112 | DIR=$( dirname "$0" )/../.. 113 | 114 | echo 'a1b2c3d4e5' > $TMPDIR/commit 115 | 116 | sequential_responses & 117 | 118 | BUILD_ID=123 $DIR/bin/out > $TMPDIR/resource-$$ <