diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f63ba9e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +sudo: required + +services: + - docker + +script: scripts/test diff --git a/Dockerfile b/Dockerfile index a0e34ce..3cc1300 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,18 @@ -FROM gliderlabs/alpine:edge +FROM alpine:3.7 -RUN apk --update add \ - ca-certificates \ - bash \ - jq \ - curl \ - git \ - openssh-client +RUN apk --no-cache add \ + bash=4.4.19-r1 \ + ca-certificates=20190108-r0 \ + curl=7.61.1-r3 \ + git=2.15.4-r0 \ + jq=1.5-r5 \ + openssh-client=7.5_p1-r10 # can't `git pull` unless we set these RUN git config --global user.email "git@localhost" && \ git config --global user.name "git" -ADD scripts/install_git_lfs.sh install_git_lfs.sh +COPY scripts/install_git_lfs.sh install_git_lfs.sh RUN ./install_git_lfs.sh -ADD assets/ /opt/resource/ -RUN chmod +x /opt/resource/* +COPY assets /opt/resource diff --git a/README.md b/README.md index 592fd6b..c393f49 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -> This resource is no longer actively maintained. +[![Build Status](https://travis-ci.org/mmb/concourse-bitbucket-pullrequest-resource.svg?branch=master)](https://travis-ci.org/mmb/concourse-bitbucket-pullrequest-resource) + +This resource is a fork of +https://github.com/laurentverbruggen/concourse-bitbucket-pullrequest-resource. + +That resource is no longer maintained but this one will continue to be +developed. # Concourse Bitbucket Pull Request Resource @@ -17,13 +23,13 @@ Use this resource by adding the following to the `resource_types` section of a p ```yaml --- resource_types: -- name: concourse-bitbucket-pullrequest - type: docker-image - source: - repository: laurentverbruggen/concourse-bitbucket-pullrequest-resource + - name: concourse-bitbucket-pullrequest + type: docker-image + source: + repository: mm62/concourse-bitbucket-pullrequest-resource ``` -See [concourse docs](http://concourse.ci/configuring-resource-types.html) for more details on adding `resource_types` to a pipeline config. +See [concourse docs](https://concourse-ci.org/resource-types.html) for more details on adding `resource_types` to a pipeline config. ## Source Configuration @@ -60,6 +66,8 @@ See [concourse docs](http://concourse.ci/configuring-resource-types.html) for mo * `only_for_branch`: *Optional.* If specified only pull requests which target those branches will be considered. It will accept a regular expression as determined by [egrep](http://linuxcommand.org/man_pages/egrep1.html). +* `sleep_between_fetches`: *Optional (default: 0).* Number of seconds to sleep between checking pull requests, avoiding too frequent fetches for check method. + * `only_without_conflicts`: *Optional (default: true).* If enabled only pull requests which are not in a conflicted state will be built. * `only_when_mergeable`: *Optional (default: false).* If enabled only pull requests which are mergeable (all tasks done, required number of approvers reached, ...) will be built. @@ -71,6 +79,8 @@ It will accept a regular expression as determined by [egrep](http://linuxcommand * `rebuild_phrase`: *Optional (default: test this please).* Regular expression as determined by [egrep](http://linuxcommand.org/man_pages/egrep1.html) will match all comments in pull request overview. If a match is found the pull request will be rebuilt. +* `create_comments`: *Optional (default: false).* If true write comments with build status to pull requests. + ## Behavior ### `check`: Search for pull requests to build. @@ -82,11 +92,13 @@ Check will return a version for every pull request that matches the criteria def Clones the repository to the destination, and locks it down to a given ref. ** IMPORTANT ** -It is essential that you set the [version](https://concourse.ci/get-step.html#get-version) to `every` on the get step of your job configuration. +It is essential that you set the [version](https://concourse-ci.org/get-step.html#get-step-version) to `every` on the get step of your job configuration. It will allow you to build all versions instead of only the latest. Submodules are initialized and updated recursively. +Note: the name of the branch from which the pull request has been created is stored in the special git config `pullrequest.branch` so that you can use it as reference in your pipeline. + #### Parameters * `depth`: *Optional.* If a positive integer is given, *shallow* clone the repository using the `--depth` option. Using this flag voids your warranty. @@ -110,52 +122,59 @@ Set the status message on specified pull request. * `status`: *Required.* The status of success, failure or pending. - * [`on_success`](https://concourse.ci/on-success-step.html) and [`on_failure`](https://concourse.ci/on-failure-step.html) triggers may be useful for you when you wanted to reflect build result to the pull request (see the example below). + * [`on_success`](https://concourse-ci.org/on-success-step-hook.html) and [`on_failure`](https://concourse-ci.org/on-failure-step-hook.html) triggers may be useful for you when you wanted to reflect build result to the pull request (see the example below). + +* `comment`: *Optional.* A custom comment that you want added to the status message. + Any occurence of `[[BRANCH]]` will be replace by the actual branch name form the + pull request. + +* `commentFile`: *Optional.* The path to a file that contains a custom comment to + add to the message. This allow the comment to be built by a previous task in the job. ## Example pipeline ```yaml resource_types: -- name: concourse-bitbucket-pullrequest - type: docker-image - source: - repository: laurentverbruggen/concourse-bitbucket-pullrequest-resource + - name: concourse-bitbucket-pullrequest + type: docker-image + source: + repository: mm62/concourse-bitbucket-pullrequest-resource resources: -- name: pullrequest - type: concourse-bitbucket-pullrequest - source: - username: {{bitbucket-username}} - password: {{bitbucket-password}} - uri: laurentverbruggen/concourse-bitbucket-pullrequest-resource + - name: pullrequest + type: concourse-bitbucket-pullrequest + source: + username: {{bitbucket-username}} + password: {{bitbucket-password}} + uri: https://your-bitbucket.com/project/repo jobs: -- name: test pull request - plan: - - get: pullrequest - trigger: true - version: every - - put: pullrequest - params: - path: pullrequest - status: pending - - task: test - config: - platform: linux - - inputs: - - name: pullrequest - - ... - - on_success: - put: pullrequest - params: - path: pullrequest - status: success - on_failure: - put: pullrequest - params: - path: pullrequest - status: failure + - name: test pull request + plan: + - get: pullrequest + trigger: true + version: every + - put: pullrequest + params: + path: pullrequest + status: pending + - task: test + config: + platform: linux + + inputs: + - name: pullrequest + + ... + + on_success: + put: pullrequest + params: + path: pullrequest + status: success + on_failure: + put: pullrequest + params: + path: pullrequest + status: failure ``` diff --git a/assets/check b/assets/check index f691df3..3f6788c 100755 --- a/assets/check +++ b/assets/check @@ -37,8 +37,11 @@ only_for_branch=$(jq -r '.source.only_for_branch // "."' < "$payload") only_without_conflicts=$(jq -r '.source.only_without_conflicts // "true"' < "$payload") only_when_mergeable=$(jq -r '.source.only_when_mergeable // "false"' < "$payload") only_when_asked=$(jq -r '.source.only_when_asked // "false"' < "$payload") +sleep_between_fetches=$(jq -r '.source.sleep_between_fetches // "0"' < "$payload") + sleep_between_fetches=$(echo "$sleep_between_fetches" | sed 's/[^0-9\.]//g') rebuild_when_target_changed=$(jq -r '.source.rebuild_when_target_changed // "false"' < "$payload") rebuild_phrase=$(jq -r '.source.rebuild_phrase // "test this please"' < "$payload") +CURRENT_VERSION_DATE=$(jq -r '.version.date // "0"' < "$payload") configure_git_ssl_verification "$skip_ssl_verification" configure_git_global "${git_config_payload}" @@ -55,10 +58,14 @@ if [ "$rebuild_when_target_changed" == "true" ]; then fi # collect all pull requests from uri -pull_requests=$(git ls-remote "$uri" | grep -E "/pull\-requests/[0-9]+/${prq_branch}" | cat) +REMOTES=$(git ls-remote "$uri") +set +e +PULL_REQUESTS=$(echo "$REMOTES" | grep -E "/pull\\-requests/[0-9]+/${prq_branch}") +set -e + versions="[]" -if [ -n "$pull_requests" ]; then +if [ -n "$PULL_REQUESTS" ]; then log "Calculating repository specifics" # determine repository name for calling REST api repo_name=$(basename "$uri" | sed "s/.git$//") @@ -72,6 +79,7 @@ if [ -n "$pull_requests" ]; then versions="[]" while read pull_request ; do + sleep $sleep_between_fetches # throttle requests to avoid excessive load to server log "Verifying pull request" # determine hash and prq number from grep prq_number=$(echo "$pull_request" | sed -E "s/^.*\/pull-requests\/([0-9]+)\/.*$/\\1/") @@ -80,10 +88,12 @@ if [ -n "$pull_requests" ]; then # verify target branch of prq prq=$(bitbucket_pullrequest "$repo_host" "$repo_project" "$repo_name" "$prq_number" "" "$skip_ssl_verification") - if [ "$prq" = "ERROR" ]; then + if [ "$prq" = "NO_SUCH_PULL_REQUEST" ]; then continue fi + PULL_REQUEST_DATE=$(echo "$prq" | jq -r '.updatedDate') + log "Pull request #${prq_number}" prq_to_branch=$(echo "$prq" | jq -r '.toRef.displayId') @@ -92,6 +102,10 @@ if [ -n "$pull_requests" ]; then if [ "$only_when_mergeable" == "true" -o "$only_without_conflicts" == "true" ]; then prq_merge=$(bitbucket_pullrequest_merge "$repo_host" "$repo_project" "$repo_name" "$prq_number" "" "$skip_ssl_verification") + if [ "$prq_merge" = "ALREADY_MERGED" ] || [ "$prq_merge" = "DECLINED" ]; then + continue + fi + # verify if prq has merge conflicts conflicted=$(echo "$prq_merge" | jq -r '.conflicted') if [ "$conflicted" == "true" -a "$only_without_conflicts" == "true" ]; then continue; fi @@ -102,7 +116,6 @@ if [ -n "$pull_requests" ]; then fi # edit timestamp to version to force new build when rebuild_phrase is included in comments - prq_verify_date=$(echo "$prq" | jq -r '.createdDate') skip_build=false comments=$(bitbucket_pullrequest_overview_comments "$repo_host" "$repo_project" "$repo_name" "$prq_number" "" "$skip_ssl_verification" | jq -c '.[]') if [ -n "$comments" ]; then @@ -118,20 +131,29 @@ if [ -n "$pull_requests" ]; then # edit timestamp to force new build when rebuild_phrase is included in comments if echo "$text" | grep -Ec "$rebuild_phrase" > /dev/null; then - prq_verify_date=$(echo "$comment" | jq -r '.createdDate') + PULL_REQUEST_DATE=$(echo "$comment" | jq -r '.createdDate') break fi done <<< "$comments" fi + if [ "$PULL_REQUEST_DATE" -lt "$CURRENT_VERSION_DATE" ]; then + continue + fi + # add prq to versions if [ "$skip_build" == "false" ]; then - pretty_date=$(date_from_epoch_seconds "$(( ($prq_verify_date + 500) / 1000))") - versions+=" + [{ id: \"$prq_number\", hash: \"$prq_hash\", date: \"$pretty_date\", change: $prq_verify_date }]" + versions+=" + [{ id: \"$prq_number\", hash: \"$prq_hash\", date: \"$PULL_REQUEST_DATE\" }]" fi - fi - done <<< "$pull_requests" + done <<< "$PULL_REQUESTS" +fi + +# On the first request return only the current version. +if [ "$CURRENT_VERSION_DATE" -eq "0" ]; then + jq -n "$versions | sort_by((.date | tonumber), (.id | tonumber), .hash) | .[-1:]" > /tmp/check_result +else + jq -n "$versions | sort_by((.date | tonumber), (.id | tonumber), .hash)" > /tmp/check_result fi -jq -n "$versions | sort_by(.change) | map(del(.change))" >&3 +cat /tmp/check_result >&3 diff --git a/assets/helpers/bitbucket.sh b/assets/helpers/bitbucket.sh index 3f710e7..5fddd3a 100755 --- a/assets/helpers/bitbucket.sh +++ b/assets/helpers/bitbucket.sh @@ -35,9 +35,6 @@ bitbucket_request() { rm -f "$request_data" } - # register the cleanup function to be called on the EXIT signal - trap request_result_cleanup EXIT - local extra_options="" if [ -n "$data" ]; then method=${method:-POST} @@ -72,8 +69,14 @@ bitbucket_request() { jq -c '.values' < "$request_result" elif [ "$(jq -c '.errors' < "$request_result")" == "null" ]; then jq '.' < "$request_result" - elif [ "${request_result/NoSuchPullRequestException}" = "${request_result}" ]; then - printf "ERROR" + elif grep -q NoSuchPullRequestException "$request_result"; then + printf "NO_SUCH_PULL_REQUEST" + return + elif grep -q 'This pull request has already been merged' "$request_result"; then + printf "ALREADY_MERGED" + return + elif grep -q 'This pull request has been declined and must be reopened before it can be merged' "$request_result"; then + printf "DECLINED" return else log "Bitbucket request ($request_url) failed: $(cat $request_result)" @@ -158,26 +161,33 @@ bitbucket_pullrequest_progress_comment() { # $2: hash of merge commit # $3: hash of source commit # $4: hash of target commit + # $5: custom comment local hash="$2" local progress_msg_end="" + local custom_comment="" + if [ "$hash" == "$3" ]; then progress_msg_end+=" into $4]" else progress_msg_end="] $3 into $4" fi + if [ -n "$5" ]; then + custom_comment="\n\n$5" + fi + local build_url="$ATC_EXTERNAL_URL/teams/$(rawurlencode "$BUILD_TEAM_NAME")/pipelines/$(rawurlencode "$BUILD_PIPELINE_NAME")/jobs/$(rawurlencode "$BUILD_JOB_NAME")/builds/$(rawurlencode "$BUILD_NAME")" local build_result_pre=" \n\n **[" local build_result_post="]($build_url)** - Build #$BUILD_NAME" case "$1" in success) - echo "$(bitbucket_pullrequest_progress_msg_start "$hash" "Finished")${progress_msg_end}${build_result_pre}✓ BUILD SUCCESS${build_result_post}" ;; + echo "$(bitbucket_pullrequest_progress_msg_start "$hash" "Finished")${progress_msg_end}${build_result_pre}✓ BUILD SUCCESS${build_result_post}${custom_comment}" ;; failure) - echo "$(bitbucket_pullrequest_progress_msg_start "$hash" "Finished")${progress_msg_end}${build_result_pre}✕ BUILD FAILED${build_result_post}" ;; + echo "$(bitbucket_pullrequest_progress_msg_start "$hash" "Finished")${progress_msg_end}${build_result_pre}✕ BUILD FAILED${build_result_post}${custom_comment}" ;; pending) - echo "$(bitbucket_pullrequest_progress_msg_start "$hash" "Started")${progress_msg_end}${build_result_pre}⌛ BUILD IN PROGRESS${build_result_post}" ;; + echo "$(bitbucket_pullrequest_progress_msg_start "$hash" "Started")${progress_msg_end}${build_result_pre}⌛ BUILD IN PROGRESS${build_result_post}${custom_comment}" ;; esac } diff --git a/assets/helpers/git.sh b/assets/helpers/git.sh index 6bca9b6..ee4496f 100644 --- a/assets/helpers/git.sh +++ b/assets/helpers/git.sh @@ -1,3 +1,5 @@ +#!/bin/bash + load_pubkey() { local private_key_path=$TMPDIR/git-resource-private-key @@ -7,7 +9,6 @@ load_pubkey() { chmod 0600 $private_key_path eval $(ssh-agent) >/dev/null 2>&1 - trap "kill $SSH_AGENT_PID" 0 SSH_ASKPASS=$ASSETS/helpers/askpass.sh DISPLAY= ssh-add $private_key_path >/dev/null diff --git a/assets/helpers/utils.sh b/assets/helpers/utils.sh index 3b37d85..06b841c 100755 --- a/assets/helpers/utils.sh +++ b/assets/helpers/utils.sh @@ -1,3 +1,5 @@ +#!/bin/bash + export TMPDIR=${TMPDIR:-/tmp} hash() { @@ -162,3 +164,13 @@ getBasePathOfBitbucket() { echo ${base_path} } + +cleanup() { + rm -rf "$TMPDIR/bitbucket-pullrequest-resource-bitbucket-request*" + rm -rf "$TMPDIR/bitbucket-pullrequest-resource-bitbucket-request-data*" + if pgrep ssh-agent > /dev/null 2>&1; then + killall ssh-agent > /dev/null 2>&1 + fi +} + +trap cleanup EXIT diff --git a/assets/in b/assets/in index 4e21009..869e5f5 100755 --- a/assets/in +++ b/assets/in @@ -132,12 +132,32 @@ if [ -z "$target_commit" ]; then exit 1 fi +# parse uri and retrieve host +uri_parser "$uri" +repo_host="${uri_schema}://${uri_address}" +repo_host=${repo_host}$(getBasePathOfBitbucket) + +# determine repository name for calling REST api +repo_name=$(basename "$uri" | sed "s/.git$//") +repo_project=$(basename $(dirname "$uri")) + +# verify target branch of prq +prq=$(bitbucket_pullrequest "$repo_host" "$repo_project" "$repo_name" "$prq_id" "" "$skip_ssl_verification") + +if [ "$prq" != "NO_SUCH_PULL_REQUEST" ] && \ + [ "$prq" != "ALREADY_MERGED" ] && \ + [ "$prq" != "DECLINED" ]; then + branch=$(echo "$prq" | jq -r '.fromRef.displayId') +fi + + # expose configuration of pull request that can be used in container git config --add pullrequest.id $prq_id git config --add pullrequest.source $source_commit git config --add pullrequest.target $target_commit git config --add pullrequest.merge $ref git config --add pullrequest.date "$prq_date" +git config --add pullrequest.branch "$branch" jq -n "{ version: $(jq '.version' < "$payload"), diff --git a/assets/out b/assets/out index 864ce29..d8db180 100755 --- a/assets/out +++ b/assets/out @@ -7,8 +7,11 @@ exec 3>&1 # make stdout available as fd 3 for the result exec 1>&2 # redirect all output to stderr for logging ASSETS=$(cd "$(dirname "$0")" && pwd) +# shellcheck source=helpers/git.sh source $ASSETS/helpers/git.sh +# shellcheck source=helpers/utils.sh source $ASSETS/helpers/utils.sh +# shellcheck source=helpers/bitbucket.sh source $ASSETS/helpers/bitbucket.sh # for all temporary files in 'out' @@ -38,9 +41,12 @@ uri=$(jq -r '.source.uri // ""' < "$payload") git_config_payload=$(jq -r '.source.git_config // []' < "$payload") rebuild_when_target_changed=$(jq -r '.source.rebuild_when_target_changed // "false"' < "$payload") rebuild_phrase=$(jq -r '.source.rebuild_phrase // "test this please"' < "$payload") +create_comments=$(jq -r '.source.create_comments // "false"' < "$payload") path=$(jq -r '.params.path // ""' < "$payload") status=$(jq -r '.params.status // ""' < "$payload") +additionnal_comment=$(jq -r '.params.comment // ""' < "$payload") +additionnal_comment_file=$(jq -r '.params.commentFile // ""' < "$payload") configure_git_ssl_verification "$skip_ssl_verification" configure_git_global "${git_config_payload}" @@ -61,14 +67,20 @@ if [ -z "$status" ]; then fi cd "$source" + +if [ -n "$additionnal_comment_file" ]; then + additionnal_comment="$(<${additionnal_comment_file})" +fi + cd "$path" merge_commit=$(git rev-parse HEAD) ls_remote=$(git ls-remote "$uri") -# collect prq id from git config stored in git config (during get step) +# collect prq id and branch name from git config stored in git config (during get step) # included cat to catch error prq_number=$(git config --get pullrequest.id | cat) +branch=$(git config --get pullrequest.branch | cat) if [ -z "$prq_number" ]; then prqs=$(echo "$ls_remote" | grep -E "/pull\-requests/[0-9]+" | grep "$merge_commit" | cat) @@ -141,13 +153,17 @@ data=$(jq -cn "{ bitbucket_pullrequest_commit_status "$repo_host" "$source_commit" "$data" "" "" "$skip_ssl_verification" # use the pullrequest date stored in git config in get -prq_verify_date=$(git config --get pullrequest.date | cat) +PULL_REQUEST_DATE=$(git config --get pullrequest.date | cat) + +# Add branch name to additional comment +if [ -n "$additionnal_comment" ]; then + additionnal_comment="${additionnal_comment//\[\[BRANCH\]\]/$branch}" +fi # add comment to pull request to track if build was started/finished -comment_message=$(bitbucket_pullrequest_progress_comment "$status" "$prq_hash" "$source_commit" "$target_commit") +comment_message=$(bitbucket_pullrequest_progress_comment "$status" "$prq_hash" "$source_commit" "$target_commit", "$additionnal_comment") comments=$(bitbucket_pullrequest_overview_comments "$repo_host" "$repo_project" "$repo_name" "$prq_number" "" "$skip_ssl_verification" | jq -c '.[]') commented="" -skip_verify=false if [ -n "$comments" ]; then while read -r comment; do id=$(echo "$comment" | jq -r '.id') @@ -155,28 +171,15 @@ if [ -n "$comments" ]; then version=$(echo "$comment" | jq -r '.version') # check for progress messages => if pull request number matches then edit comment (instead of creating a new one) - if [ -z "$commented" ]; then - if bitbucket_pullrequest_progress_commit_match "$text" "$prq_hash" "Started"; then - bitbucket_pullrequest_update_comment_status "$repo_host" "$repo_project" "$repo_name" "$prq_number" "$comment_message" "$id" "$version" "" "$skip_ssl_verification" >/dev/null - commented=true - fi - fi - - # edit timestamp to force new build when rebuild_phrase is included in comments - if [ "$skip_verify" != "true" ]; then - if echo "$text" | grep -Ec "$rebuild_phrase" > /dev/null; then - prq_verify_date=$(date_from_epoch_seconds $(( ($(echo "$comment" | jq -r '.createdDate') + 500) / 1000))) - skip_verify=true - fi - fi - - if [ "$skip_verify" == "true" -a "$commented" == "true" ]; then + if bitbucket_pullrequest_progress_commit_match "$text" "$prq_hash" "Started" && [ "$create_comments" == "true" ]; then + bitbucket_pullrequest_update_comment_status "$repo_host" "$repo_project" "$repo_name" "$prq_number" "$comment_message" "$id" "$version" "" "$skip_ssl_verification" >/dev/null + commented=true break fi done <<< "$comments" fi -if [ -z "$commented" ]; then +if [ -z "$commented" ] && [ "$create_comments" == "true" ]; then bitbucket_pullrequest_add_comment_status "$repo_host" "$repo_project" "$repo_name" "$prq_number" "$comment_message" "" "$skip_ssl_verification" >/dev/null fi @@ -184,7 +187,7 @@ jq -n "{ version: { id: \"$prq_number\", hash: \"$prq_hash\", - date: \"$prq_verify_date\" + date: \"$PULL_REQUEST_DATE\" }, metadata: $(pullrequest_metadata "$prq_number" "$uri" "$skip_ssl_verification") }" >&3 diff --git a/scripts/test b/scripts/test index 85cec78..3b0020d 100755 --- a/scripts/test +++ b/scripts/test @@ -1,24 +1,21 @@ #!/bin/bash -set -e - -not_installed() { - ! command -v $1 > /dev/null 2>&1 -} +set -eu -bitbucket_pullrequest_dir=$(cd $(dirname $0)/.. && pwd) - -if not_installed docker; then - echo "# docker is not installed! run the following commands:" - echo " brew install docker" - echo " brew cask install docker-machine" - echo " docker-machine create dev --driver virtualbox" - echo ' eval $(docker-machine env dev)' - echo " docker login" - exit 1 -fi +set +e +docker run --interactive --rm hadolint/hadolint < Dockerfile +docker run --interactive --rm --volume "$PWD:/mnt" koalaman/shellcheck \ + --color=always \ + /mnt/assets/check \ + /mnt/assets/helpers/askpass.sh \ + /mnt/assets/helpers/bitbucket.sh \ + /mnt/assets/helpers/git.sh \ + /mnt/assets/helpers/utils.sh \ + /mnt/assets/in \ + /mnt/assets/out \ + /mnt/install_git_lfs.sh \ + /mnt/scripts/test +set -e -name=laurentverbruggen/concourse-bitbucket-pullrequest-resource -cd $bitbucket_pullrequest_dir -docker build --rm . -t $name -docker push $name +docker build --pull --rm --tag mm62/concourse-bitbucket-pullrequest-resource-test . +docker rmi mm62/concourse-bitbucket-pullrequest-resource-test