├── .atlassian └── OWNER ├── .changes ├── 0.0.1.json ├── 0.0.2.json ├── 0.0.3.json ├── 0.1.0.json ├── 0.1.1.json ├── 0.1.2.json ├── 0.1.3.json └── 0.2.0.json ├── .github └── CODEOWNERS ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── RELEASING.md ├── bin ├── git-setup.sh └── release.sh ├── bitbucket-pipelines.yml ├── pipe.yml ├── src ├── release-blocker.sh └── url-encoding.sh └── test └── test.bats /.atlassian/OWNER: -------------------------------------------------------------------------------- 1 | tmack -------------------------------------------------------------------------------- /.changes/0.0.1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Fix bitbucket pipelines to have initial release.", 4 | "type": "patch" 5 | } 6 | ] -------------------------------------------------------------------------------- /.changes/0.0.2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Fix invalid environment variables for Jira Authentication.", 4 | "type": "patch" 5 | } 6 | ] -------------------------------------------------------------------------------- /.changes/0.0.3.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Add additional tests and fix curl command while also limiting response fields from jira via the fields query parameter greatly improving debug-ability.", 4 | "type": "patch" 5 | } 6 | ] -------------------------------------------------------------------------------- /.changes/0.1.0.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Prepare for Open Source and Push Docker container to Atlassian Docker Hub organization user.", 4 | "type": "minor" 5 | } 6 | ] -------------------------------------------------------------------------------- /.changes/0.1.1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "OSR-475 rename bitbucket pipe to jira-release-blocker", 4 | "type": "patch" 5 | } 6 | ] -------------------------------------------------------------------------------- /.changes/0.1.2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "setup ssh keys for writing to master branch from pipelines build/deployment", 4 | "type": "patch" 5 | } 6 | ] -------------------------------------------------------------------------------- /.changes/0.1.3.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "fix invalid syntax of pipe.yml", 4 | "type": "patch" 5 | } 6 | ] -------------------------------------------------------------------------------- /.changes/0.2.0.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Add support for encoded and non encoded JQL parameters - Jira Ticket: BLOCK-1", 4 | "type": "minor" 5 | } 6 | ] -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global rule: 2 | * @tmack8001 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | Note: version releases in the 0.x.y range may introduce breaking changes. 3 | 4 | ## 0.2.0 5 | 6 | - minor: Add support for encoded and non encoded JQL parameters - Jira Ticket: BLOCK-1 7 | 8 | ## 0.1.3 9 | 10 | - patch: fix invalid syntax of pipe.yml 11 | 12 | ## 0.1.2 13 | 14 | - patch: setup ssh keys for writing to master branch from pipelines build/deployment 15 | 16 | ## 0.1.1 17 | 18 | - patch: OSR-475 rename bitbucket pipe to jira-release-blocker 19 | 20 | ## 0.1.0 21 | 22 | - minor: Prepare for Open Source and Push Docker container to Atlassian Docker Hub organization user. 23 | 24 | ## 0.0.3 25 | 26 | - patch: Add additional tests and fix curl command while also limiting response fields from jira via the fields query parameter greatly improving debug-ability. 27 | 28 | ## 0.0.2 29 | 30 | - patch: Fix invalid environment variables for Jira Authentication. 31 | 32 | ## 0.0.1 33 | 34 | - patch: Fix bitbucket pipelines to have initial release. 35 | 36 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Submitting contributions or comments that you know to violate the intellectual property or privacy rights of others 15 | * Other unethical or unprofessional conduct 16 | 17 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 18 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 19 | 20 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 21 | 22 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer. Complaints will result in a response and be reviewed and investigated in a way that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. 23 | 24 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [http://contributor-covenant.org/version/1/3/0/][version] 25 | 26 | [homepage]: http://contributor-covenant.org 27 | [version]: http://contributor-covenant.org/version/1/3/0/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Release Blockers 2 | 3 | Thank you for considering a contribution to Release Blockers! Pull requests, issues and comments are welcome. For pull requests, please: 4 | 5 | * Add tests for new features and bug fixes 6 | * Follow the existing style 7 | * Separate unrelated changes into multiple pull requests 8 | 9 | See the existing issues for things to start contributing. 10 | 11 | For bigger changes, please make sure you start a discussion first by creating an issue and explaining the intended change. 12 | 13 | Atlassian requires contributors to sign a Contributor License Agreement, known as a CLA. This serves as a record stating that the contributor is entitled to contribute the code/documentation/translation to the project and is willing to have it used in distributions and derivative works (or is willing to transfer ownership). 14 | 15 | Prior to accepting your contributions we ask that you please follow the appropriate link below to digitally sign the CLA. The Corporate CLA is for those who are contributing as a member of an organization and the individual CLA is for those contributing as an individual. 16 | 17 | * [CLA for corporate contributors](https://na2.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=e1c17c66-ca4d-4aab-a953-2c231af4a20b) 18 | * [CLA for individuals](https://na2.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=3f94fbdc-2fbe-46ac-b14c-5d152700ae5d) -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.11 2 | 3 | RUN apk add --update --no-cache bash curl jq 4 | 5 | COPY src / 6 | COPY LICENSE pipe.yml README.md / 7 | RUN wget -P / https://bitbucket.org/bitbucketpipelines/bitbucket-pipes-toolkit-bash/raw/0.6.0/common.sh 8 | 9 | RUN chmod a+x /*.sh 10 | 11 | ENTRYPOINT ["/release-blocker.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2020] Atlassian Pty Ltd 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitbucket Pipe: jira-release-blocker 2 | [![Atlassian license](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](CONTRIBUTING.md) ![build](https://img.shields.io/bitbucket/pipelines/atlassian/jira-release-blocker) ![version](https://img.shields.io/docker/v/atlassianlabs/jira-release-blocker?sort=semver) 3 | 4 | When building web applications industry best practices are to have a CI/CD (continuous integration and continuous delivery) pipeline put in place. With Bitbucket Pipelines this is as easy as providing a bitbucket-pipelines.yml file that contains the logic and code to do just that. However, infrequently the development team might want to block releases to their production environment due to various reasons (earnings calls, major traffic days, release windows, significant new feature, change review, and several more). 5 | 6 | A great way to implement this type of release block is to configure a Jira Issue as a "release blocker" (typically using labels). Whenever this release blocker is found the release to your production environment the release pipeline would bail over before continuing waiting for a human to resolve the release blocker ticket. 7 | 8 | This [Bitbucket Pipelines Pipe](https://bitbucket.org/product/features/pipelines/integrations) will query the provided JQL and/or Jira Filter (cloud only) and will block any and all deployments to the configured environments if Jira Issues are found as a result of the provided search. 9 | 10 | ## YAML Definition 11 | Add the following snippet to the script section of your `bitbucket-pipelines.yml` file: 12 | 13 | ### (preferred) Bitbucket Repository Reference 14 | 15 | When referencing a Bitbucket Pipe via a repository reference `/:` this tells Bitbucket Pipelines to look at the relative `pipe.yml` within that repository for where to find the Pipe Docker image. This is best as this allows the DockerHub namespace to change over time if needed. 16 | 17 | ```yaml 18 | script: 19 | - pipe: atlassian/jira-release-blocker:0.2.0 20 | variables: 21 | JIRA_JQL: "" 22 | JIRA_CLOUD_ID: "" 23 | JIRA_USERNAME: "" 24 | JIRA_API_TOKEN: $JIRA_API_TOKEN # DO NOT type value of api_token here, instead store as "secure" environment variable in pipelines settings 25 | # JIRA_HOSTNAME: "" # Optional, required if JIRA_CLOUD_ID not specified 26 | ``` 27 | 28 | ### Docker Image Reference 29 | 30 | ```yaml 31 | script: 32 | - pipe: docker://atlassianlabs/jira-release-blocker:0.2.0 33 | variables: 34 | JIRA_JQL: "" 35 | JIRA_CLOUD_ID: "" 36 | JIRA_USERNAME: "" 37 | JIRA_API_TOKEN: $JIRA_API_TOKEN # DO NOT type value of api_token here, instead store as "secure" environment variable in pipelines settings 38 | # JIRA_HOSTNAME: "" # Optional, required if JIRA_CLOUD_ID not specified 39 | ``` 40 | 41 | ## Variables 42 | 43 | | Variable | Usage | 44 | | ---------------------- | ---------------------------------------------------------------------------- | 45 | | JIRA_JQL (*) | Jira Query Language (JQL) for executing a search against a Jira Instance. | 46 | | JIRA_CLOUD_ID (1*) | Cloud ID of the Jira Site to execute a search against. | 47 | | JIRA_HOSTNAME (1*) | Jira Hostname to use if Jira Cloud ID isn't specified. | 48 | | JIRA_USERNAME (*) | Username of the user you want this pipe to query Jira with (remember project permissions). Typically the username/email used to log into the Atlassian Account (via id.atlassian.net) which has access to the Jira you want to query. | 49 | | JIRA_API_TOKEN (***) | API Token associated with JIRA_USERNAME stored as a secure environment variable. | 50 | | ALLOW_ROLLBACK_DEPLOYS | Boolean to opt rollback deploys into release blockers. Default: `true` | 51 | | DEBUG | Turn on extra debug information. Default: `false`. | 52 | 53 | _(*) = required variable._ 54 | 55 | _(***) = required secure environment variable._ 56 | 57 | _(1*) = at least one of these variables is required._ 58 | 59 | ## Use In Other CI/CD Platforms (ie. Bamboo, CircleCi, TravisCI, etc) 60 | There are a few options for execution in other CI/CD Platforms. The best method to verify that all nessecary files, dependency programs, and configurations are properly setup is to execute the Docker container that would be executed in Bitbucket Pipelines. The alternative is to setup all dependencies yourself and execute the shell script directly. 61 | 62 | ### (preferred) Pull and Run Docker Container 63 | 64 | If the following returns an error code (non zero) abort the build with a "paused" or "failed" status. 65 | 66 | ```bash 67 | docker run -e JIRA_USERNAME=${username} -e JIRA_API_TOKEN=${api_token} -e JIRA_JQL= -e JIRA_HOSTNAME=.atlassian.net atlassianlabs/jira-release-blocker:0.2.0 68 | ``` 69 | 70 | ### (not recommended) Download individual script files to execute locally 71 | 72 | If the following returns an error code (non zero) abort the build with a "paused" or "failed" status. 73 | 74 | The benefit with this method is that there is no build dependency on docker, though most build images now adays contain docker runtimes as it is a commonly used containerization framework. Downsize is as dependencies and additional sources are added to jira-release-blocker release engineers will need to update their scripts to be compatible. 75 | 76 | ```bash 77 | wget -P / https://bitbucket.org/atlassian/jira-release-blocker/raw/0.1.3/src/release-blocker.sh 78 | wget -P / https://bitbucket.org/atlassian/jira-release-blocker/raw/0.1.3/src/url-encoding.sh 79 | wget -P / https://bitbucket.org/bitbucketpipelines/bitbucket-pipes-toolkit-bash/raw/0.6.0/common.sh 80 | chmod a+x ./*.sh 81 | 82 | JIRA_USERNAME=${username} JIRA_API_TOKEN=${api_token} JIRA_JQL= JIRA_HOSTNAME=.atlassian.net ./release-blocker.sh 83 | ``` 84 | 85 | ## Support 86 | If you'd like help with this pipe, or you have an issue or feature request, let us know. 87 | The pipe is maintained by Trevor Mack, tmack(at)atlassian.com. 88 | 89 | If you're reporting an issue, please include: 90 | 91 | - the version of the pipe 92 | - relevant logs and error messages 93 | - steps to reproduce 94 | 95 | ## Contributions 96 | 97 | Contributions to Release Blockers are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. 98 | 99 | ## License 100 | 101 | Copyright (c) [2020] Atlassian and others. 102 | Apache 2.0 licensed, see [LICENSE](LICENSE) file. 103 | 104 |
105 | 106 | [![With ❤️ from Atlassian](https://raw.githubusercontent.com/atlassian-internal/oss-assets/master/banner-cheers.png)](https://www.atlassian.com) -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | ## Releasing a new version 2 | 3 | This pipe uses an automated release process to bump versions using semantic versioning and generate the CHANGELOG.md file automatically. 4 | 5 | In order to automate this process it uses a tool called [`semversioner`](https://pypi.org/project/semversioner/). 6 | 7 | ### Steps to release 8 | 9 | 1) Install semversioner in local. 10 | 11 | ```sh 12 | pip install semversioner 13 | ``` 14 | 15 | 2) During development phase, every change that needs to be integrated to `master` will need one or more changeset files. You can use semversioner to generate changeset. 16 | 17 | ```sh 18 | semversioner add-change --type patch --description "Fix security vulnerability with authentication." 19 | ``` 20 | 21 | 3) Make sure you commit the changeset files generated in `.change/next-release/` folder with your code. For example: 22 | 23 | ```sh 24 | git add . 25 | git commit -m "BP-234 FIX security issue with authentication" 26 | git push origin 27 | ``` 28 | 29 | 4) That's it! Merge to `master` and Enjoy! Bitbucket Pipelines will do the rest: 30 | 31 | - Generate new version number based on the changeset types `major`, `minor`, `patch`. 32 | - Generate a new file in `.changes` directory with all the changes for this specific version. 33 | - (Re)generate the CHANGELOG.md file. 34 | - Bump the version number in `README.md` example and `pipe.yml` metadata. 35 | - Commit and push back to the repository. 36 | - Tag your commit with the new version number. -------------------------------------------------------------------------------- /bin/git-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Configure git to allow push back to the remote repository. 4 | # 5 | # Required globals: 6 | # PRIVATE_KEY: base64 encoded ssh private key 7 | # 8 | 9 | set -e 10 | 11 | echo $PRIVATE_KEY > ~/.ssh/id_rsa.tmp 12 | base64 -d ~/.ssh/id_rsa.tmp > ~/.ssh/id_rsa 13 | chmod 600 ~/.ssh/id_rsa 14 | 15 | # Configure git 16 | git config user.name "Pipelines Tasks" 17 | git config user.email commits-noreply@bitbucket.org 18 | git remote set-url origin git@bitbucket.org:${BITBUCKET_WORKSPACE}/${BITBUCKET_REPO_SLUG}.git -------------------------------------------------------------------------------- /bin/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | IMAGE=$1 5 | 6 | ## 7 | # Step 1: Generate new version 8 | ## 9 | previous_version=$(semversioner current-version) 10 | semversioner release 11 | new_version=$(semversioner current-version) 12 | 13 | ## 14 | # Step 2: Generate CHANGELOG.md 15 | ## 16 | echo "Generating CHANGELOG.md file..." 17 | semversioner changelog > CHANGELOG.md 18 | # Use new version in the README.md examples 19 | echo "Replace version '$previous_version' to '$new_version' in README.md ..." 20 | sed -i "s/$BITBUCKET_REPO_SLUG:[0-9]*\.[0-9]*\.[0-9]*/$BITBUCKET_REPO_SLUG:$new_version/g" README.md 21 | # Use new version in the pipe.yml metadata file 22 | echo "Replace version '$previous_version' to '$new_version' in pipe.yml ..." 23 | sed -i "s/$BITBUCKET_REPO_SLUG:[0-9]*\.[0-9]*\.[0-9]*/$BITBUCKET_REPO_SLUG:$new_version/g" pipe.yml 24 | 25 | ## 26 | # Step 3: Build and push docker image 27 | ## 28 | echo "Build and push docker image..." 29 | echo ${DOCKERHUB_PASSWORD} | docker login --username "$DOCKERHUB_USERNAME" --password-stdin 30 | docker build -t ${IMAGE} . 31 | docker tag ${IMAGE} ${IMAGE}:${new_version} 32 | docker push ${IMAGE} 33 | 34 | ## 35 | # Step 4: Commit back to the repository 36 | ## 37 | echo "Committing updated files to the repository..." 38 | git add . 39 | git commit -m "Update files for new version '${new_version}' [skip ci]" 40 | git push origin ${BITBUCKET_BRANCH} 41 | 42 | ## 43 | # Step 5: Tag the repository 44 | ## 45 | echo "Tagging for release ${new_version}" "${new_version}" 46 | git tag -a -m "Tagging for release ${new_version}" "${new_version}" 47 | git push origin ${new_version} -------------------------------------------------------------------------------- /bitbucket-pipelines.yml: -------------------------------------------------------------------------------- 1 | image: atlassian/default-image:2 2 | 3 | test: &test 4 | step: 5 | name: Test 6 | script: 7 | - npm install -g bats 8 | - chmod a+x test/*.bats 9 | - bats test/test.bats 10 | services: 11 | - docker 12 | 13 | push: &push 14 | step: 15 | name: Push and Tag 16 | deployment: Production 17 | image: python:3.7 18 | script: 19 | - pip install semversioner==0.* 20 | - ./bin/git-setup.sh 21 | - ./bin/release.sh $DOCKERHUB_NAMESPACE/$BITBUCKET_REPO_SLUG 22 | services: 23 | - docker 24 | 25 | pipelines: 26 | default: 27 | - <<: *test 28 | branches: 29 | master: 30 | - <<: *test 31 | - <<: *push 32 | -------------------------------------------------------------------------------- /pipe.yml: -------------------------------------------------------------------------------- 1 | name: Jira Release Blocker 2 | image: atlassianlabs/jira-release-blocker:0.2.0 3 | variables: 4 | - name: JIRA_USERNAME 5 | default: "$JIRA_USERNAME" 6 | - name: JIRA_API_TOKEN 7 | default: "$JIRA_API_TOKEN" 8 | - name: JIRA_JQL 9 | default: "$JIRA_JQL" 10 | - name: JIRA_CLOUD_ID 11 | default: "$JIRA_CLOUD_ID" 12 | - name: JIRA_HOSTNAME 13 | default: "$JIRA_HOSTNAME" 14 | - name: ALLOW_ROLLBACK_DEPLOYS 15 | default: "$ALLOW_ROLLBACK_DEPLOYS" 16 | - name: DEBUG 17 | default: "$DEBUG" 18 | description: This pipe will enforce there aren't any release blockers before allowing a deployment. 19 | repository: https://bitbucket.org/atlassian/jira-release-blocker 20 | maintainer: tmack@atlassian.com 21 | tags: 22 | - bitbucket-pipelines 23 | - jira 24 | - deployment 25 | - pipes 26 | - bash 27 | - release 28 | - release-blocker 29 | -------------------------------------------------------------------------------- /src/release-blocker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$(dirname "$0")/common.sh" 4 | source "$(dirname "$0")/url-encoding.sh" 5 | 6 | # 7 | # Required globals: 8 | # JIRA_JQL 9 | # JIRA_CLOUD_ID or JIRA_HOSTNAME 10 | # JIRA_USERNAME 11 | # JIRA_API_TOKEN 12 | # 13 | # Optional globals: 14 | # ALLOW_ROLLBACK_DEPLOYS (default: true) 15 | # DEBUG (default: false) 16 | 17 | set -e 18 | 19 | # required parameters 20 | JIRA_USERNAME=${JIRA_USERNAME:?'JIRA_USERNAME environment variable missing'} 21 | JIRA_API_TOKEN=${JIRA_API_TOKEN:?'JIRA_API_TOKEN environment variable missing.'} 22 | JIRA_JQL=${JIRA_JQL:?'JIRA_JQL environment variable missing (ex. filter=15793).'} # 'filter=15793' 23 | 24 | # default parameters 25 | ALLOW_ROLLBACK_DEPLOYS=${ALLOW_ROLLBACK_DEPLOYS:=true} 26 | DEBUG=${DEBUG:="false"} 27 | 28 | JIRA_CLOUD_ID=${JIRA_CLOUD_ID:=} # 'DUMMY-158c8204-ff3b-47c2-adbb-a0906ccc722b' 29 | JIRA_HOSTNAME=${JIRA_HOSTNAME:=} # 'product-fabric.atlassian.net' 30 | 31 | enable_debug 32 | 33 | # TODO (tmack) check for newer version via commons.sh method check_for_newer_version 34 | # the above requires the repository to be public and open to the world 35 | 36 | # TODO (tmack) can we support bitbucket pipelines rollback, https://confluence.atlassian.com/bitbucket/rollbacks-981147477.html 37 | 38 | # Always allow rollback deployments if configured 39 | if [[ "${ALLOW_ROLLBACK_DEPLOYS}" = true && "${bamboo_deploy_rollback}" = true ]]; then 40 | success "Proceed with the rollback deployment"; 41 | fi 42 | 43 | # check for Jira Site configuration data 44 | if [[ -z "${JIRA_HOSTNAME}" && -z "${JIRA_CLOUD_ID}" ]]; then 45 | fail "JIRA_CLOUD_ID or JIRA_HOSTNAME environment variable missing"; 46 | fi 47 | 48 | DECODED_JQL=$(urldecode "${JIRA_JQL}") # decode incoming UGC (decoded -> decoded; encoded -> decoded) 49 | 50 | API_HOSTNAME="api.atlassian.com" 51 | JIRA_ENDPOINT="/rest/api/2/search" 52 | 53 | FIELDS_QUERY_PARAMETER="fields=summary" 54 | JQL_QUERY_PARAMETER=${DECODED_JQL} 55 | 56 | debug ${JQL_QUERY_PARAMETER}; 57 | 58 | if [[ ! -z "${JIRA_HOSTNAME}" ]]; then 59 | JIRA_BLOCKERS_URL="https://${JIRA_HOSTNAME}${JIRA_ENDPOINT}" 60 | elif [[ ! -z "${JIRA_CLOUD_ID}" ]]; then 61 | JIRA_BLOCKERS_URL="https://${API_HOSTNAME}/ex/jira/${JIRA_CLOUD_ID}${JIRA_ENDPOINT}" 62 | fi 63 | 64 | # check for Jira Authentication configuration 65 | if [[ -z "${JIRA_USERNAME}" ]]; then 66 | fail "JIRA_USERNAME environment variable missing"; 67 | fi 68 | 69 | # current limitation: only supported User API Tokens 70 | if [[ -z "${JIRA_API_TOKEN}" ]]; then 71 | warning "Create an API Token via instructions provided here: https://confluence.atlassian.com/cloud/api-tokens-938839638.html"; 72 | fail "JIRA_API_TOKEN environment variable missing"; 73 | fi 74 | 75 | # We fetch all the issues that match the provided JQL search criteria (filter or native JQL supported). 76 | # HTTP_RESPONSE=$( 77 | # curl -X GET \ 78 | # -w '%{stderr}{"status": %{http_code}, "body":%{stdout}}' -s -o - \ 79 | # --user "${JIRA_USERNAME}:${JIRA_API_TOKEN}" \ 80 | # --header 'Accept: application/json' \ 81 | # --url $JIRA_BLOCKERS_URL 2>&1 \ 82 | # ) 83 | 84 | # store the whole response with the status at the and 85 | HTTP_RESPONSE=$( 86 | curl --silent --write-out "HTTPSTATUS:%{http_code}" \ 87 | --user "${JIRA_USERNAME}:${JIRA_API_TOKEN}" \ 88 | --header 'Accept: application/json' \ 89 | -G "${JIRA_BLOCKERS_URL}" --data-urlencode "${FIELDS_QUERY_PARAMETER}" --data-urlencode "jql=${JQL_QUERY_PARAMETER}" 2>&1 \ 90 | ) 91 | 92 | # extract the body 93 | HTTP_BODY=$( 94 | echo "$HTTP_RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g' 95 | ) 96 | 97 | # extract the status 98 | HTTP_STATUS=$( 99 | echo "$HTTP_RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://' 100 | ) 101 | 102 | debug $HTTP_BODY 103 | debug $HTTP_STATUS 104 | 105 | if [ ! $HTTP_STATUS -eq 200 ]; then 106 | info $HTTP_BODY 107 | fail "Error: Could not fetch release blockers from Jira [Http Status: $HTTP_STATUS]"; 108 | fi 109 | 110 | TOTAL=$(echo "$HTTP_BODY" | jq .total); 111 | ISSUES=$(echo $HTTP_BODY | jq '.issues | .[] | {key, title: .fields.summary}') 112 | 113 | if [ ! $TOTAL -eq 0 ]; then 114 | info "Found following release blockers with JQL query \"${JQL_QUERY_PARAMETER}\"" 115 | 116 | if [[ ! -z "${JIRA_HOSTNAME}" ]]; then 117 | ISSUE_DATA=`echo $ISSUES | jq --arg HOSTNAME "${JIRA_HOSTNAME}" '["https://" + $HOSTNAME + "/browse/" + .key,.title] | @tsv'` 118 | else 119 | ISSUE_DATA=$(echo $ISSUES | jq '[.key,.title] | @tsv') 120 | fi 121 | 122 | while IFS= read -r line; do 123 | info "$line" 124 | done <<< "$ISSUE_DATA" 125 | 126 | 127 | fail "!!!Total of $TOTAL release blocker(s) found in Jira!!!"; 128 | else 129 | success "No release blockers found! Proceed to deployment!"; 130 | fi -------------------------------------------------------------------------------- /src/url-encoding.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Begin Standard 'imports' 4 | set -e 5 | set -o pipefail 6 | 7 | ####################################### 8 | # url encodes a provided string 9 | # Globals: 10 | # None 11 | # Arguments: 12 | # string: the raw string to url-encode 13 | # Returns: 14 | # url-encoded string 15 | ####################################### 16 | url_encode() { 17 | local string="${1}" 18 | local strlen=${#string} 19 | local encoded="" 20 | local pos c o 21 | 22 | for (( pos=0 ; pos 47 | 48 | local url_encoded="${1//+/ }" 49 | printf '%b' "${url_encoded//\%/\\x}" 50 | } -------------------------------------------------------------------------------- /test/test.bats: -------------------------------------------------------------------------------- 1 | setup() { 2 | DOCKER_IMAGE=${DOCKER_IMAGE:="test/release-blocker-pipe"} 3 | 4 | echo "Building image..." 5 | docker build -t ${DOCKER_IMAGE}:test . 6 | } 7 | 8 | @test "Requires JIRA_USERNAME Argument" { 9 | run docker run \ 10 | -v $(pwd):$(pwd) \ 11 | -w $(pwd) \ 12 | ${DOCKER_IMAGE}:test 13 | 14 | echo "Status: $status" 15 | echo "Output: $output" 16 | 17 | [ "$status" -eq 1 ] 18 | [[ "$output" == *"JIRA_USERNAME environment variable missing"* ]] 19 | } 20 | 21 | @test "Requires JIRA_API_TOKEN Argument" { 22 | run docker run \ 23 | -e JIRA_USERNAME="username@atlassian.com" \ 24 | -v $(pwd):$(pwd) \ 25 | -w $(pwd) \ 26 | ${DOCKER_IMAGE}:test 27 | 28 | echo "Status: $status" 29 | echo "Output: $output" 30 | 31 | [ "$status" -eq 1 ] 32 | [[ "$output" == *"JIRA_API_TOKEN environment variable missing"* ]] 33 | } 34 | 35 | @test "Requires JIRA_JQL Argument" { 36 | run docker run \ 37 | -e JIRA_USERNAME="username@atlassian.com" \ 38 | -e JIRA_API_TOKEN="my-api-token" \ 39 | -v $(pwd):$(pwd) \ 40 | -w $(pwd) \ 41 | ${DOCKER_IMAGE}:test 42 | 43 | echo "Status: $status" 44 | echo "Output: $output" 45 | 46 | [ "$status" -eq 1 ] 47 | [[ "$output" == *"JIRA_JQL environment variable missing"* ]] 48 | } 49 | 50 | @test "Requires JIRA_CLOUD_ID or JIRA_HOSTNAME Argument" { 51 | run docker run \ 52 | -e JIRA_USERNAME="username@atlassian.com" \ 53 | -e JIRA_API_TOKEN="my-api-token" \ 54 | -e JIRA_JQL="filter=1234" \ 55 | -v $(pwd):$(pwd) \ 56 | -w $(pwd) \ 57 | ${DOCKER_IMAGE}:test 58 | 59 | echo "Status: $status" 60 | echo "Output: $output" 61 | 62 | [ "$status" -eq 1 ] 63 | [[ "$output" == *"JIRA_CLOUD_ID or JIRA_HOSTNAME environment variable missing"* ]] 64 | } 65 | 66 | @test "JIRA_CLOUD_ID provided fails 404" { 67 | run docker run \ 68 | -e JIRA_USERNAME="${JIRA_USERNAME}" \ 69 | -e JIRA_API_TOKEN="${JIRA_API_TOKEN}" \ 70 | -e JIRA_JQL="filter=1234" \ 71 | -e JIRA_CLOUD_ID="1234" \ 72 | -v $(pwd):$(pwd) \ 73 | -w $(pwd) \ 74 | ${DOCKER_IMAGE}:test 75 | 76 | echo "Status: $status" 77 | echo "Output: $output" 78 | 79 | [ "$status" -eq 1 ] 80 | [[ "$output" == *"Error: Could not fetch release blockers from Jira [Http Status: 404]"* ]] 81 | } 82 | 83 | @test "JIRA_HOSTNAME provided fails 404" { 84 | run docker run \ 85 | -e JIRA_USERNAME="${JIRA_USERNAME}" \ 86 | -e JIRA_API_TOKEN="${JIRA_API_TOKEN}" \ 87 | -e JIRA_JQL="filter=1234" \ 88 | -e JIRA_HOSTNAME="ignored-non-existent.atlassian.net" \ 89 | -v $(pwd):$(pwd) \ 90 | -w $(pwd) \ 91 | ${DOCKER_IMAGE}:test 92 | 93 | echo "Status: $status" 94 | echo "Output: $output" 95 | 96 | [ "$status" -eq 1 ] 97 | [[ "$output" == *"Error: Could not fetch release blockers from Jira [Http Status: 404]"* ]] 98 | } 99 | 100 | @test "JIRA_HOSTNAME provided no release blockers found" { 101 | run docker run \ 102 | -e JIRA_USERNAME="${JIRA_USERNAME}" \ 103 | -e JIRA_API_TOKEN="${JIRA_API_TOKEN}" \ 104 | -e JIRA_JQL="labels=%22release-blocker-not-found%22" \ 105 | -e JIRA_HOSTNAME="hello.atlassian.net" \ 106 | -v $(pwd):$(pwd) \ 107 | -w $(pwd) \ 108 | ${DOCKER_IMAGE}:test 109 | 110 | echo "Status: $status" 111 | echo "Output: $output" 112 | 113 | [ "$status" -eq 0 ] 114 | [[ "$output" == *"No release blockers found! Proceed to deployment!"* ]] 115 | } 116 | 117 | @test "JIRA_CLOUD_ID provided no release blockers found" { 118 | run docker run \ 119 | -e JIRA_USERNAME="${JIRA_USERNAME}" \ 120 | -e JIRA_API_TOKEN="${JIRA_API_TOKEN}" \ 121 | -e JIRA_JQL="labels=%22release-blocker-not-found%22" \ 122 | -e JIRA_CLOUD_ID="a436116f-02ce-4520-8fbb-7301462a1674" \ 123 | -v $(pwd):$(pwd) \ 124 | -w $(pwd) \ 125 | ${DOCKER_IMAGE}:test 126 | 127 | echo "Status: $status" 128 | echo "Output: $output" 129 | 130 | [ "$status" -eq 0 ] 131 | [[ "$output" == *"No release blockers found! Proceed to deployment!"* ]] 132 | } 133 | 134 | @test "JIRA_CLOUD_ID provided finds release blocker with encoded jql query" { 135 | run docker run \ 136 | -e JIRA_USERNAME="${JIRA_USERNAME}" \ 137 | -e JIRA_API_TOKEN="${JIRA_API_TOKEN}" \ 138 | -e JIRA_JQL="labels=%22release-blocker%22" \ 139 | -e JIRA_CLOUD_ID="a436116f-02ce-4520-8fbb-7301462a1674" \ 140 | -v $(pwd):$(pwd) \ 141 | -w $(pwd) \ 142 | ${DOCKER_IMAGE}:test 143 | 144 | echo "Status: $status" 145 | echo "Output: $output" 146 | 147 | [ "$status" -eq 1 ] 148 | [[ "$output" == *"release blocker(s) found in Jira!!!"* ]] 149 | } 150 | 151 | @test "JIRA_CLOUD_ID provided finds release blocker with unencoded jql query" { 152 | run docker run \ 153 | -e JIRA_USERNAME="${JIRA_USERNAME}" \ 154 | -e JIRA_API_TOKEN="${JIRA_API_TOKEN}" \ 155 | -e JIRA_JQL="labels=\"release-blocker\"" \ 156 | -e JIRA_CLOUD_ID="a436116f-02ce-4520-8fbb-7301462a1674" \ 157 | -v $(pwd):$(pwd) \ 158 | -w $(pwd) \ 159 | ${DOCKER_IMAGE}:test 160 | 161 | echo "Status: $status" 162 | echo "Output: $output" 163 | 164 | [ "$status" -eq 1 ] 165 | [[ "$output" == *"release blocker(s) found in Jira!!!"* ]] 166 | } 167 | 168 | @test "JIRA_CLOUD_ID provided finds release blocker with jql query containing spaces" { 169 | run docker run \ 170 | -e JIRA_USERNAME="${JIRA_USERNAME}" \ 171 | -e JIRA_API_TOKEN="${JIRA_API_TOKEN}" \ 172 | -e JIRA_JQL="labels = \"release-blocker\" " \ 173 | -e JIRA_CLOUD_ID="a436116f-02ce-4520-8fbb-7301462a1674" \ 174 | -v $(pwd):$(pwd) \ 175 | -w $(pwd) \ 176 | ${DOCKER_IMAGE}:test 177 | 178 | echo "Status: $status" 179 | echo "Output: $output" 180 | 181 | [ "$status" -eq 1 ] 182 | [[ "$output" == *"release blocker(s) found in Jira!!!"* ]] 183 | } 184 | 185 | @test "JIRA_HOSTNAME provided finds release blocker with encoded jql query" { 186 | run docker run \ 187 | -e JIRA_USERNAME="${JIRA_USERNAME}" \ 188 | -e JIRA_API_TOKEN="${JIRA_API_TOKEN}" \ 189 | -e JIRA_JQL="labels=%22release-blocker%22" \ 190 | -e JIRA_HOSTNAME="hello.atlassian.net" \ 191 | -v $(pwd):$(pwd) \ 192 | -w $(pwd) \ 193 | ${DOCKER_IMAGE}:test 194 | 195 | echo "Status: $status" 196 | echo "Output: $output" 197 | 198 | [ "$status" -eq 1 ] 199 | [[ "$output" == *"release blocker(s) found in Jira!!!"* ]] 200 | } 201 | 202 | @test "JIRA_HOSTNAME provided finds release blocker with non encoded jql query" { 203 | run docker run \ 204 | -e JIRA_USERNAME="${JIRA_USERNAME}" \ 205 | -e JIRA_API_TOKEN="${JIRA_API_TOKEN}" \ 206 | -e JIRA_JQL="labels=\"release-blocker\"" \ 207 | -e JIRA_HOSTNAME="hello.atlassian.net" \ 208 | -v $(pwd):$(pwd) \ 209 | -w $(pwd) \ 210 | ${DOCKER_IMAGE}:test 211 | 212 | echo "Status: $status" 213 | echo "Output: $output" 214 | 215 | [ "$status" -eq 1 ] 216 | [[ "$output" == *"release blocker(s) found in Jira!!!"* ]] 217 | } 218 | 219 | @test "JIRA_HOSTNAME provided finds release blocker with jql query containing spaces" { 220 | run docker run \ 221 | -e JIRA_USERNAME="${JIRA_USERNAME}" \ 222 | -e JIRA_API_TOKEN="${JIRA_API_TOKEN}" \ 223 | -e JIRA_JQL="labels = \"release-blocker\" " \ 224 | -e JIRA_HOSTNAME="hello.atlassian.net" \ 225 | -v $(pwd):$(pwd) \ 226 | -w $(pwd) \ 227 | ${DOCKER_IMAGE}:test 228 | 229 | echo "Status: $status" 230 | echo "Output: $output" 231 | 232 | [ "$status" -eq 1 ] 233 | [[ "$output" == *"release blocker(s) found in Jira!!!"* ]] 234 | } 235 | --------------------------------------------------------------------------------