├── .circleci └── config.yml ├── .github └── workflows │ ├── build_and_test.yaml │ └── release.yaml ├── .gitignore ├── CHANGES.md ├── LICENSE ├── README.md ├── actions ├── add_comment.py ├── add_comment.yaml ├── add_status.py ├── add_status.yaml ├── add_team_membership.py ├── add_team_membership.yaml ├── check_deployment_env.py ├── check_deployment_env.yaml ├── create_deployment.py ├── create_deployment.yaml ├── create_deployment_status.py ├── create_deployment_status.yaml ├── create_file.py ├── create_file.yaml ├── create_issue.py ├── create_issue.yaml ├── create_pull.py ├── create_pull.yaml ├── create_release.py ├── create_release.yaml ├── delete_branch_protection.py ├── delete_branch_protection.yaml ├── deployment_event.yaml ├── get_branch_protection.py ├── get_branch_protection.yaml ├── get_clone_stats.py ├── get_clone_stats.yaml ├── get_contents.py ├── get_contents.yaml ├── get_deployment_statuses.py ├── get_deployment_statuses.yaml ├── get_issue.py ├── get_issue.yaml ├── get_pull.py ├── get_pull.yaml ├── get_traffic_stats.py ├── get_traffic_stats.yaml ├── get_user.py ├── get_user.yaml ├── latest_release.py ├── latest_release.yaml ├── lib │ ├── __init__.py │ ├── base.py │ ├── formatters.py │ └── utils.py ├── list_deployments.py ├── list_deployments.yaml ├── list_issues.py ├── list_issues.yaml ├── list_pulls.py ├── list_pulls.yaml ├── list_releases.py ├── list_releases.yaml ├── list_teams.py ├── list_teams.yaml ├── merge_pull.py ├── merge_pull.yaml ├── review_pull.py ├── review_pull.yaml ├── store_oauth_token.py ├── store_oauth_token.yaml ├── update_branch_protection.py ├── update_branch_protection.yaml ├── update_file.py ├── update_file.yaml └── workflows │ └── deployment_event.yaml ├── aliases ├── create_deployment.yaml ├── create_release.yaml ├── deployment_statuses.yaml ├── get_user.yaml ├── latest_release.yaml ├── list_releases.yaml └── store_oauth_token.yaml ├── config.schema.yaml ├── github.yaml.example ├── icon.png ├── pack.yaml ├── requirements-tests.txt ├── requirements.txt ├── rules └── deploy_pack_on_deployment_event.yaml ├── sensors ├── github_repository_sensor.py └── github_repository_sensor.yaml └── tests ├── fixtures ├── blank.yaml ├── full-enterprise.yaml └── full.yaml ├── github_base_action_test_case.py ├── test_action_add_comment.py ├── test_action_add_status.py ├── test_action_add_team_membership.py ├── test_action_aliases.py ├── test_action_check_deployment_env.py ├── test_action_create_deployment.py ├── test_action_create_deployment_status.py ├── test_action_create_file.py ├── test_action_create_issue.py ├── test_action_create_release.py ├── test_action_get_clone_stats.py ├── test_action_get_contents.py ├── test_action_get_deployment_statuses.py ├── test_action_get_issue.py ├── test_action_get_traffic_stats.py ├── test_action_get_user.py ├── test_action_latest_release.py ├── test_action_list_deployments.py ├── test_action_list_issues.py ├── test_action_list_releases.py ├── test_action_list_teams.py ├── test_action_store_oauth_token.py └── test_action_update_file.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | circleci_is_disabled_job: 5 | docker: 6 | - image: cimg/base:stable 7 | steps: 8 | - run: 9 | shell: /bin/bash 10 | command: echo CircleCI disabled on StackStorm-Exchange 11 | 12 | workflows: 13 | version: 2 14 | circleci_is_disabled: 15 | jobs: 16 | - circleci_is_disabled_job 17 | -------------------------------------------------------------------------------- /.github/workflows/build_and_test.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | schedule: 6 | # NOTE: We run this weekly at 1 am UTC on every Saturday 7 | - cron: '0 1 * * 6' 8 | 9 | jobs: 10 | # This is mirrored in the release workflow. 11 | build_and_test: 12 | name: 'Build and Test' 13 | uses: StackStorm-Exchange/ci/.github/workflows/pack-build_and_test.yaml@master 14 | with: 15 | enable-common-libs: true 16 | #apt-cache-version: v0 17 | #py-cache-version: v0 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | # the default branch 7 | - master 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | # This mirrors build_and_test workflow 14 | build_and_test: 15 | name: 'Build and Test' 16 | uses: StackStorm-Exchange/ci/.github/workflows/pack-build_and_test.yaml@master 17 | with: 18 | enable-common-libs: true 19 | #apt-cache-version: v0 20 | #py-cache-version: v0 21 | 22 | tag_release: 23 | needs: build_and_test 24 | name: Tag Release 25 | uses: StackStorm-Exchange/ci/.github/workflows/pack-tag_release.yaml@master 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.1.5 4 | 5 | * Fix default for token_user parameter in get_user action so that empty parameter has expected behavior. 6 | 7 | ## 2.1.4 8 | 9 | * Bug fix (#41) that `github.get_contents` action would be failed when decode parameter is set, 10 | and fix encoding processing problem in `github.create_file` and `github.update_file` actions. 11 | 12 | ## 2.1.3 13 | 14 | * Fix `update_branch_protection` action: dismissal users and teams can now be null in GitHub's API response. 15 | 16 | ## 2.1.2 17 | 18 | * Fix `merge_pull` action where it was incorrectly assessing mergeability of the given PR. 19 | 20 | ## 2.1.1 21 | 22 | * Bug fix (#43) where the sensor will throw an exception if no events are returned from the GitHub api. 23 | 24 | ## 2.1.0 25 | 26 | * Add new ``github.create_pull`` action which allows user create Pull Requests for a branch. 27 | 28 | ## 2.0.1 29 | 30 | * Bug fix (#38) where the sensor assumes `event.id` coming from the GitHub api will be in numeric order. 31 | 32 | ## 2.0.0 33 | 34 | * Drop Python 2.7 support 35 | 36 | ## 1.3.0 37 | 38 | * Add new ``github.get_branch_protection`` action which allows user to retrieve protection settings set on branch. 39 | * Add new ``github.update_branch_protection`` action which allows user to update protection settings for branch. 40 | * Add new ``github.delete_branch_protection`` action which allows user to delete protection from branch. 41 | 42 | ## 1.2.0 43 | 44 | __IMPORTANT__: Configuration scheme changed to mark token and password as secret, if you were using st2 datastore, you might need to encrypt the values! 45 | 46 | * Allow passing base64 encoded content to update and create file actions. 47 | * Bug fix where author/committer information wasn't correctly passed for file_create/update actions. Updated parameters' description with expected format should you want to add committer and/or author. 48 | * Add new ``github.get_contents`` action which allows user to retrieve file and repository contents. 49 | * Add new ``github.create_file`` action which allows user to create new files in repositories. 50 | * Add new ``github.update_file`` action which allows user to update existing files in repositories. 51 | * Bump libs version in requirements.txt 52 | 53 | ## 1.1.0 54 | 55 | * Add new ``github.get_pull`` action which allows user to retrieve details about 56 | a specific PR. 57 | 58 | ## 1.0.0 59 | 60 | * Clean up `enterprise_url` params, fixing github enterprise support 61 | 62 | ## 0.8.4 63 | 64 | * Version bump to fix tagging issues, no code changes 65 | 66 | ## 0.8.3 67 | 68 | * Added pull request list/review/merge actions 69 | 70 | ## 0.8.2 71 | 72 | * Strip whitespace from tokens when being stored by a user (Fixes: #14). 73 | 74 | ## 0.8.1 75 | 76 | * Make `repository_sensor` section in config schema optional 77 | 78 | ## 0.8.0 79 | 80 | * Added missing repository\_sensor section to `config.schema.yaml` 81 | * Added example configuration 82 | 83 | ## 0.7.1 84 | 85 | * Update sensor to use ``base_url`` option. 86 | 87 | ## 0.7.0 88 | 89 | * Updated action `runner_type` from `run-python` to `python-script` 90 | 91 | ## v0.6.3 92 | 93 | * Update the parameters for `pack.install` as they have changed. 94 | 95 | ## v0.6.2 96 | 97 | * Remove `immutable: true` from deploy\_payload the parameter in 98 | deployment\_event action. 99 | 100 | ## v0.6.1 101 | 102 | * Add context parameter to github.add\_status action 103 | 104 | ## v0.6.0 105 | 106 | * Add deployment event webhook. 107 | * Add deployment event workflow to trigger packs.install. 108 | * Add new action check\_deployment\_env env. 109 | * Add deployment\_environment config option. 110 | 111 | ## v0.5.0 112 | 113 | * Migrate config.yaml to config.schema.yaml. 114 | * Add actions and aliases managing releases (list, create, latest). 115 | * Add actions and aliases for managing deployments. 116 | * Add action and aliases for sorting a user scoped GitHub oauth token 117 | for GitHub.com and GitHub Enterprise. 118 | 119 | ## v0.4.0 120 | 121 | * Add support for Github enterprise by allowing user to provide ``base_url`` option in the config. 122 | This option can point to a custom URL for the Github Enterprise installations. 123 | 124 | ## v0.1.0 125 | 126 | * Initial release 127 | -------------------------------------------------------------------------------- /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 {yyyy} {name of copyright owner} 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. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Github Integration Pack 2 | 3 | Pack which allows integration with [Github](https://github.com/). 4 | 5 | ## Configuration 6 | 7 | Copy the example configuration in [github.yaml.example](./github.yaml.example) 8 | to `/opt/stackstorm/configs/github.yaml` and edit as required. 9 | 10 | It must contain: 11 | 12 | * ``token`` - Authentication token. Note: token only needs to be specified for 13 | actions such as ``add_comment`` and ``add_status`` which require 14 | authentication. If you use ``get_issue`` action only with public 15 | repositories, then token doesn't need to be specified. 16 | * ``repository_sensor.repositories`` - A list of repositories to monitor. Each 17 | item needs to contain the following keys: ``user`` - user or organization the 18 | repository you want to monitor belongs to and ``name`` - name of the 19 | repository you want to monitor. 20 | * ``repository_sensor.event_type_whitelist`` - List of whitelisted events to listen for. 21 | * ``user`` - GitHub Username (only for use with ``get_traffic_stats`` and ``get_clone_stats`` actions). 22 | * ``password`` - GitHub Password (only for use with ``get_traffic_stats`` and ``get_clone_stats`` actions). 23 | 24 | Keep in mind that even though actions which operate on public repositories 25 | don't require an authentication token, you are still encouraged to supply one 26 | because unauthenticated requests have a very low rate limit. 27 | 28 | **Note** : When modifying the configuration in `/opt/stackstorm/configs/` please 29 | remember to tell StackStorm to load these new values by running 30 | `st2ctl reload --register-configs` 31 | 32 | ## Obtaining an Authentication Token 33 | 34 | To obtain an authentication token, follow the instructions on the [Creating an 35 | access token for command-line use](https://help.github.com/articles/creating-an-access-token-for-command-line-use/) 36 | page. 37 | 38 | ## Sensors 39 | 40 | ### GithubRepositorySensor 41 | 42 | This sensor monitors Github repository for activity and dispatches a trigger 43 | for each repository event. 44 | 45 | > Note that current default poll interval requires authentication because of 46 | GitHub [rate-limiting](https://developer.github.com/v3/#rate-limiting) for 47 | unauthenticated requests. 48 | 49 | Currently supported event types: 50 | 51 | * ``IssuesEvent`` - Triggered when an issue is assigned, unassigned, labeled, 52 | unlabeled, closed, or reopened. 53 | * ``IssueCommentEvent`` - Triggered when an issue comment is created. 54 | * ``ForkEvent`` - Triggered when a user forks a repository. 55 | * ``WatchEvent`` - Triggered when a user stars a repository. 56 | * ``ReleaseEvent`` - Triggered when new release is available. 57 | * ``PushEvent`` - Triggered when a repository branch is pushed to. In addition to branch pushes, webhook push events are also triggered when repository tags are pushed. 58 | 59 | **Note** : The sensor will only listen events for the `github_type` you chosen 60 | in config.yaml 61 | 62 | 63 | #### github.repository_event trigger 64 | 65 | Example trigger payload: 66 | 67 | ```json 68 | { 69 | "repository": "st2", 70 | "id": "2482918921", 71 | "type": "WatchEvent", 72 | "created_at": "2014-12-25T11:47:27.000000Z", 73 | "actor": { 74 | "bio": null, 75 | "name": null, 76 | "url": "https://api.github.com/users/odyss009", 77 | "id": 483157, 78 | "loaction": null, 79 | "email": "redacted" 80 | }, 81 | "payload": { 82 | "action": "started" 83 | } 84 | } 85 | ``` 86 | 87 | All the events contain `repository`, `id`, `created_at`, `actor` and 88 | `payload` attribute. 89 | 90 | Value of the payload attribute depends on the event type. You can see a list 91 | of the available event types and their attributes on the [Event Types & 92 | Payloads](https://developer.github.com/v3/activity/events/types/) page. 93 | 94 | Note: Similar thing can be achieved using Github webhooks in combination with 95 | StackStorm webhook handler. 96 | 97 | ## Actions 98 | 99 | * ``add_comment`` - Add comment to the provided issue / pull request. 100 | * ``add_status`` - Add commit status to the provided commit. 101 | * ``create_file`` - Create new file. 102 | * ``create_issue`` - Create a new issue. 103 | * ``create_pull`` - Create a new Pull Request. 104 | * ``delete_branch_protection`` - Remove branch protection settings. 105 | * ``get_branch_protection`` - Get branch protection settings. 106 | * ``get_contents`` - Get repository or file contents. 107 | * ``get_issue`` - Retrieve information about a particular issue. Note: You 108 | only need to specify authentication token in the config if you use this 109 | action with a private repository. 110 | * ``list_issues`` - List all the issues for a particular repo (includes pull 111 | requests since pull requests are just a special type of issues). 112 | * ``list_pulls`` - List all pull requests for a particular repo. 113 | * ``merge_pull`` - Merge the provided pull request. 114 | * ``review_pull`` - Create a review for the provided pull request. 115 | * ``update_branch_protection`` - Update branch protection settings. 116 | * ``update_file`` - Update existing file. 117 | 118 | ## Rules 119 | 120 | ### github.deployment_event_webhook 121 | 122 | To enable this rule, run the following on the CLI (with a valid ST2 auth token): 123 | 124 | ```bash 125 | st2 rule enable github.deploy_pack_on_deployment_event 126 | ``` 127 | 128 | Then you should add a web hook in github sending deployment events to the following URL: 129 | 130 | `https:///api/v1/webhooks/github_deployment_event?st2-api-key=` 131 | 132 | By default the enviroment is set to production, you can change this in 133 | your own `github.yaml`. 134 | 135 | You can then create a deployment via ChatOps with the following 136 | command: 137 | 138 | ``` 139 | @hubot github deployment create me/my_st2_pack description Lets get the feature to production 140 | ``` 141 | 142 | ### Limitations 143 | 144 | - You need to have logged an OAuth key with StackStorm (via `github.store_oauth_token`). 145 | - If using with GitHub.com your ST2 server needs to be contactable via the internet! 146 | - Deployment Statuses will be logged as the creating user in GitHub. 147 | - With StackStorm v2.1+ you should be able to deploy tags. 148 | - Certain branch protection attributes are not supported by current Python GitHub module. 149 | -------------------------------------------------------------------------------- /actions/add_comment.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | 3 | __all__ = [ 4 | 'AddCommentAction' 5 | ] 6 | 7 | 8 | class AddCommentAction(BaseGithubAction): 9 | def run(self, user, repo, issue, body): 10 | issue = int(issue) 11 | 12 | user = self._client.get_user(user) 13 | repo = user.get_repo(repo) 14 | issue = repo.get_issue(issue) 15 | 16 | issue.create_comment(body=body) 17 | return True 18 | -------------------------------------------------------------------------------- /actions/add_comment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "add_comment" 3 | runner_type: "python-script" 4 | description: "Add a comment to the provided issue / pull request." 5 | enabled: true 6 | entry_point: "add_comment.py" 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | issue: 17 | type: "string" 18 | description: "Issue / pull request number" 19 | required: true 20 | body: 21 | type: "string" 22 | description: "Comment body" 23 | required: true 24 | -------------------------------------------------------------------------------- /actions/add_status.py: -------------------------------------------------------------------------------- 1 | from github import GithubObject 2 | 3 | from lib.base import BaseGithubAction 4 | 5 | __all__ = [ 6 | 'AddCommitStatusAction' 7 | ] 8 | 9 | 10 | class AddCommitStatusAction(BaseGithubAction): 11 | def run(self, user, repo, sha, state, target_url=None, description=None, context=None): 12 | target_url = target_url or GithubObject.NotSet 13 | description = description or GithubObject.NotSet 14 | context = context or GithubObject.NotSet 15 | 16 | user = self._client.get_user(user) 17 | repo = user.get_repo(repo) 18 | commit = repo.get_commit(sha) 19 | 20 | commit.create_status(state=state, target_url=target_url, 21 | description=description, context=context) 22 | return True 23 | -------------------------------------------------------------------------------- /actions/add_status.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "add_status" 3 | runner_type: "python-script" 4 | description: "Add a commit status for a provided ref." 5 | enabled: true 6 | entry_point: "add_status.py" 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | sha: 17 | type: "string" 18 | description: "Commit SHA" 19 | required: true 20 | state: 21 | type: "string" 22 | description: "State of the status (pending, success, error, failure)" 23 | required: true 24 | target_url: 25 | type: "string" 26 | description: "The target URL to associate with this status." 27 | required: false 28 | description: 29 | type: "string" 30 | description: "A short description of the status." 31 | required: false 32 | context: 33 | type: "string" 34 | description: "A string label to differentiate this status from the status of other systems. Default: \"default\"" 35 | required: false 36 | -------------------------------------------------------------------------------- /actions/add_team_membership.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from lib.formatters import user_to_dict 3 | 4 | __all__ = [ 5 | 'AddTeamMembershipAction' 6 | ] 7 | 8 | 9 | class AddTeamMembershipAction(BaseGithubAction): 10 | def run(self, organization, team_id, user): 11 | user = self._client.get_user(user) 12 | organization = self._client.get_organization(organization) 13 | team = organization.get_team(team_id) 14 | team.add_membership(member=user) 15 | result = user_to_dict(user=user) 16 | return result 17 | -------------------------------------------------------------------------------- /actions/add_team_membership.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: add_team_membership 3 | runner_type: python-script 4 | description: Add (and invite if not a member) a user to a team 5 | enabled: true 6 | entry_point: add_team_membership.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User to invite" 11 | required: true 12 | organization: 13 | type: "string" 14 | description: "Organization the team belongs to" 15 | required: true 16 | team_id: 17 | type: "integer" 18 | description: "Team ID." 19 | required: true 20 | -------------------------------------------------------------------------------- /actions/check_deployment_env.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | from lib.base import BaseGithubAction 16 | 17 | 18 | class CheckDeploymentEnvAction(BaseGithubAction): 19 | def run(self, deploy_env): 20 | 21 | if deploy_env == self.config['deployment_environment']: 22 | return True 23 | else: 24 | raise ValueError( 25 | "No deployment, my env is '{}' and event for '{}'".format( 26 | deploy_env, 27 | self.config['deployment_environment'])) 28 | -------------------------------------------------------------------------------- /actions/check_deployment_env.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "check_deployment_env" 3 | runner_type: "python-script" 4 | description: "Check if deployment event applies to this server." 5 | enabled: true 6 | entry_point: "check_deployment_env.py" 7 | parameters: 8 | deploy_env: 9 | type: "string" 10 | description: "The deployement enviroment to check against the config value." 11 | required: true 12 | -------------------------------------------------------------------------------- /actions/create_deployment.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | import time 16 | import datetime 17 | 18 | from lib.base import BaseGithubAction 19 | 20 | 21 | class CreateDeploymentAction(BaseGithubAction): 22 | def run(self, api_user, repository, description, payload, github_type, 23 | ref="master", environment="production", task="deploy"): 24 | 25 | enterprise = self._is_enterprise(github_type) 26 | 27 | if api_user: 28 | self.token = self._get_user_token(api_user, 29 | enterprise) 30 | 31 | payload = {"ref": ref, 32 | "task": task, 33 | "payload": payload, 34 | "environment": environment, 35 | "description": description} 36 | 37 | response = self._request("POST", 38 | "/repos/{}/deployments".format(repository), 39 | payload, 40 | self.token, 41 | enterprise) 42 | 43 | ts_created_at = time.mktime( 44 | datetime.datetime.strptime( 45 | response['created_at'], 46 | "%Y-%m-%dT%H:%M:%SZ").timetuple()) 47 | 48 | results = {'creator': response['creator']['login'], 49 | 'id': response['id'], 50 | 'sha': response['sha'], 51 | 'url': response['url'], 52 | 'ref': response['ref'], 53 | 'task': response['task'], 54 | 'payload': response['payload'], 55 | 'environment': response['environment'], 56 | 'description': response['description'], 57 | 'statuses_url': response['statuses_url'], 58 | 'repository_url': response['repository_url'], 59 | 'created_at': response['created_at'], 60 | 'updated_at': response['updated_at'], 61 | 'ts_created_at': ts_created_at} 62 | results['response'] = response 63 | 64 | return results 65 | -------------------------------------------------------------------------------- /actions/create_deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "create_deployment" 3 | runner_type: "python-script" 4 | description: "Create a new deployment for a GitHub repository" 5 | enabled: true 6 | entry_point: "create_deployment.py" 7 | parameters: 8 | api_user: 9 | type: "string" 10 | description: "The API user" 11 | default: "{{action_context.api_user|default(None)}}" 12 | repository: 13 | type: "string" 14 | description: "The full (Organization|User)/repository path" 15 | required: true 16 | ref: 17 | type: "string" 18 | description: "The branch, tag, or SHA to deploy." 19 | default: "master" 20 | environment: 21 | type: "string" 22 | description: "Deploy to this environment." 23 | default: "production" 24 | description: 25 | type: "string" 26 | description: "Optional short description." 27 | default: "" 28 | payload: 29 | type: "string" 30 | description: "Optional JSON payload with extra information about the deployment." 31 | default: "" 32 | github_type: 33 | type: "string" 34 | description: "The type of github installation to target, if unset will use the configured default." 35 | default: ~ 36 | -------------------------------------------------------------------------------- /actions/create_deployment_status.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | import time 16 | import datetime 17 | 18 | from lib.base import BaseGithubAction 19 | 20 | 21 | class CreateDeploymentAction(BaseGithubAction): 22 | def run(self, api_user, repository, deployment_id, state, 23 | description, github_type): 24 | 25 | valid_states = ['pending', 'success', 'error', 'failure'] 26 | 27 | enterprise = self._is_enterprise(github_type) 28 | 29 | if api_user: 30 | self.token = self._get_user_token(api_user, 31 | enterprise) 32 | 33 | if state not in valid_states: 34 | raise ValueError("Invalid state: {}".format(state)) 35 | 36 | payload = {"state": state, 37 | "description": description} 38 | 39 | response = self._request("POST", 40 | "/repos/{}/deployments/{}/statuses".format( 41 | repository, 42 | deployment_id), 43 | payload, 44 | self.token, 45 | enterprise) 46 | 47 | ts_created_at = time.mktime( 48 | datetime.datetime.strptime( 49 | response['created_at'], 50 | "%Y-%m-%dT%H:%M:%SZ").timetuple()) 51 | 52 | ts_updated_at = time.mktime( 53 | datetime.datetime.strptime( 54 | response['updated_at'], 55 | "%Y-%m-%dT%H:%M:%SZ").timetuple()) 56 | 57 | results = {'creator': response['creator']['login'], 58 | 'id': response['id'], 59 | 'url': response['url'], 60 | 'description': response['description'], 61 | 'repository_url': response['repository_url'], 62 | 'created_at': response['created_at'], 63 | 'updated_at': response['updated_at'], 64 | 'ts_created_at': ts_created_at, 65 | 'ts_updated_at': ts_updated_at} 66 | results['response'] = response 67 | 68 | return results 69 | -------------------------------------------------------------------------------- /actions/create_deployment_status.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "create_deployment_status" 3 | runner_type: "python-script" 4 | description: "Create a new deployment Status for a GitHub repository" 5 | enabled: true 6 | entry_point: "create_deployment_status.py" 7 | parameters: 8 | api_user: 9 | type: "string" 10 | description: "The API user" 11 | default: "{{action_context.api_user|default(None)}}" 12 | repository: 13 | type: "string" 14 | description: "The full (Organization|User)/repository path" 15 | required: true 16 | deployment_id: 17 | type: "integer" 18 | description: "The ID of the deployment." 19 | required: true 20 | state: 21 | type: "string" 22 | description: "The state of the status." 23 | enum: 24 | - "pending" 25 | - "success" 26 | - "error" 27 | - "failure" 28 | required: true 29 | description: 30 | type: "string" 31 | description: "A short description of the status. Maximum length of 140 characters." 32 | default: "" 33 | github_type: 34 | type: "string" 35 | description: "The type of github installation to target, if unset will use the configured default." 36 | default: ~ 37 | -------------------------------------------------------------------------------- /actions/create_file.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | from lib.base import BaseGithubAction 4 | from lib.formatters import file_response_to_dict 5 | from lib.utils import prep_github_params_for_file_ops 6 | 7 | __all__ = [ 8 | 'CreateFileAction' 9 | ] 10 | 11 | 12 | class CreateFileAction(BaseGithubAction): 13 | def run(self, user, repo, path, message, content, branch=None, committer=None, author=None, 14 | encoding=None): 15 | author, branch, committer = prep_github_params_for_file_ops(author, branch, committer) 16 | 17 | if encoding and encoding == 'base64': 18 | content = base64.b64encode(content.encode('utf-8')) 19 | 20 | user = self._client.get_user(user) 21 | repo = user.get_repo(repo) 22 | api_response = repo.create_file(path=path, message=message, content=content, branch=branch, 23 | committer=committer, author=author) 24 | result = file_response_to_dict(api_response) 25 | return result 26 | 27 | 28 | if __name__ == '__main__': 29 | import os 30 | GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN') 31 | GITHUB_ORG = os.environ.get('GITHUB_ORG') 32 | GITHUB_REPO = os.environ.get('GITHUB_REPO') 33 | COMMITTER = os.environ.get('COMMITTER', None) 34 | AUTHOR = os.environ.get('AUTHOR', None) 35 | 36 | act = CreateFileAction(config={'token': GITHUB_TOKEN, 'github_type': 'online'}) 37 | res = act.run(user=GITHUB_ORG, repo=GITHUB_REPO, path='README5.md', message='Test commit', 38 | content='Super duper read me file, pushed from Stackstorm github pack!\n', 39 | branch='branch1', committer=COMMITTER, author=AUTHOR) 40 | import pprint 41 | pp = pprint.PrettyPrinter(indent=4) 42 | pp.pprint(res) 43 | -------------------------------------------------------------------------------- /actions/create_file.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: create_file 3 | runner_type: python-script 4 | description: Create a file in this repository. 5 | enabled: true 6 | entry_point: create_file.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | path: 17 | type: "string" 18 | description: "Path of the file in the repository." 19 | required: true 20 | message: 21 | type: "string" 22 | description: "Commit message." 23 | required: true 24 | content: 25 | type: "string" 26 | description: "The actual data in the file." 27 | required: true 28 | encoding: 29 | type: "string" 30 | description: "Specify if the input content is encoded." 31 | enum: 32 | - "none" 33 | - "base64" 34 | default: "none" 35 | required: false 36 | branch: 37 | type: "string" 38 | description: "Branch to create the commit on. Defaults to the default branch of the repository" 39 | required: false 40 | committer: 41 | type: "string" 42 | description: "If no information is given the authenticated user's information will be used. You must specify both a name and email. Expected format: FirstName LastName " 43 | required: false 44 | author: 45 | type: "string" 46 | description: "If omitted this will be filled in with committer information. If passed, you must specify both a name and email. Expected format: FirstName LastName " 47 | required: false 48 | -------------------------------------------------------------------------------- /actions/create_issue.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from github import GithubObject 3 | from lib.formatters import issue_to_dict 4 | 5 | __all__ = [ 6 | 'CreateIssueAction' 7 | ] 8 | 9 | 10 | class CreateIssueAction(BaseGithubAction): 11 | def run(self, user, repo, title, description=None, assignee=None): 12 | user = self._client.get_user(user) 13 | repo = user.get_repo(repo) 14 | issue = repo.create_issue(title, description or GithubObject.NotSet, 15 | assignee or GithubObject.NotSet) 16 | result = issue_to_dict(issue=issue) 17 | return result 18 | -------------------------------------------------------------------------------- /actions/create_issue.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: create_issue 3 | runner_type: python-script 4 | description: Create a Github issue. 5 | enabled: true 6 | entry_point: create_issue.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | title: 17 | type: "string" 18 | description: Title 19 | required: true 20 | description: 21 | type: "string" 22 | description: Description 23 | required: false 24 | assignee: 25 | type: "string" 26 | description: Assignee 27 | required: false 28 | -------------------------------------------------------------------------------- /actions/create_pull.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from lib.formatters import pull_to_dict 3 | 4 | __all__ = [ 5 | 'CreatePullAction' 6 | ] 7 | 8 | 9 | class CreatePullAction(BaseGithubAction): 10 | def run(self, user, repo, title, body, head, base): 11 | user = self._client.get_user(user) 12 | repo = user.get_repo(repo) 13 | pull = repo.create_pull(title=title, body=body, head=head, base=base) 14 | result = pull_to_dict(pull=pull) 15 | return result 16 | -------------------------------------------------------------------------------- /actions/create_pull.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: create_pull 3 | runner_type: python-script 4 | description: > 5 | Creates a Github pull request. 6 | Example: 7 | st2 run github.create_pull user=user_or_org repo=myreponame title="test github.create_pull" body="test" head=feature/xyz base=master 8 | enabled: true 9 | entry_point: create_pull.py 10 | parameters: 11 | user: 12 | type: "string" 13 | description: "User / organization name." 14 | required: true 15 | repo: 16 | type: "string" 17 | description: "Repository name." 18 | required: true 19 | title: 20 | type: "string" 21 | description: "Title of the Pull Request" 22 | required: true 23 | body: 24 | type: "string" 25 | description: "The contents of the pull request." 26 | required: true 27 | head: 28 | type: "string" 29 | description: "The name of the branch where your changes are implemented. For cross-repository pull requests in the same network, namespace head with a user like this: username:branch." 30 | required: true 31 | base: 32 | type: "string" 33 | description: "The name of the branch you want the changes pulled into. This should be an existing branch on the current repository. You cannot submit a pull request to one repository that requests a merge to a base of another repository." 34 | required: true 35 | -------------------------------------------------------------------------------- /actions/create_release.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | import datetime 4 | 5 | from lib.base import BaseGithubAction 6 | 7 | 8 | class CreateReleaseAction(BaseGithubAction): 9 | def run(self, api_user, repository, name, body, github_type, 10 | target_commitish="master", version_increase="patch", 11 | draft=False, prerelease=False): 12 | 13 | enterprise = self._is_enterprise(github_type) 14 | 15 | if api_user: 16 | self.token = self._get_user_token(api_user, 17 | enterprise) 18 | 19 | release = self._request("GET", 20 | "/repos/{}/releases/latest".format(repository), 21 | None, 22 | self.token, 23 | enterprise) 24 | 25 | (major, minor, patch) = release['tag_name'].split(".") 26 | major = int(major.replace("v", "")) 27 | minor = int(minor) 28 | patch = int(patch) 29 | 30 | if version_increase == "major": 31 | major += 1 32 | minor = 0 33 | patch = 0 34 | elif version_increase == "minor": 35 | minor += 1 36 | patch = 0 37 | elif version_increase == "patch": 38 | patch += 1 39 | 40 | tag_name = "v{}.{}.{}".format(major, 41 | minor, 42 | patch) 43 | 44 | payload = {"tag_name": tag_name, 45 | "target_commitish": target_commitish, 46 | "name": name, 47 | "body": body, 48 | "draft": draft, 49 | "prerelease": prerelease} 50 | 51 | release = self._request("POST", 52 | "/repos/{}/releases".format(repository), 53 | payload, 54 | self.token, 55 | enterprise) 56 | 57 | ts_published_at = time.mktime( 58 | datetime.datetime.strptime( 59 | release['published_at'], 60 | "%Y-%m-%dT%H:%M:%SZ").timetuple()) 61 | 62 | results = {'author': release['author']['login'], 63 | 'html_url': release['html_url'], 64 | 'tag_name': release['tag_name'], 65 | 'target_commitish': release['target_commitish'], 66 | 'name': release['name'], 67 | 'body': release['body'], 68 | 'draft': release['draft'], 69 | 'prerelease': release['prerelease'], 70 | 'created_at': release['created_at'], 71 | 'published_at': release['published_at'], 72 | 'ts_published_at': ts_published_at, 73 | 'total_assets': len(release['assets'])} 74 | results['response'] = release 75 | 76 | return results 77 | -------------------------------------------------------------------------------- /actions/create_release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "create_release" 3 | runner_type: "python-script" 4 | description: "Create a new release for a GitHub repository" 5 | enabled: true 6 | entry_point: "create_release.py" 7 | parameters: 8 | api_user: 9 | type: "string" 10 | description: "The API user" 11 | default: "{{action_context.api_user|default(None)}}" 12 | repository: 13 | type: "string" 14 | description: "The full (Organization|User)/repository path" 15 | required: true 16 | name: 17 | type: "string" 18 | description: "The name of the tag." 19 | required: true 20 | target_commitish: 21 | type: "string" 22 | description: "The value that determines where the Git tag is created from (Can be any branch or commit SHA)." 23 | default: "master" 24 | body: 25 | type: "string" 26 | description: "Text describing the contents of the tag." 27 | required: true 28 | version_increase: 29 | type: "string" 30 | description: "Which part of the version number to increased from the latest tag." 31 | enum: 32 | - "major" 33 | - "minor" 34 | - "patch" 35 | default: "patch" 36 | draft: 37 | type: "boolean" 38 | description: "Create a draft (unpublished) release" 39 | default: false 40 | immutable: true 41 | prerelease: 42 | type: "boolean" 43 | description: "Identify the release as a prerelease." 44 | default: false 45 | immutable: true 46 | github_type: 47 | type: "string" 48 | description: "The type of github installation to target, if unset will use the configured default." 49 | default: ~ 50 | -------------------------------------------------------------------------------- /actions/delete_branch_protection.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | 3 | __all__ = [ 4 | 'DeleteBranchProtectionAction' 5 | ] 6 | 7 | 8 | class DeleteBranchProtectionAction(BaseGithubAction): 9 | def run(self, user, repo, branch): 10 | 11 | user = self._client.get_user(user) 12 | repo = user.get_repo(repo) 13 | branch = repo.get_branch(branch) 14 | branch.remove_protection() 15 | return True 16 | 17 | 18 | if __name__ == '__main__': 19 | import os 20 | 21 | GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN') 22 | GITHUB_ORG = os.environ.get('GITHUB_ORG') 23 | GITHUB_REPO = os.environ.get('GITHUB_REPO') 24 | GITHUB_BRANCH = os.environ.get('GITHUB_BRANCH') 25 | 26 | act = DeleteBranchProtectionAction(config={'token': GITHUB_TOKEN, 'github_type': 'online'}) 27 | res = act.run(user=GITHUB_ORG, repo=GITHUB_REPO, branch=GITHUB_BRANCH) 28 | import pprint 29 | 30 | pp = pprint.PrettyPrinter(indent=4) 31 | pp.pprint(res) 32 | -------------------------------------------------------------------------------- /actions/delete_branch_protection.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: delete_branch_protection 3 | runner_type: python-script 4 | description: Deletes branch protection. 5 | enabled: true 6 | entry_point: delete_branch_protection.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | branch: 17 | type: "string" 18 | description: "Branch name." 19 | required: false 20 | default: "master" 21 | -------------------------------------------------------------------------------- /actions/deployment_event.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: deployment_event 3 | description: Process an github deployment event and install a pack if the environment matches. 4 | enabled: true 5 | entry_point: workflows/deployment_event.yaml 6 | 7 | runner_type: orquesta 8 | 9 | parameters: 10 | repo_fullname: 11 | type: string 12 | description: The full repo path (e.g. [Org|User]/repo_name 13 | required: true 14 | repo_name: 15 | type: string 16 | description: The repo name. 17 | required: true 18 | deploy_ref: 19 | type: string 20 | description: 'The branch to deploy (Note: tags are not supported!)' 21 | default: master 22 | deploy_env: 23 | type: string 24 | description: The environment to target. 25 | default: production 26 | deploy_sha: 27 | type: string 28 | description: The SHA of the commit to deploy. 29 | required: true 30 | deploy_desc: 31 | type: string 32 | description: The description of the deployment. 33 | required: true 34 | deploy_id: 35 | type: integer 36 | description: The deployment ID. 37 | required: true 38 | ssh_url: 39 | type: string 40 | description: The location of the repo for using with SSH. 41 | required: true 42 | creator: 43 | type: string 44 | description: Who created the deployment (will be used to update the deployment status. 45 | required: true 46 | deploy_payload: 47 | type: string 48 | description: Additional payload information from GitHub 49 | default: '{}' 50 | -------------------------------------------------------------------------------- /actions/get_branch_protection.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from lib.formatters import branch_protection_to_dict 3 | 4 | __all__ = [ 5 | 'GetBranchProtectionAction' 6 | ] 7 | 8 | 9 | class GetBranchProtectionAction(BaseGithubAction): 10 | def run(self, user, repo, branch): 11 | 12 | user = self._client.get_user(user) 13 | repo = user.get_repo(repo) 14 | branch = repo.get_branch(branch) 15 | branch_protection = branch.get_protection() 16 | result = branch_protection_to_dict(branch_protection) 17 | return result 18 | 19 | 20 | if __name__ == '__main__': 21 | import os 22 | GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN') 23 | GITHUB_ORG = os.environ.get('GITHUB_ORG') 24 | GITHUB_REPO = os.environ.get('GITHUB_REPO') 25 | GITHUB_BRANCH = os.environ.get('GITHUB_BRANCH') 26 | 27 | act = GetBranchProtectionAction(config={'token': GITHUB_TOKEN, 'github_type': 'online'}) 28 | res = act.run(user=GITHUB_ORG, repo=GITHUB_REPO, branch=GITHUB_BRANCH) 29 | import pprint 30 | pp = pprint.PrettyPrinter(indent=4) 31 | pp.pprint(res) 32 | -------------------------------------------------------------------------------- /actions/get_branch_protection.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: get_branch_protection 3 | runner_type: python-script 4 | description: Gets branch protection details for given repo and branch. 5 | enabled: true 6 | entry_point: get_branch_protection.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | branch: 17 | type: "string" 18 | description: "Branch name." 19 | required: false 20 | default: "master" 21 | -------------------------------------------------------------------------------- /actions/get_clone_stats.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | 3 | __all__ = [ 4 | 'GetCloneStatsAction' 5 | ] 6 | 7 | 8 | class GetCloneStatsAction(BaseGithubAction): 9 | def run(self, repo, github_type): 10 | clone_data = self._get_analytics( 11 | category='clone-activity-data', repo=repo, enterprise=self._is_enterprise(github_type)) 12 | return clone_data['summary'] 13 | -------------------------------------------------------------------------------- /actions/get_clone_stats.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: get_clone_stats 3 | runner_type: python-script 4 | description: Retrieve clone statistics for a given repository 5 | enabled: true 6 | entry_point: get_clone_stats.py 7 | parameters: 8 | repo: 9 | type: string 10 | description: "Repository to query for clone stats (org/repo)" 11 | required: true 12 | github_type: 13 | type: "string" 14 | description: "The type of github installation to target, if unset will use the configured default." 15 | default: ~ 16 | -------------------------------------------------------------------------------- /actions/get_contents.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from lib.formatters import contents_to_dict 3 | 4 | __all__ = [ 5 | 'GetContentsAction' 6 | ] 7 | 8 | 9 | class GetContentsAction(BaseGithubAction): 10 | def run(self, user, repo, ref, path, decode=False): 11 | 12 | user = self._client.get_user(user) 13 | repo = user.get_repo(repo) 14 | contents = repo.get_contents(path, ref=ref) 15 | result = contents_to_dict(contents=contents, decode=decode) 16 | return result 17 | 18 | 19 | if __name__ == '__main__': 20 | import os 21 | GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN') 22 | GITHUB_ORG = os.environ.get('GITHUB_ORG') 23 | GITHUB_REPO = os.environ.get('GITHUB_REPO') 24 | 25 | act = GetContentsAction(config={'token': GITHUB_TOKEN, 'github_type': 'online'}) 26 | res = act.run(user=GITHUB_ORG, repo=GITHUB_REPO, ref='branch1', path='README.md', decode=True) 27 | import pprint 28 | pp = pprint.PrettyPrinter(indent=4) 29 | pp.pprint(res) 30 | -------------------------------------------------------------------------------- /actions/get_contents.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: get_contents 3 | runner_type: python-script 4 | description: Gets the contents of a file or directory in a repository. 5 | enabled: true 6 | entry_point: get_contents.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | ref: 17 | type: "string" 18 | description: "Git ref." 19 | required: false 20 | default: "HEAD" 21 | path: 22 | type: "string" 23 | description: "Path to file." 24 | required: true 25 | decode: 26 | type: boolean 27 | description: "Decode content." 28 | required: false 29 | default: false 30 | -------------------------------------------------------------------------------- /actions/get_deployment_statuses.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | import time 16 | import datetime 17 | 18 | from lib.base import BaseGithubAction 19 | 20 | 21 | class GetDeploymentStatusesAction(BaseGithubAction): 22 | def run(self, api_user, repository, deployment_id, github_type): 23 | 24 | enterprise = self._is_enterprise(github_type) 25 | 26 | if api_user: 27 | self.token = self._get_user_token(api_user, enterprise) 28 | 29 | payload = {"id": deployment_id} 30 | 31 | responses = self._request("GET", 32 | "/repos/{}/deployments/{}/statuses".format( 33 | repository, deployment_id), 34 | payload, 35 | self.token, 36 | enterprise) 37 | 38 | results = [] 39 | for response in responses: 40 | ts_created_at = time.mktime( 41 | datetime.datetime.strptime( 42 | response['created_at'], 43 | "%Y-%m-%dT%H:%M:%SZ").timetuple()) 44 | 45 | results.append({'creator': response['creator']['login'], 46 | 'id': response['id'], 47 | 'description': response['description'], 48 | 'state': response['state'], 49 | 'target_url': response['target_url'], 50 | 'created_at': response['created_at'], 51 | 'updated_at': response['updated_at'], 52 | 'ts_created_at': ts_created_at}) 53 | 54 | return results 55 | -------------------------------------------------------------------------------- /actions/get_deployment_statuses.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "get_deployment_statuses" 3 | runner_type: "python-script" 4 | description: "Get the statuses of a deployment for a GitHub repository" 5 | enabled: true 6 | entry_point: "get_deployment_statuses.py" 7 | parameters: 8 | api_user: 9 | type: "string" 10 | description: "The API user" 11 | default: "{{action_context.api_user|default(None)}}" 12 | repository: 13 | type: "string" 14 | description: "The full (Organization|User)/repository path" 15 | required: true 16 | deployment_id: 17 | type: "integer" 18 | description: "The ID of the deloyment to get the states for." 19 | required: true 20 | github_type: 21 | type: "string" 22 | description: "The type of github installation to target, if unset will use the configured default." 23 | default: ~ 24 | -------------------------------------------------------------------------------- /actions/get_issue.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from lib.formatters import issue_to_dict 3 | 4 | __all__ = [ 5 | 'GetIssueAction' 6 | ] 7 | 8 | 9 | class GetIssueAction(BaseGithubAction): 10 | def run(self, user, repo, issue_id): 11 | issue_id = int(issue_id) 12 | 13 | user = self._client.get_user(user) 14 | repo = user.get_repo(repo) 15 | issue = repo.get_issue(issue_id) 16 | result = issue_to_dict(issue=issue) 17 | return result 18 | -------------------------------------------------------------------------------- /actions/get_issue.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: get_issue 3 | runner_type: python-script 4 | description: Retrieve information about a particular Github issue. 5 | enabled: true 6 | entry_point: get_issue.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | issue_id: 17 | type: "string" 18 | description: Issue id 19 | required: true 20 | -------------------------------------------------------------------------------- /actions/get_pull.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from lib.formatters import pull_to_dict 3 | 4 | __all__ = [ 5 | 'GetPullAction' 6 | ] 7 | 8 | 9 | class GetPullAction(BaseGithubAction): 10 | def run(self, user, repo, pull_id): 11 | issue_id = int(pull_id) 12 | 13 | user = self._client.get_user(user) 14 | repo = user.get_repo(repo) 15 | pull = repo.get_pull(issue_id) 16 | result = pull_to_dict(pull=pull) 17 | return result 18 | -------------------------------------------------------------------------------- /actions/get_pull.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: get_pull 3 | runner_type: python-script 4 | description: Retrieve information about a particular Github pull request. 5 | enabled: true 6 | entry_point: get_pull.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | pull_id: 17 | type: "string" 18 | description: Pull request id 19 | required: true 20 | -------------------------------------------------------------------------------- /actions/get_traffic_stats.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | 3 | __all__ = [ 4 | 'GetTrafficStatsAction' 5 | ] 6 | 7 | 8 | class GetTrafficStatsAction(BaseGithubAction): 9 | def run(self, repo, github_type): 10 | traffic_data = self._get_analytics( 11 | category='traffic-data', repo=repo, enterprise=self._is_enterprise(github_type)) 12 | return traffic_data['summary'] 13 | -------------------------------------------------------------------------------- /actions/get_traffic_stats.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: get_traffic_stats 3 | runner_type: python-script 4 | description: Retrieve traffic statistics for a given repository 5 | enabled: true 6 | entry_point: get_traffic_stats.py 7 | parameters: 8 | repo: 9 | type: string 10 | description: "Repository to query for traffic stats (org/repo)" 11 | required: true 12 | github_type: 13 | type: "string" 14 | description: "The type of github installation to target, if unset will use the configured default." 15 | default: ~ 16 | -------------------------------------------------------------------------------- /actions/get_user.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from lib.formatters import user_to_dict 3 | 4 | __all__ = [ 5 | 'GetUserAction' 6 | ] 7 | 8 | 9 | class GetUserAction(BaseGithubAction): 10 | def run(self, user, token_user, github_type): 11 | enterprise = self._is_enterprise(github_type) 12 | if token_user: 13 | self._change_to_user_token(token_user, enterprise) 14 | 15 | user = self._client.get_user(user) 16 | result = user_to_dict(user=user) 17 | return result 18 | -------------------------------------------------------------------------------- /actions/get_user.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: get_user 3 | runner_type: python-script 4 | description: Get a user from the Github user database 5 | enabled: true 6 | entry_point: get_user.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "The username" 11 | required: true 12 | token_user: 13 | type: "string" 14 | description: "The" 15 | default: "{{action_context.api_user|default()}}" 16 | github_type: 17 | type: "string" 18 | description: "The type of github installation to target, if unset will use the configured default." 19 | default: ~ 20 | -------------------------------------------------------------------------------- /actions/latest_release.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | import time 16 | import datetime 17 | 18 | from lib.base import BaseGithubAction 19 | 20 | 21 | class LatestReleaseAction(BaseGithubAction): 22 | def run(self, api_user, repository, github_type): 23 | 24 | enterprise = self._is_enterprise(github_type) 25 | 26 | if api_user: 27 | self.token = self._get_user_token(api_user, enterprise) 28 | 29 | release = self._request("GET", 30 | "/repos/{}/releases/latest".format(repository), 31 | None, 32 | token=self.token, 33 | enterprise=enterprise) 34 | 35 | ts_published_at = time.mktime( 36 | datetime.datetime.strptime( 37 | release['published_at'], 38 | "%Y-%m-%dT%H:%M:%SZ").timetuple()) 39 | 40 | results = {'author': release['author']['login'], 41 | 'avatar_url': release['author']['avatar_url'], 42 | 'html_url': release['html_url'], 43 | 'tag_name': release['tag_name'], 44 | 'target_commitish': release['target_commitish'], 45 | 'name': release['name'], 46 | 'body': release['body'], 47 | 'draft': release['draft'], 48 | 'prerelease': release['prerelease'], 49 | 'created_at': release['created_at'], 50 | 'published_at': release['published_at'], 51 | 'ts_published_at': ts_published_at, 52 | 'total_assets': len(release['assets'])} 53 | 54 | return results 55 | -------------------------------------------------------------------------------- /actions/latest_release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "latest_release" 3 | runner_type: "python-script" 4 | description: "List the latest release for a GitHub repository" 5 | enabled: true 6 | entry_point: "latest_release.py" 7 | parameters: 8 | api_user: 9 | type: "string" 10 | description: "The API user" 11 | default: "{{action_context.api_user|default(None)}}" 12 | repository: 13 | type: "string" 14 | description: "The full (Organization|User)/repository path" 15 | required: true 16 | github_type: 17 | type: "string" 18 | description: "The type of github installation to target, if unset will use the configured default." 19 | default: ~ 20 | -------------------------------------------------------------------------------- /actions/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackStorm-Exchange/stackstorm-github/fc8e45bd25bcfdd891f3e88e0b87f7592fe7f634/actions/lib/__init__.py -------------------------------------------------------------------------------- /actions/lib/base.py: -------------------------------------------------------------------------------- 1 | from github import Github 2 | import requests 3 | from bs4 import BeautifulSoup 4 | import json 5 | 6 | from st2common.runners.base_action import Action 7 | 8 | __all__ = [ 9 | 'BaseGithubAction' 10 | ] 11 | 12 | # Default Github web URL (used by tasks which directly scrape data from HTML) 13 | # pages 14 | DEFAULT_WEB_URL = 'https://github.com' 15 | 16 | # Default Github API url 17 | DEFAULT_API_URL = 'https://api.github.com' 18 | 19 | 20 | class BaseGithubAction(Action): 21 | def run(self, **kwargs): 22 | pass 23 | 24 | def __init__(self, config): 25 | super(BaseGithubAction, self).__init__(config=config) 26 | token = self.config.get('token', None) 27 | self.token = token or None 28 | 29 | self.web_url = self.config.get('web_url', None) 30 | self.base_url = self.config.get('base_url', None) 31 | 32 | self.default_github_type = self.config.get('github_type', None) 33 | 34 | if self.default_github_type == 'online': 35 | self._client = Github(self.token, base_url=DEFAULT_API_URL) 36 | else: 37 | self._client = Github(self.token, base_url=self.base_url) 38 | 39 | self._session = requests.Session() 40 | 41 | def _web_session(self, web_url=DEFAULT_WEB_URL): 42 | """Returns a requests session to scrape off the web""" 43 | login_url = web_url + '/login' 44 | session = requests.Session() 45 | request = session.get(login_url).text 46 | html = BeautifulSoup(request) 47 | token = html.find('input', {'name': 'authenticity_token'}).attrs['value'] 48 | commit_value = html.find('input', {'name': 'commit'}).attrs['value'] 49 | session_path = html.find('form', {'method': 'post'}).attrs['action'] 50 | 51 | login_data = { 52 | 'login': self.config['user'], 53 | 'password': self.config['password'], 54 | 'commit': commit_value, 55 | 'authenticity_token': token 56 | } 57 | 58 | session_url = web_url + session_path 59 | session.post(session_url, data=login_data) 60 | return session 61 | 62 | def _get_analytics(self, category, repo, enterprise): 63 | if enterprise: 64 | url = self.web_url + repo + '/graphs/' + category + '.json' 65 | s = self._web_session(self.web_url) 66 | else: 67 | url = DEFAULT_WEB_URL + repo + '/graphs/' + category + '.json' 68 | s = self._web_session() 69 | 70 | response = s.get(url) 71 | return response.json() 72 | 73 | def _is_enterprise(self, github_type): 74 | 75 | if github_type == "enterprise": 76 | return True 77 | elif github_type == "online": 78 | return False 79 | elif self.default_github_type == "enterprise": 80 | return True 81 | elif self.default_github_type == "online": 82 | return False 83 | else: 84 | raise ValueError("Default GitHub Invalid!") 85 | 86 | def _get_user_token(self, user, enterprise): 87 | """ 88 | Return a users GitHub OAuth Token, if it fails replace '-' 89 | with '.' as '.' is not valid for GitHub names. 90 | """ 91 | 92 | if enterprise: 93 | token_name = "token_enterprise_" 94 | else: 95 | token_name = "token_" 96 | 97 | token = self.action_service.get_value(token_name + user) 98 | 99 | # if a token is not returned, try using reversing changes made by 100 | # GitHub Enterprise during LDAP sync'ing. 101 | if token is None: 102 | token = self.action_service.get_value( 103 | token_name + user.replace("-", ".")) 104 | 105 | return token 106 | 107 | def _change_to_user_token(self, user, enterprise): 108 | token = self._get_user_token(user, enterprise) 109 | 110 | if enterprise: 111 | self._client = Github(token, base_url=self.base_url) 112 | else: 113 | self._client = Github(token, base_url=DEFAULT_API_URL) 114 | 115 | return True 116 | 117 | def _request(self, method, uri, payload, token, enterprise): 118 | headers = {'Authorization': 'token {}'.format(token)} 119 | 120 | if enterprise: 121 | url = "{}{}".format(self.base_url, uri) 122 | else: 123 | url = "{}{}".format(DEFAULT_API_URL, uri) 124 | 125 | r = None 126 | try: 127 | r = self._session.request(method, 128 | url, 129 | data=json.dumps(payload), 130 | headers=headers, 131 | verify=False) 132 | r.raise_for_status() 133 | except requests.exceptions.HTTPError: 134 | raise Exception( 135 | "ERROR: '{}'ing to '{}' - status code: {} payload: {}".format( 136 | method, url, r.status_code, json.dumps(payload))) 137 | except requests.exceptions.ConnectionError as e: 138 | raise Exception("Could not connect to: {} : {}".format(url, e)) 139 | else: 140 | if r.status_code == 204: 141 | return None 142 | else: 143 | return r.json() 144 | -------------------------------------------------------------------------------- /actions/lib/formatters.py: -------------------------------------------------------------------------------- 1 | import re 2 | from lib.utils import branch_protection_attributes, required_pull_request_reviews_attributes 3 | from st2common.util import isotime 4 | 5 | __all__ = [ 6 | 'branch_protection_to_dict', 7 | 'issue_to_dict', 8 | 'pull_to_dict', 9 | 'commit_to_dict', 10 | 'label_to_dict', 11 | 'user_to_dict', 12 | 'contents_to_dict', 13 | 'file_response_to_dict', 14 | ] 15 | 16 | 17 | def branch_protection_to_dict(branch_protection): 18 | result = {} 19 | for attr in branch_protection_attributes: 20 | 21 | # skip unknown/unsupported attributes 22 | if not hasattr(branch_protection, attr): 23 | continue 24 | 25 | # special treatment for some of the attributes 26 | if attr == 'required_status_checks': 27 | req_status_checks = branch_protection.required_status_checks 28 | if req_status_checks: 29 | result[attr] = {'contexts': req_status_checks.contexts, 30 | 'strict': req_status_checks.strict} 31 | else: 32 | result[attr] = None 33 | elif attr == 'required_pull_request_reviews': 34 | req_pr_reviews = branch_protection.required_pull_request_reviews 35 | if req_pr_reviews: 36 | result[attr] = {} 37 | for attr2 in required_pull_request_reviews_attributes: 38 | if attr2 == 'dismissal_users': 39 | if req_pr_reviews.dismissal_users is not None: 40 | users = [user.login for user in req_pr_reviews.dismissal_users] 41 | else: 42 | users = None 43 | result[attr][attr2] = users 44 | elif attr2 == 'dismissal_teams': 45 | if req_pr_reviews.dismissal_teams is not None: 46 | teams = [team.slug for team in req_pr_reviews.dismissal_teams] 47 | else: 48 | teams = None 49 | result[attr][attr2] = teams 50 | else: 51 | result[attr][attr2] = getattr(req_pr_reviews, attr2) 52 | else: 53 | result[attr] = None 54 | else: 55 | result[attr] = getattr(branch_protection, attr) 56 | 57 | return result 58 | 59 | 60 | def issue_to_dict(issue): 61 | result = {} 62 | 63 | author = user_to_dict(issue.user) 64 | assignee = user_to_dict(issue.assignee) 65 | closed_by = user_to_dict(issue.closed_by) 66 | 67 | if issue.pull_request: 68 | is_pull_request = True 69 | else: 70 | is_pull_request = False 71 | 72 | result['id'] = issue.id 73 | result['repository'] = issue.repository.name 74 | result['author'] = author 75 | result['assign'] = assignee 76 | result['title'] = issue.title 77 | result['body'] = issue.body 78 | result['url'] = issue.html_url 79 | result['state'] = issue.state 80 | result['is_pull_request'] = is_pull_request 81 | 82 | if issue.labels: 83 | labels = [label_to_dict(label) for label in issue.labels] 84 | else: 85 | labels = [] 86 | 87 | result['labels'] = labels 88 | 89 | # Note: We convert it to a serialize type (string) 90 | if issue.created_at: 91 | created_at = isotime.format(issue.created_at) 92 | else: 93 | created_at = None 94 | 95 | if issue.closed_at: 96 | closed_at = isotime.format(issue.closed_at) 97 | else: 98 | closed_at = None 99 | 100 | result['created_at'] = created_at 101 | result['closed_at'] = closed_at 102 | result['closed_by'] = closed_by 103 | return result 104 | 105 | 106 | def pull_to_dict(pull): 107 | result = {} 108 | 109 | author = user_to_dict(pull.user) 110 | assignee = user_to_dict(pull.assignee) 111 | merged_by = user_to_dict(pull.merged_by) 112 | 113 | result['id'] = pull.id 114 | result['pr_id'] = int(re.sub(r'.*/([0-9]+)(#.*)?', r'\1', pull.html_url)) 115 | result['author'] = author 116 | result['assign'] = assignee 117 | result['title'] = pull.title 118 | result['body'] = pull.body 119 | result['url'] = pull.html_url 120 | result['base'] = pull.base.ref 121 | result['head'] = pull.head.ref 122 | result['state'] = pull.state 123 | result['merged'] = pull.merged 124 | # noinspection SpellCheckingInspection 125 | result['mergeable_state'] = pull.mergeable_state 126 | result['merge_commit_sha'] = pull.merge_commit_sha 127 | 128 | if pull.labels: 129 | labels = [label_to_dict(label) for label in pull.labels] 130 | else: 131 | labels = [] 132 | 133 | result['labels'] = labels 134 | 135 | if pull.get_commits(): 136 | commits = [commit_to_dict(commit) for commit in pull.get_commits()] 137 | else: 138 | commits = [] 139 | 140 | result['commits'] = commits 141 | 142 | # Note: We convert it to a serialize type (string) 143 | if pull.created_at: 144 | created_at = isotime.format(pull.created_at) 145 | else: 146 | created_at = None 147 | 148 | if pull.closed_at: 149 | closed_at = isotime.format(pull.closed_at) 150 | else: 151 | closed_at = None 152 | 153 | if pull.merged_at: 154 | merged_at = isotime.format(pull.merged_at) 155 | else: 156 | merged_at = None 157 | 158 | result['created_at'] = created_at 159 | result['closed_at'] = closed_at 160 | result['merged_at'] = merged_at 161 | result['merged_by'] = merged_by 162 | return result 163 | 164 | 165 | def commit_to_dict(commit): 166 | result = {'sha': commit.sha} 167 | return result 168 | 169 | 170 | def label_to_dict(label): 171 | result = {'name': label.name, 'color': label.color, 'url': label.url} 172 | 173 | return result 174 | 175 | 176 | def user_to_dict(user): 177 | if not user: 178 | return None 179 | 180 | result = {'name': user.name, 'login': user.login} 181 | return result 182 | 183 | 184 | def team_to_dict(team): 185 | if not team: 186 | return None 187 | 188 | result = {'id': team.id, 'name': team.name, 'members_count': team.members_count} 189 | return result 190 | 191 | 192 | def contents_to_dict(contents, decode=False): 193 | if not contents: 194 | return None 195 | 196 | directory = False 197 | if isinstance(contents, list): 198 | directory = True 199 | else: 200 | contents = [contents] 201 | 202 | result = [] 203 | data = {} 204 | 205 | for item in contents: 206 | item_type = item.type 207 | data['type'] = item_type 208 | if item_type == 'symlink': 209 | data['target'] = item.target 210 | elif item_type == 'submodule': 211 | data['submodule_git_url'] = item.submodule_git_url 212 | elif not directory: 213 | data['encoding'] = item.encoding 214 | data['content'] = item.decoded_content.decode('utf-8') if decode else item.content 215 | 216 | data['size'] = item.size 217 | data['name'] = item.name 218 | data['path'] = item.path 219 | data['sha'] = item.sha 220 | data['url'] = item.url 221 | data['git_url'] = item.git_url 222 | data['html_url'] = item.html_url 223 | data['download_url'] = item.download_url 224 | result.append(data) 225 | 226 | if not directory: 227 | return result[0] 228 | else: 229 | return result 230 | 231 | 232 | def file_response_to_dict(response): 233 | result = {'commit': response['commit'].sha} 234 | return result 235 | -------------------------------------------------------------------------------- /actions/lib/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | from github.InputGitAuthor import InputGitAuthor 3 | from github.GithubObject import NotSet 4 | 5 | __all__ = [ 6 | 'prep_github_params_for_file_ops', 'branch_protection_attributes', 7 | 'required_pull_request_reviews_attributes', 'restrictions_attributes' 8 | ] 9 | 10 | # expecting string in the format of "FirstName LastName " 11 | author_pattern = re.compile(r"^(.*?)\s+<([a-zA-Z0-9_.+-]+?@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)?>$") 12 | 13 | branch_protection_attributes = ['url', 'required_status_checks', 'enforce_admins', 14 | 'required_pull_request_reviews', 'required_linear_history', 15 | 'allow_force_pushes', 'allow_deletions'] 16 | 17 | required_pull_request_reviews_attributes = ['dismissal_teams', 'dismissal_users', 18 | 'dismiss_stale_reviews', 19 | 'require_code_owner_reviews', 20 | 'required_approving_review_count'] 21 | 22 | restrictions_attributes = ['users', 'teams', 'apps'] 23 | 24 | 25 | def create_github_author(s): 26 | match = re.match(author_pattern, s) 27 | if match: 28 | name = match.group(1) 29 | email = match.group(2) 30 | return InputGitAuthor(name=name, email=email) 31 | else: 32 | return None 33 | 34 | 35 | def prep_github_params_for_file_ops(author, branch, committer): 36 | if not branch: 37 | branch = NotSet 38 | 39 | if committer: 40 | committer = create_github_author(committer) 41 | else: 42 | committer = NotSet 43 | 44 | if author: 45 | author = create_github_author(author) 46 | else: 47 | author = NotSet 48 | 49 | return author, branch, committer 50 | -------------------------------------------------------------------------------- /actions/list_deployments.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | from lib.base import BaseGithubAction 16 | 17 | 18 | class ListDeploymentsAction(BaseGithubAction): 19 | def run(self, api_user, repository, github_type): 20 | results = [] 21 | 22 | enterprise = self._is_enterprise(github_type) 23 | 24 | if api_user: 25 | self.token = self._get_user_token(api_user, 26 | enterprise) 27 | 28 | response = self._request("GET", 29 | "/repos/{}/deployments".format(repository), 30 | None, 31 | self.token, 32 | enterprise) 33 | 34 | for dep in response: 35 | results.append( 36 | {'creator': dep['creator']['login'], 37 | 'statuses_url': dep['statuses_url'], 38 | 'repository_url': dep['repository_url'], 39 | 'ref': dep['ref'], 40 | 'task': dep['task'], 41 | 'payload': dep['payload'], 42 | 'environment': dep['environment'], 43 | 'description': dep['description'], 44 | 'created_at': dep['created_at'], 45 | 'updated_at': dep['updated_at']}) 46 | 47 | return results 48 | -------------------------------------------------------------------------------- /actions/list_deployments.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "list_deployments" 3 | runner_type: "python-script" 4 | description: "List deployments for a GitHub repository" 5 | enabled: true 6 | entry_point: "list_deployments.py" 7 | parameters: 8 | api_user: 9 | type: "string" 10 | description: "The API user" 11 | default: "{{action_context.api_user|default(None)}}" 12 | repository: 13 | type: "string" 14 | description: "The full (Organization|User)/repository path" 15 | required: true 16 | github_type: 17 | type: "string" 18 | description: "The type of github installation to target, if unset will use the configured default." 19 | default: ~ 20 | -------------------------------------------------------------------------------- /actions/list_issues.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from lib.base import BaseGithubAction 4 | from lib.formatters import issue_to_dict 5 | 6 | __all__ = [ 7 | 'ListIssuesAction' 8 | ] 9 | 10 | 11 | class ListIssuesAction(BaseGithubAction): 12 | def run(self, user, repo, filter=None, state=None, sort=None, 13 | direction=None, since=None, limit=20): 14 | user = self._client.get_user(user) 15 | repo = user.get_repo(repo) 16 | 17 | kwargs = {} 18 | if filter: 19 | kwargs['filter'] = filter 20 | if state: 21 | kwargs['state'] = state 22 | if sort: 23 | kwargs['sort'] = sort 24 | if direction: 25 | kwargs['direction'] = direction 26 | if since: 27 | kwargs['since'] = datetime.datetime.fromtimestamp(since) 28 | 29 | # Note: PyGithub library introduces an abstraction for paginated lists 30 | # which doesn't conform to Python's iterator spec so we can't use 31 | # array slicing to exhaust the list :/ 32 | issues = repo.get_issues(**kwargs) 33 | issues = list(issues) 34 | 35 | result = [] 36 | for index, issue in enumerate(issues): 37 | issue = issue_to_dict(issue=issue) 38 | result.append(issue) 39 | 40 | if (index + 1) >= limit: 41 | break 42 | 43 | return result 44 | -------------------------------------------------------------------------------- /actions/list_issues.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: list_issues 3 | runner_type: python-script 4 | description: Retrieve a list of issues (including pull requests) for a particular repository. 5 | enabled: true 6 | entry_point: list_issues.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | filter: 17 | type: "string" 18 | description: "Filter string." 19 | required: false 20 | state: 21 | type: "string" 22 | description: "State to filter on." 23 | required: false 24 | sort: 25 | type: "string" 26 | description: "Field to sort on." 27 | required: false 28 | direction: 29 | type: "string" 30 | description: "Sort direction (e.g. asc / desc)." 31 | required: false 32 | since: 33 | type: "number" 34 | description: "Only returns issues newer than this timestamp." 35 | required: false 36 | limit: 37 | type: "number" 38 | description: "Maximum number of issues to return." 39 | required: false 40 | default: 20 41 | -------------------------------------------------------------------------------- /actions/list_pulls.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from lib.base import BaseGithubAction 4 | from lib.formatters import pull_to_dict 5 | 6 | __all__ = [ 7 | 'ListPullsAction' 8 | ] 9 | 10 | 11 | class ListPullsAction(BaseGithubAction): 12 | def run(self, user, repo, filter=None, state=None, sort=None, 13 | direction=None, base=None, head=None, since=None, limit=20): 14 | user = self._client.get_user(user) 15 | repo = user.get_repo(repo) 16 | 17 | kwargs = {} 18 | if state: 19 | kwargs['state'] = state 20 | if sort: 21 | kwargs['sort'] = sort 22 | if direction: 23 | kwargs['direction'] = direction 24 | if base: 25 | kwargs['base'] = base 26 | if head: 27 | kwargs['head'] = head 28 | 29 | if filter: 30 | pattern = re.escape(filter['pattern']) 31 | 32 | # Note: PyGithub library introduces an abstraction for paginated lists 33 | # which doesn't conform to Python's iterator spec so we can't use 34 | # array slicing to exhaust the list :/ 35 | pulls = repo.get_pulls(**kwargs) 36 | pulls = list(pulls) 37 | 38 | result = [] 39 | for index, pull in enumerate(pulls): 40 | pull = pull_to_dict(pull=pull) 41 | if filter: 42 | if re.search(pattern, pull[filter['key']]): 43 | result.append(pull) 44 | if (index + 1) >= limit: 45 | break 46 | else: 47 | result.append(pull) 48 | if (index + 1) >= limit: 49 | break 50 | 51 | return result 52 | -------------------------------------------------------------------------------- /actions/list_pulls.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: list_pulls 3 | runner_type: python-script 4 | description: Retrieve a list of pull requests for a particular repository. 5 | enabled: true 6 | entry_point: list_pulls.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | state: 17 | type: "string" 18 | description: "State to filter on." 19 | required: false 20 | sort: 21 | type: "string" 22 | description: "Field to sort on." 23 | required: false 24 | direction: 25 | type: "string" 26 | description: "Sort direction (e.g. asc / desc)." 27 | required: false 28 | base: 29 | type: "string" 30 | description: "Filter based on the base branch name" 31 | required: false 32 | head: 33 | type: "string" 34 | description: "Filter based on the head branch name" 35 | required: false 36 | limit: 37 | type: "number" 38 | description: "Maximum number of PR to return." 39 | required: false 40 | default: 20 41 | filter: 42 | type: "object" 43 | required: false 44 | description: "Filter expression." 45 | properties: 46 | key: 47 | type: "string" 48 | required: true 49 | description: "The key to filter on" 50 | enum: 51 | - "title" 52 | - "author" 53 | - "url" 54 | pattern: 55 | type: "string" 56 | required: true 57 | description: "The pattern to filter with" 58 | -------------------------------------------------------------------------------- /actions/list_releases.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | from lib.base import BaseGithubAction 16 | 17 | 18 | class ListReleasesAction(BaseGithubAction): 19 | def run(self, api_user, repository, github_type): 20 | results = [] 21 | 22 | enterprise = self._is_enterprise(github_type) 23 | 24 | if api_user: 25 | self.token = self._get_user_token(api_user, enterprise) 26 | 27 | releases = self._request("GET", 28 | "/repos/{}/releases".format(repository), 29 | None, 30 | self.token, 31 | enterprise=enterprise) 32 | 33 | for release in releases: 34 | results.append( 35 | {'author': release['author']['login'], 36 | 'html_url': release['html_url'], 37 | 'tag_name': release['tag_name'], 38 | 'target_commitish': release['target_commitish'], 39 | 'name': release['name'], 40 | 'body': release['body'], 41 | 'draft': release['draft'], 42 | 'prerelease': release['prerelease'], 43 | 'created_at': release['created_at'], 44 | 'published_at': release['published_at'], 45 | 'total_assets': len(release['assets'])}) 46 | 47 | return results 48 | -------------------------------------------------------------------------------- /actions/list_releases.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "list_releases" 3 | runner_type: "python-script" 4 | description: "List releases for a GitHub repository" 5 | enabled: true 6 | entry_point: "list_releases.py" 7 | parameters: 8 | api_user: 9 | type: "string" 10 | description: "The API user" 11 | default: "{{action_context.api_user|default(None)}}" 12 | repository: 13 | type: "string" 14 | description: "The full (Organization|User)/repository path" 15 | required: true 16 | github_type: 17 | type: "string" 18 | description: "The type of github installation to target, if unset will use the configured default." 19 | default: ~ 20 | -------------------------------------------------------------------------------- /actions/list_teams.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from lib.formatters import team_to_dict 3 | 4 | __all__ = [ 5 | 'ListTeamsAction' 6 | ] 7 | 8 | 9 | class ListTeamsAction(BaseGithubAction): 10 | def run(self, organization): 11 | organization = self._client.get_organization(organization) 12 | teams = organization.get_teams() 13 | result = [] 14 | for team in teams: 15 | team = team_to_dict(team=team) 16 | result.append(team) 17 | 18 | return result 19 | -------------------------------------------------------------------------------- /actions/list_teams.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: list_teams 3 | runner_type: python-script 4 | description: List teams in organization 5 | enabled: true 6 | entry_point: list_teams.py 7 | parameters: 8 | organization: 9 | type: string 10 | description: "The organization to list teams for" 11 | required: true 12 | -------------------------------------------------------------------------------- /actions/merge_pull.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | 3 | __all__ = [ 4 | 'MergePullAction' 5 | ] 6 | 7 | 8 | class MergePullAction(BaseGithubAction): 9 | def run(self, user, repo, pull_id): 10 | 11 | user = self._client.get_user(user) 12 | repo = user.get_repo(repo) 13 | pull = repo.get_pull(pull_id) 14 | 15 | if pull.merged: 16 | return False, 'Pull Request is already merged' 17 | if not pull.mergeable: 18 | return False, 'Pull Request is not mergeable' 19 | 20 | status = pull.merge() 21 | 22 | result = {'merged': status.merged, 'message': status.message} 23 | return result 24 | 25 | 26 | if __name__ == '__main__': 27 | import os 28 | 29 | GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN') 30 | GITHUB_ORG = os.environ.get('GITHUB_ORG') 31 | GITHUB_REPO = os.environ.get('GITHUB_REPO') 32 | GITHUB_BRANCH = os.environ.get('GITHUB_BRANCH') 33 | 34 | PULL_ID = 13 35 | act = MergePullAction(config={'token': GITHUB_TOKEN, 'github_type': 'online'}) 36 | res = act.run(user=GITHUB_ORG, repo=GITHUB_REPO, pull_id=PULL_ID) 37 | import pprint 38 | 39 | pp = pprint.PrettyPrinter(indent=4) 40 | pp.pprint(res) 41 | -------------------------------------------------------------------------------- /actions/merge_pull.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: merge_pull 3 | runner_type: python-script 4 | description: Merge a pull request. 5 | enabled: true 6 | entry_point: merge_pull.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | pull_id: 17 | type: "integer" 18 | description: Pull Request id 19 | required: true 20 | -------------------------------------------------------------------------------- /actions/review_pull.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from lib.formatters import pull_to_dict 3 | 4 | __all__ = [ 5 | 'ReviewPullAction' 6 | ] 7 | 8 | 9 | class ReviewPullAction(BaseGithubAction): 10 | def run(self, user, repo, pull_id, message, event): 11 | 12 | user = self._client.get_user(user) 13 | repo = user.get_repo(repo) 14 | pull = repo.get_pull(pull_id) 15 | pull.create_review(commit=repo.get_commit(pull.merge_commit_sha), 16 | body=str(message), event=str(event)) 17 | result = pull_to_dict(pull) 18 | return result 19 | -------------------------------------------------------------------------------- /actions/review_pull.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: review_pull 3 | runner_type: python-script 4 | description: Review a Github pull request given its PR id. 5 | enabled: true 6 | entry_point: review_pull.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | pull_id: 17 | type: "integer" 18 | description: Pull Request id 19 | required: true 20 | message: 21 | type: "string" 22 | description: Pull request review message 23 | required: true 24 | event: 25 | type: "string" 26 | description: The review action you want to perform 27 | enum: 28 | - "APPROVE" 29 | - "REQUEST_CHANGES" 30 | - "COMMENT" 31 | default: "APPROVE" 32 | -------------------------------------------------------------------------------- /actions/store_oauth_token.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | from lib.base import BaseGithubAction 16 | 17 | 18 | class StoreOauthTokenAction(BaseGithubAction): 19 | def run(self, user, token, github_type): 20 | enterprise = self._is_enterprise(github_type) 21 | 22 | if enterprise: 23 | value_name = "token_enterprise_{}".format(user) 24 | results = {'github_type': "enterprise"} 25 | else: 26 | value_name = "token_{}".format(user) 27 | results = {'github_type': "online"} 28 | 29 | self.action_service.set_value( 30 | name=value_name, 31 | value=token.strip()) 32 | 33 | return results 34 | -------------------------------------------------------------------------------- /actions/store_oauth_token.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "store_oauth_token" 3 | runner_type: "python-script" 4 | description: "Store a users GitHub OAuth token." 5 | enabled: true 6 | entry_point: "store_oauth_token.py" 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "The user that the tokens for" 11 | default: "{{action_context.api_user|default(None)}}" 12 | token: 13 | type: "string" 14 | description: "The GitHub OAuth token" 15 | required: true 16 | github_type: 17 | type: "string" 18 | description: "The type of github installation to target, if unset will use the configured default." 19 | default: ~ 20 | -------------------------------------------------------------------------------- /actions/update_branch_protection.py: -------------------------------------------------------------------------------- 1 | from lib.base import BaseGithubAction 2 | from github.GithubObject import NotSet 3 | 4 | __all__ = [ 5 | 'UpdateBranchProtectionAction' 6 | ] 7 | 8 | 9 | class UpdateBranchProtectionAction(BaseGithubAction): 10 | def run(self, user, repo, branch, required_status_checks, enforce_admins, 11 | required_pull_request_reviews, restrictions, required_linear_history=False, 12 | allow_force_pushes=False, allow_deletions=False): 13 | 14 | user = self._client.get_user(user) 15 | repo = user.get_repo(repo) 16 | branch = repo.get_branch(branch) 17 | 18 | if not required_status_checks: 19 | strict = NotSet 20 | contexts = NotSet 21 | else: 22 | strict = required_status_checks['strict'] 23 | contexts = required_status_checks['contexts'] 24 | 25 | if not required_pull_request_reviews: 26 | dismissal_users = NotSet 27 | dismissal_teams = NotSet 28 | dismiss_stale_reviews = NotSet 29 | require_code_owner_reviews = NotSet 30 | required_approving_review_count = NotSet 31 | else: 32 | dismissal_users = required_pull_request_reviews['dismissal_users'] 33 | dismissal_teams = required_pull_request_reviews['dismissal_teams'] 34 | dismiss_stale_reviews = required_pull_request_reviews['dismiss_stale_reviews'] 35 | require_code_owner_reviews = required_pull_request_reviews['require_code_owner_reviews'] 36 | required_approving_review_count = required_pull_request_reviews[ 37 | 'required_approving_review_count'] 38 | 39 | if not restrictions: 40 | user_push_restrictions = NotSet 41 | team_push_restrictions = NotSet 42 | else: 43 | user_push_restrictions = restrictions['user_push_restrictions'] 44 | team_push_restrictions = restrictions['team_push_restrictions'] 45 | 46 | if not dismissal_users: 47 | dismissal_users = NotSet 48 | if not dismissal_teams: 49 | dismissal_teams = NotSet 50 | 51 | branch.edit_protection(strict=strict, contexts=contexts, 52 | enforce_admins=enforce_admins, 53 | dismissal_users=dismissal_users, 54 | dismissal_teams=dismissal_teams, 55 | dismiss_stale_reviews=dismiss_stale_reviews, 56 | require_code_owner_reviews=require_code_owner_reviews, 57 | required_approving_review_count=required_approving_review_count, 58 | user_push_restrictions=user_push_restrictions, 59 | team_push_restrictions=team_push_restrictions) 60 | return True 61 | 62 | 63 | if __name__ == '__main__': 64 | import os 65 | 66 | GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN') 67 | GITHUB_ORG = os.environ.get('GITHUB_ORG') 68 | GITHUB_REPO = os.environ.get('GITHUB_REPO') 69 | GITHUB_BRANCH = os.environ.get('GITHUB_BRANCH') 70 | 71 | # As produced by get_branch_protection action 72 | BRANCH_PROTECTION = {'enforce_admins': False, 73 | 'required_pull_request_reviews': {'dismiss_stale_reviews': False, 74 | 'dismissal_teams': None, 75 | 'dismissal_users': None, 76 | 'require_code_owner_reviews': False, 77 | 'required_approving_review_count': 0}, 78 | 'required_status_checks': None, 79 | 'restrictions': None 80 | } 81 | 82 | act = UpdateBranchProtectionAction(config={'token': GITHUB_TOKEN, 'github_type': 'online'}) 83 | res = act.run(user=GITHUB_ORG, repo=GITHUB_REPO, branch=GITHUB_BRANCH, 84 | required_status_checks=BRANCH_PROTECTION['required_status_checks'], 85 | enforce_admins=BRANCH_PROTECTION['enforce_admins'], 86 | required_pull_request_reviews=BRANCH_PROTECTION['required_pull_request_reviews'], 87 | restrictions=BRANCH_PROTECTION['restrictions']) 88 | import pprint 89 | 90 | pp = pprint.PrettyPrinter(indent=4) 91 | pp.pprint(res) 92 | -------------------------------------------------------------------------------- /actions/update_branch_protection.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: update_branch_protection 3 | runner_type: python-script 4 | description: Updates branch protection. 5 | enabled: true 6 | entry_point: update_branch_protection.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | branch: 17 | type: "string" 18 | description: "Branch name." 19 | required: false 20 | default: "master" 21 | required_status_checks: 22 | type: "object" 23 | description: "Required. Require status checks to pass before merging. Set to null to disable." 24 | enforce_admins: 25 | type: "boolean" 26 | description: "Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable." 27 | required_pull_request_reviews: 28 | type: "object" 29 | description: "Required. Require at least one approving review on a pull request, before merging. Set to null to disable." 30 | restrictions: 31 | type: "object" 32 | description: "Required. Restrict who can push to the protected branch. User, app, and team restrictions are only available for organization-owned repositories. Set to null to disable." 33 | required_linear_history: 34 | type: "boolean" 35 | default: false 36 | description: "Enforces a linear commit Git history, which prevents anyone from pushing merge commits to a branch. Set to true to enforce a linear commit history. Set to false to disable a linear commit Git history. Your repository must allow squash merging or rebase merging before you can enable a linear commit history. Default: false." 37 | allow_force_pushes: 38 | type: "boolean" 39 | default: false 40 | description: "Permits force pushes to the protected branch by anyone with write access to the repository. Set to true to allow force pushes. Set to false or null to block force pushes. Default: false." 41 | allow_deletions: 42 | type: "boolean" 43 | default: false 44 | description: "Allows deletion of the protected branch by anyone with write access to the repository. Set to false to prevent deletion of the protected branch. Default: false." 45 | 46 | -------------------------------------------------------------------------------- /actions/update_file.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | from lib.base import BaseGithubAction 4 | from lib.formatters import file_response_to_dict 5 | from lib.utils import prep_github_params_for_file_ops 6 | 7 | __all__ = [ 8 | 'UpdateFileAction' 9 | ] 10 | 11 | 12 | class UpdateFileAction(BaseGithubAction): 13 | def run(self, user, repo, path, message, content, sha, branch=None, committer=None, 14 | author=None, encoding=None): 15 | author, branch, committer = prep_github_params_for_file_ops(author, branch, committer) 16 | 17 | if encoding and encoding == 'base64': 18 | content = base64.b64encode(content.encode('utf-8')) 19 | 20 | user = self._client.get_user(user) 21 | repo = user.get_repo(repo) 22 | api_response = repo.update_file(path=path, message=message, content=content, sha=sha, 23 | branch=branch, committer=committer, author=author) 24 | result = file_response_to_dict(api_response) 25 | return result 26 | 27 | 28 | if __name__ == '__main__': 29 | import os 30 | 31 | GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN') 32 | GITHUB_ORG = os.environ.get('GITHUB_ORG') 33 | GITHUB_REPO = os.environ.get('GITHUB_REPO') 34 | COMMITTER = os.environ.get('COMMITTER', None) 35 | AUTHOR = os.environ.get('AUTHOR', None) 36 | 37 | act = UpdateFileAction(config={'token': GITHUB_TOKEN, 'github_type': 'online'}) 38 | res = act.run(user=GITHUB_ORG, repo=GITHUB_REPO, path='README.md', 39 | message='Test commit, committer: {}, author: {}'.format(COMMITTER, AUTHOR), 40 | content='Super duper read me file, pushed from Stackstorm github pack!\n' 41 | '##new lines added!\n\n*YES*\nHOORAY!!!\nHELL YEAH!\n', 42 | sha='058d97c135546cf5d1a029dabc16c22313a8c90b', 43 | branch='branch1', committer=COMMITTER, author=AUTHOR) 44 | import pprint 45 | 46 | pp = pprint.PrettyPrinter(indent=4) 47 | pp.pprint(res) 48 | -------------------------------------------------------------------------------- /actions/update_file.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: update_file 3 | runner_type: python-script 4 | description: Update a file in this repository. 5 | enabled: true 6 | entry_point: update_file.py 7 | parameters: 8 | user: 9 | type: "string" 10 | description: "User / organization name." 11 | required: true 12 | repo: 13 | type: "string" 14 | description: "Repository name." 15 | required: true 16 | path: 17 | type: "string" 18 | description: "Path of the file in the repository." 19 | required: true 20 | message: 21 | type: "string" 22 | description: "Commit message." 23 | required: true 24 | content: 25 | type: "string" 26 | description: "The actual data in the file." 27 | required: true 28 | encoding: 29 | type: "string" 30 | description: "Specify if the input content is encoded." 31 | enum: 32 | - "none" 33 | - "base64" 34 | default: "none" 35 | required: false 36 | sha: 37 | type: "string" 38 | description: "The blob SHA of the file being replaced." 39 | required: true 40 | branch: 41 | type: "string" 42 | description: "Branch to create the commit on. Defaults to the default branch of the repository" 43 | required: false 44 | committer: 45 | type: "string" 46 | description: "If no information is given the authenticated user's information will be used. You must specify both a name and email. Expected format: FirstName LastName " 47 | required: false 48 | author: 49 | type: "string" 50 | description: "If omitted this will be filled in with committer information. If passed, you must specify both a name and email. Expected format: FirstName LastName " 51 | required: false 52 | -------------------------------------------------------------------------------- /actions/workflows/deployment_event.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '1.0' 3 | description: A workflow to process an github deployment event and install a pack if the environment matches. 4 | input: 5 | - repo_fullname 6 | - repo_name 7 | - deploy_ref 8 | - deploy_env 9 | - deploy_sha 10 | - deploy_payload 11 | - deploy_desc 12 | - deploy_id 13 | - ssh_url 14 | - creator 15 | tasks: 16 | check_environment: 17 | action: github.check_deployment_env 18 | input: 19 | deploy_env: <% ctx().deploy_env %> 20 | next: 21 | - when: <% succeeded() %> 22 | do: 23 | - install_pack 24 | - when: <% failed() %> 25 | do: 26 | - no_deloyment 27 | install_pack: 28 | action: packs.install 29 | input: 30 | packs: 31 | - <% ctx().ssh_url %>=<% ctx().deploy_ref %> 32 | next: 33 | - when: <% succeeded() %> 34 | do: 35 | - deployment_successful 36 | - when: <% failed() %> 37 | do: 38 | - deployment_error 39 | deployment_successful: 40 | action: github.create_deployment_status 41 | input: 42 | api_user: <% ctx().creator %> 43 | repository: <% ctx().repo_fullname %> 44 | deployment_id: <% ctx().deploy_id %> 45 | state: success 46 | description: Completed deployment of <% ctx().repo_fullname %> on <% ctx().deploy_env %>. 47 | deployment_error: 48 | action: github.create_deployment_status 49 | input: 50 | api_user: <% ctx().creator %> 51 | repository: <% ctx().repo_fullname %> 52 | deployment_id: <% ctx().deploy_id %> 53 | state: failure 54 | description: Failed deployment of <% ctx().repo_fullname %> on <% ctx().deploy_env %>. 55 | no_deloyment: 56 | action: core.noop 57 | -------------------------------------------------------------------------------- /aliases/create_deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "create_deployment" 3 | pack: "github" 4 | description: "Create a new git deployment for a repository." 5 | action_ref: "github.create_deployment" 6 | formats: 7 | - display: "github deployment create {{repository}} [type (enterprise¦online)] [ref {{ref=master}}] [environment {{environment=production}}] description {{description}}" 8 | representation: 9 | - "github deployment create {{repository}}( type {{github_type}})?( ref {{ref=master}})?( environment {{environment=production}})? description {{description}}" 10 | ack: 11 | enabled: false 12 | append_url: false 13 | result: 14 | extra: 15 | slack: 16 | author_name: "{{execution.result.result.creator}}" 17 | color: "#00ad52" # aka Slack 'good' 18 | title: "{{execution.result.result.task}} (ID {{execution.result.result.id}})" 19 | ts: "{{execution.result.result.ts_created_at}}" 20 | footer: "GitHub" 21 | format: | 22 | {% if execution.status == 'succeeded' %} 23 | Created a new deployment for _{{execution.parameters.repository}}_ for ref _{{execution.parameters.ref}}_{~} 24 | Description: {{execution.result.result.description}} Environment: {{execution.result.result.environment}}. 25 | 26 | Check deployment status with: ```! github deployment statuses {{execution.parameters.repository}} id {{execution.result.result.id}}``` 27 | {% else %} 28 | Error: {{execution.result.stdout}}{~}See {{execution.id}} for more details. 29 | {% endif %} 30 | -------------------------------------------------------------------------------- /aliases/create_release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "create_release" 3 | pack: "github" 4 | description: "Create a new git release." 5 | action_ref: "github.create_release" 6 | formats: 7 | - display: "github release create {{repository}} [type (enterprise¦online)] [version {{version_increase}] [commit {{target_commitish}}] name {{name}} body {{body}}" 8 | representation: 9 | - "github release create {{repository}}( type {{github_type}})?( version {{version_increase}})?( commit {{target_commitish}})? name {{name}} body {{body}}" 10 | ack: 11 | enabled: false 12 | append_url: false 13 | result: 14 | extra: 15 | slack: 16 | author_name: "{{execution.result.result.author}}" 17 | text: "{{execution.result.result.body}}" 18 | color: "#00ad52" # aka Slack 'good' 19 | title: "{{execution.result.result.tag_name}} - {{execution.result.result.name}}" 20 | title_link: "{{execution.result.result.html_url}}" 21 | ts: "{{execution.result.result.ts_published_at}}" 22 | footer: "GitHub" 23 | format: | 24 | {% if execution.status == 'succeeded' %} 25 | Created a new release for _{{execution.parameters.repository}}_ with the tag {{execution.result.result.tag_name}}{~} 26 | {{execution.result.result.body}} 27 | {% else %} 28 | Error: {{execution.result.stdout}}{~}See {{execution.id}} for more details. 29 | {% endif %} 30 | -------------------------------------------------------------------------------- /aliases/deployment_statuses.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "deployment_statuses" 3 | pack: "github" 4 | description: "Show the deployment statuses for a deployment ID." 5 | action_ref: "github.get_deployment_statuses" 6 | formats: 7 | - display: "github deployment statuses [type (enterprise¦online)]{{repository}} id {{deployment_id}}" 8 | representation: 9 | - "github deployment statuses( type {{github_type}})? {{repository}} id {{deployment_id}}" 10 | ack: 11 | enabled: false 12 | append_url: false 13 | result: 14 | format: | 15 | {% if execution.status == 'succeeded' %} 16 | Deployment statuses for {{execution.parameters.repository}} (ID: {{execution.parameters.deployment_id}}):{~} 17 | {% for status in execution.result.result %} 18 | *{{status.updated_at}}:* _{{status.state}}_ ({{status.description}}) by {{status.creator}} {% if status.target_url %} (<{{status.target_url}}|link>){% endif %} 19 | {% endfor %} 20 | {% else %} 21 | Error: {{execution.result.stdout}}{~}See {{execution.id}} for more details. 22 | {% endif %} 23 | -------------------------------------------------------------------------------- /aliases/get_user.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "get_user" 3 | pack: "github" 4 | description: "Get the name of a GitHub user." 5 | action_ref: "github.get_user" 6 | formats: 7 | - display: "github get user [type (enterprise¦online)] {{user}}" 8 | representation: 9 | - "github get user( type {{github_type}})? {{user}}" 10 | ack: 11 | enabled: false 12 | append_url: false 13 | result: 14 | format: | 15 | {% if execution.status == 'succeeded' %} 16 | {{ execution.result.result.login}} {{ execution.result.result.name}} 17 | {% else %} 18 | Error: {{execution.result.stdout}}{~}See {{execution.id}} for more details. 19 | {% endif %} 20 | -------------------------------------------------------------------------------- /aliases/latest_release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "latest_release" 3 | pack: "github" 4 | description: "Get the latest release for a repository." 5 | action_ref: "github.latest_release" 6 | formats: 7 | - display: "github release latest [type (enterprise¦online)] {{repository}}" 8 | representation: 9 | - "github release latest( type {{github_type}})? {{repository}}" 10 | ack: 11 | enabled: false 12 | append_url: false 13 | result: 14 | extra: 15 | slack: 16 | author_name: "{{execution.result.result.author}}" 17 | author_icon: "{{execution.result.result.avatar_url}}" 18 | text: "{{execution.result.result.body}}" 19 | color: "#00ad52" # aka Slack 'good' 20 | title: "{{execution.result.result.tag_name}} - {{execution.result.result.name}}" 21 | title_link: "{{execution.result.result.html_url}}" 22 | ts: "{{execution.result.result.ts_published_at}}" 23 | footer: "GitHub" 24 | format: | 25 | {% if execution.status == 'succeeded' %} 26 | The latest release for _{{execution.parameters.repository}}_ is {{execution.result.result.tag_name}} ({{execution.result.result.name}}){~} 27 | {{execution.result.result.body}} 28 | {% else %} 29 | Error: {{execution.result.stdout}}{~}See {{execution.id}} for more details. 30 | {% endif %} 31 | -------------------------------------------------------------------------------- /aliases/list_releases.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "list_releases" 3 | pack: "github" 4 | description: "List the releases for a repository." 5 | action_ref: "github.list_releases" 6 | formats: 7 | - display: "github releases list [type (enterprise¦online)] {{repository}}" 8 | representation: 9 | - "github releases list( type {{github_type}})? {{repository}}" 10 | ack: 11 | enabled: false 12 | append_url: false 13 | result: 14 | extra: 15 | slack: 16 | color: "#00ad52" # aka Slack 'good' 17 | format: | 18 | {% if execution.status == 'succeeded' %} 19 | The releases for _{{execution.parameters.repository}}_:{~} 20 | {% for release in execution.result.result %} 21 | <{{release.html_url}}|{{release.tag_name}}: {{release.name}}> (by {{release.author}} @ {{release.published_at}}){% if release.prerelease %} _Prerelease_{% endif %}{% if release.draft %} _Draft_{% endif %}. 22 | {% endfor %} 23 | {% else %} 24 | Error: {{execution.result.stdout}}{~}See {{execution.id}} for more details. 25 | {% endif %} 26 | -------------------------------------------------------------------------------- /aliases/store_oauth_token.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "store_oauth_token" 3 | pack: "github" 4 | description: "Store your own GitHub (online or enterprise) OAuth Token." 5 | action_ref: "github.store_oauth_token" 6 | formats: 7 | - display: "github store [type (enterprise¦online)] token {{token}}" 8 | representation: 9 | - "github store( type {{github_type}})? token {{token}}" 10 | ack: 11 | enabled: false 12 | append_url: false 13 | result: 14 | format: | 15 | {% if execution.status == 'succeeded' %} 16 | Your {{execution.result.result.github_type}} GitHub token has been stored! 17 | {% else %} 18 | Error: {{execution.result.stdout}}{~}See {{execution.id}} for more details. 19 | {% endif %} 20 | -------------------------------------------------------------------------------- /config.schema.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | token: 3 | description: "GitHub oAuth Token" 4 | type: "string" 5 | default: "{{ st2kv.system.github_oauth_token }}" 6 | required: true 7 | secret: true 8 | 9 | user: 10 | description: "GitHub Username" 11 | type: "string" 12 | 13 | password: 14 | description: "GitHub Password" 15 | type: "string" 16 | secret: true 17 | 18 | github_type: 19 | description: "Default to either github or enterprise." 20 | type: "string" 21 | default: "online" 22 | enum: 23 | - "online" 24 | - "enterprise" 25 | required: true 26 | 27 | web_url: 28 | description: "The GitHub URL." 29 | type: "string" 30 | default: "https://github.example.com" 31 | required: false 32 | 33 | base_url: 34 | description: "GitHub API url, include /api/v3 for GitHub Enterprise. exp: " 35 | type: "string" 36 | default: "https://github.example.com/api/v3" 37 | required: false 38 | 39 | deployment_environment: 40 | description: "The environment for this StackStorm server." 41 | type: "string" 42 | required: true 43 | default: "production" 44 | 45 | repository_sensor: 46 | description: "Sensor specific settings, with default github type." 47 | type: "object" 48 | required: false 49 | additionalProperties: false 50 | properties: 51 | event_type_whitelist: 52 | type: "array" 53 | description: "Event types to watch for, e.g. IssuesEvent, PushEvent" 54 | items: 55 | type: "string" 56 | required: false 57 | default: 58 | - "IssuesEvent" 59 | - "IssueCommentEvent" 60 | - "ForkEvent" 61 | - "WatchEvent" 62 | - "ReleaseEvent" 63 | - "PushEvent" 64 | repositories: 65 | description: "Repositories to monitor" 66 | type: "array" 67 | required: true 68 | items: 69 | type: "object" 70 | required: true 71 | properties: 72 | user: 73 | description: "GitHub user or organization name" 74 | type: "string" 75 | required: true 76 | name: 77 | description: "Repository name" 78 | type: "string" 79 | required: true 80 | count: 81 | type: "integer" 82 | description: "Maximum number of old events to retrieve" 83 | required: false 84 | default: 30 85 | -------------------------------------------------------------------------------- /github.yaml.example: -------------------------------------------------------------------------------- 1 | --- 2 | token: "{{ st2kv.system.github_oauth_token }}" 3 | user: "Stanley" 4 | password: "P4ssw0rd" 5 | github_type: "online" 6 | web_url: "https://github.example.com" 7 | base_url: "https://github.example.com/api/v3" 8 | deployment_environment: "production" 9 | repository_sensor: 10 | event_type_whitelist: 11 | - "IssuesEvent" 12 | - "IssueCommentEvent" 13 | - "ForkEvent" 14 | - "WatchEvent" 15 | - "ReleaseEvent" 16 | - "PushEvent" 17 | repositories: 18 | - user: "StackStorm" 19 | name: "st2" 20 | - user: "StackStorm" 21 | name: "st2contrib" 22 | count: 30 # Maximum number of old events to retrieve 23 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackStorm-Exchange/stackstorm-github/fc8e45bd25bcfdd891f3e88e0b87f7592fe7f634/icon.png -------------------------------------------------------------------------------- /pack.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ref: github 3 | name : github 4 | description : GitHub integration 5 | keywords: 6 | - github 7 | - github enterprise 8 | - git 9 | - scm 10 | - serverless 11 | version: 2.1.5 12 | python_versions: 13 | - "3" 14 | author : StackStorm, Inc. 15 | email : info@stackstorm.com 16 | contributors: 17 | - Jon Middleton 18 | - Igor Cherkaev 19 | -------------------------------------------------------------------------------- /requirements-tests.txt: -------------------------------------------------------------------------------- 1 | urllib3 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyGithub==1.45 2 | urllib3 3 | requests==2.23.0 4 | beautifulsoup4==4.7.1 5 | -------------------------------------------------------------------------------- /rules/deploy_pack_on_deployment_event.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "deploy_pack_on_deployment_event" 3 | pack: "github" 4 | description: "Trigger an ST2 pack.install when receiving a deployment event from GitHub." 5 | enabled: false 6 | 7 | trigger: 8 | type: "core.st2.webhook" 9 | parameters: 10 | url: "github_deployment_event" 11 | 12 | criteria: 13 | trigger.body: 14 | pattern: "deployment" 15 | type: "exists" 16 | 17 | action: 18 | ref: "github.deployment_event" 19 | parameters: 20 | repo_fullname: "{{trigger.body.repository.full_name}}" 21 | repo_name: "{{trigger.body.repository.name}}" 22 | deploy_ref: "{{trigger.body.deployment.ref}}" 23 | deploy_env: "{{trigger.body.deployment.environment}}" 24 | deploy_sha: "{{trigger.body.deployment.sha}}" 25 | deploy_payload: "{{trigger.body.deployment.payload}}" 26 | deploy_desc: "{{trigger.body.deployment.description}}" 27 | deploy_id: "{{trigger.body.deployment.id}}" 28 | ssh_url: "{{trigger.body.repository.ssh_url}}" 29 | creator: "{{trigger.body.deployment.creator.login}}" 30 | -------------------------------------------------------------------------------- /sensors/github_repository_sensor.py: -------------------------------------------------------------------------------- 1 | import six 2 | import eventlet 3 | from github import Github 4 | 5 | from st2reactor.sensor.base import PollingSensor 6 | 7 | eventlet.monkey_patch( 8 | os=True, 9 | select=True, 10 | socket=True, 11 | thread=True, 12 | time=True) 13 | 14 | DATE_FORMAT_STRING = '%Y-%m-%d %H:%M:%S' 15 | 16 | 17 | # Default Github API url 18 | DEFAULT_API_URL = 'https://api.github.com' 19 | 20 | 21 | class GithubRepositorySensor(PollingSensor): 22 | def __init__(self, sensor_service, config=None, poll_interval=None): 23 | super(GithubRepositorySensor, self).__init__(sensor_service=sensor_service, 24 | config=config, 25 | poll_interval=poll_interval) 26 | self._trigger_ref = 'github.repository_event' 27 | self._logger = self._sensor_service.get_logger(__name__) 28 | 29 | self._client = None 30 | self._repositories = [] 31 | self._last_event_ids = {} 32 | self.EVENT_TYPE_WHITELIST = [] 33 | 34 | def setup(self): 35 | # Empty string '' is not ok but None is fine. (Sigh) 36 | github_type = self._config.get('github_type', None) 37 | if github_type == 'online': 38 | config_base_url = DEFAULT_API_URL 39 | else: 40 | config_base_url = self._config.get('base_url', None) or None 41 | 42 | config_token = self._config.get('token', None) or None 43 | self._client = Github(config_token or None, base_url=config_base_url) 44 | 45 | repository_sensor = self._config.get('repository_sensor', None) 46 | if repository_sensor is None: 47 | raise ValueError('"repository_sensor" config value is required.') 48 | 49 | self.EVENT_TYPE_WHITELIST = repository_sensor.get('event_type_whitelist', []) 50 | 51 | repositories = repository_sensor.get('repositories', None) 52 | if not repositories: 53 | raise ValueError('GithubRepositorySensor should have at least 1 repository.') 54 | 55 | for repository_dict in repositories: 56 | user = self._client.get_user(repository_dict['user']) 57 | repository = user.get_repo(repository_dict['name']) 58 | self._repositories.append((repository_dict['name'], repository)) 59 | 60 | def poll(self): 61 | for repository_name, repository_obj in self._repositories: 62 | self._logger.debug('Processing repository "%s"' % 63 | (repository_name)) 64 | self._process_repository(name=repository_name, 65 | repository=repository_obj) 66 | 67 | def _process_repository(self, name, repository): 68 | """ 69 | Retrieve events for the provided repository and dispatch triggers for 70 | new events. 71 | 72 | :param name: Repository name. 73 | :type name: ``str`` 74 | 75 | :param repository: Repository object. 76 | :type repository: :class:`Repository` 77 | """ 78 | assert(isinstance(name, six.text_type)) 79 | 80 | # Assume a default value of 30. Better for the sensor to operate with some 81 | # default value in this case rather than raise an exception. 82 | count = self._config['repository_sensor'].get('count', 30) 83 | repository_events = repository.get_events() 84 | 85 | events = list(repository_events[:count]) \ 86 | if repository_events.totalCount > count else list(repository_events) 87 | events.sort(key=lambda _event: _event.id, reverse=False) 88 | 89 | last_event_id = self._get_last_id(name=name) 90 | 91 | for event in events: 92 | if last_event_id and int(event.id) <= int(last_event_id): 93 | # This event has already been processed 94 | continue 95 | 96 | self._handle_event(repository=name, event=event) 97 | 98 | if events: 99 | self._set_last_id(name=name, last_id=events[-1].id) 100 | 101 | def cleanup(self): 102 | pass 103 | 104 | def add_trigger(self, trigger): 105 | pass 106 | 107 | def update_trigger(self, trigger): 108 | pass 109 | 110 | def remove_trigger(self, trigger): 111 | pass 112 | 113 | def _get_last_id(self, name): 114 | """ 115 | :param name: Repository name. 116 | :type name: ``str`` 117 | """ 118 | if not self._last_event_ids.get(name, None) and hasattr(self._sensor_service, 'get_value'): 119 | key_name = 'last_id.%s' % (name) 120 | self._last_event_ids[name] = self._sensor_service.get_value(name=key_name) 121 | 122 | return self._last_event_ids.get(name, None) 123 | 124 | def _set_last_id(self, name, last_id): 125 | """ 126 | :param name: Repository name. 127 | :type name: ``str`` 128 | """ 129 | self._last_event_ids[name] = last_id 130 | 131 | if hasattr(self._sensor_service, 'set_value'): 132 | key_name = 'last_id.%s' % (name) 133 | self._sensor_service.set_value(name=key_name, value=last_id) 134 | 135 | def _handle_event(self, repository, event): 136 | if event.type not in self.EVENT_TYPE_WHITELIST: 137 | self._logger.debug('Skipping ignored event (type=%s)' % (event.type)) 138 | return 139 | 140 | self._dispatch_trigger_for_event(repository=repository, event=event) 141 | 142 | def _dispatch_trigger_for_event(self, repository, event): 143 | trigger = self._trigger_ref 144 | 145 | created_at = event.created_at 146 | 147 | if created_at: 148 | created_at = created_at.strftime(DATE_FORMAT_STRING) 149 | 150 | # Common attributes 151 | payload = { 152 | 'repository': repository, 153 | 'id': event.id, 154 | 'created_at': created_at, 155 | 'type': event.type, 156 | 'actor': { 157 | 'id': event.actor.id, 158 | 'login': event.actor.login, 159 | 'name': event.actor.name, 160 | 'email': event.actor.email, 161 | 'loaction': event.actor.location, 162 | 'bio': event.actor.bio, 163 | 'url': event.actor.html_url 164 | }, 165 | 'payload': {} 166 | } 167 | 168 | event_specific_payload = self._get_payload_for_event(event=event) 169 | payload['payload'] = event_specific_payload 170 | self._sensor_service.dispatch(trigger=trigger, payload=payload) 171 | 172 | def _get_payload_for_event(self, event): 173 | payload = event.payload or {} 174 | return payload 175 | -------------------------------------------------------------------------------- /sensors/github_repository_sensor.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | class_name: "GithubRepositorySensor" 3 | entry_point: "github_repository_sensor.py" 4 | description: "Sensor which monitors Github repository for activity" 5 | # requires authentication because of GitHub rate limiting 6 | # use default with auth or increase poll interval 7 | enabled: true 8 | poll_interval: 30 9 | trigger_types: 10 | - name: "repository_event" 11 | description: "Trigger which indicates a new repository action" 12 | payload_schema: 13 | type: "object" 14 | properties: 15 | id: 16 | type: "string" 17 | created_at: 18 | type: "string" 19 | type: 20 | type: "string" 21 | actor: 22 | type: "object" 23 | payload: 24 | type: "object" 25 | -------------------------------------------------------------------------------- /tests/fixtures/blank.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /tests/fixtures/full-enterprise.yaml: -------------------------------------------------------------------------------- 1 | web_url: "https://github.exmple.com" 2 | base_url: "https://github.exmple.com/api/v3" 3 | token: "foobar" 4 | 5 | github_type: "enterprise" 6 | -------------------------------------------------------------------------------- /tests/fixtures/full.yaml: -------------------------------------------------------------------------------- 1 | token: "foobar" 2 | 3 | github_type: "online" 4 | 5 | deployment_environment: "production" 6 | -------------------------------------------------------------------------------- /tests/github_base_action_test_case.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | import yaml 16 | # from mock import MagicMock 17 | 18 | from st2tests.base import BaseActionTestCase 19 | 20 | 21 | class GitHubBaseActionTestCase(BaseActionTestCase): 22 | __test__ = False 23 | 24 | def setUp(self): 25 | super(GitHubBaseActionTestCase, self).setUp() 26 | 27 | self._blank_config = self.load_yaml('blank.yaml') 28 | self._full_config = self.load_yaml('full.yaml') 29 | self._enterprise_default_config = self.load_yaml( 30 | 'full-enterprise.yaml') 31 | 32 | def load_yaml(self, filename): 33 | return yaml.safe_load(self.get_fixture_content(filename)) 34 | 35 | @property 36 | def blank_config(self): 37 | return self._blank_config 38 | 39 | @property 40 | def full_config(self): 41 | return self._full_config 42 | 43 | @property 44 | def enterprise_config(self): 45 | return self._enterprise_default_config 46 | 47 | def test_run_no_config(self): 48 | action = self.get_action_instance(self.full_config) 49 | self.assertIsInstance(action, self.action_cls) 50 | 51 | def test_run_is_instance(self): 52 | action = self.get_action_instance(self.full_config) 53 | self.assertIsInstance(action, self.action_cls) 54 | -------------------------------------------------------------------------------- /tests/test_action_add_comment.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from add_comment import AddCommentAction 20 | 21 | 22 | class AddCommentActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = AddCommentAction 25 | -------------------------------------------------------------------------------- /tests/test_action_add_status.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from add_status import AddCommitStatusAction 20 | 21 | 22 | class AddCommitStatusActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = AddCommitStatusAction 25 | -------------------------------------------------------------------------------- /tests/test_action_add_team_membership.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from add_team_membership import AddTeamMembershipAction 20 | 21 | 22 | class AddTeamMembershipActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = AddTeamMembershipAction 25 | -------------------------------------------------------------------------------- /tests/test_action_aliases.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | from st2tests.base import BaseActionAliasTestCase 16 | 17 | 18 | class CreateDeployment(BaseActionAliasTestCase): 19 | action_alias_name = "create_deployment" 20 | 21 | def test_alias_create_deployment_simple(self): 22 | format_string = self.action_alias_db.formats[0]['representation'][0] 23 | format_strings = self.action_alias_db.get_format_strings() 24 | 25 | command = "github deployment create st2contrib description A description" 26 | expected_parameters = { 27 | 'repository': "st2contrib", 28 | 'github_type': None, 29 | 'ref': "master", 30 | 'environment': "production", 31 | 'description': "A description" 32 | } 33 | 34 | self.assertExtractedParametersMatch(format_string=format_string, 35 | command=command, 36 | parameters=expected_parameters) 37 | self.assertCommandMatchesExactlyOneFormatString( 38 | format_strings=format_strings, 39 | command=command) 40 | 41 | def test_alias_create_deployment_full(self): 42 | format_string = self.action_alias_db.formats[0]['representation'][0] 43 | format_strings = self.action_alias_db.get_format_strings() 44 | 45 | command = "github deployment create st2contrib type online ref v1.0.0 environment staging description Another description" # NOQA 46 | expected_parameters = { 47 | 'repository': "st2contrib", 48 | 'github_type': "online", 49 | 'ref': "v1.0.0", 50 | 'environment': "staging", 51 | 'description': "Another description" 52 | } 53 | 54 | self.assertExtractedParametersMatch(format_string=format_string, 55 | command=command, 56 | parameters=expected_parameters) 57 | self.assertCommandMatchesExactlyOneFormatString( 58 | format_strings=format_strings, 59 | command=command) 60 | 61 | 62 | class CreateRelease(BaseActionAliasTestCase): 63 | action_alias_name = "create_release" 64 | 65 | def test_alias_create_release_simple(self): 66 | format_string = self.action_alias_db.formats[0]['representation'][0] 67 | format_strings = self.action_alias_db.get_format_strings() 68 | 69 | command = "github release create st2contrib name v1.0.0 body It's a release Jim, but not as we know it!" # NOQA 70 | expected_parameters = { 71 | 'repository': "st2contrib", 72 | 'github_type': None, 73 | 'name': "v1.0.0", 74 | 'version_increase': None, 75 | 'target_commitish': None, 76 | 'body': "It's a release Jim, but not as we know it!" 77 | } 78 | 79 | self.assertExtractedParametersMatch(format_string=format_string, 80 | command=command, 81 | parameters=expected_parameters) 82 | self.assertCommandMatchesExactlyOneFormatString( 83 | format_strings=format_strings, 84 | command=command) 85 | 86 | def test_alias_create_release_full(self): 87 | format_string = self.action_alias_db.formats[0]['representation'][0] 88 | format_strings = self.action_alias_db.get_format_strings() 89 | 90 | command = "github release create st2contrib type online version patch commit master name v1.0.0 body It's a release Jim, but not as we know it!" # NOQA 91 | expected_parameters = { 92 | 'repository': "st2contrib", 93 | 'github_type': "online", 94 | 'version_increase': "patch", 95 | 'target_commitish': "master", 96 | 'name': "v1.0.0", 97 | 'body': "It's a release Jim, but not as we know it!" 98 | } 99 | 100 | self.assertExtractedParametersMatch(format_string=format_string, 101 | command=command, 102 | parameters=expected_parameters) 103 | self.assertCommandMatchesExactlyOneFormatString( 104 | format_strings=format_strings, 105 | command=command) 106 | 107 | 108 | class DeploymentStatuses(BaseActionAliasTestCase): 109 | action_alias_name = "deployment_statuses" 110 | 111 | def test_alias_deployment_statuses_simple(self): 112 | format_string = self.action_alias_db.formats[0]['representation'][0] 113 | format_strings = self.action_alias_db.get_format_strings() 114 | 115 | command = "github deployment statuses st2contrib id 1" 116 | expected_parameters = { 117 | 'repository': "st2contrib", 118 | 'github_type': None, 119 | 'deployment_id': "1", 120 | } 121 | 122 | self.assertExtractedParametersMatch(format_string=format_string, 123 | command=command, 124 | parameters=expected_parameters) 125 | self.assertCommandMatchesExactlyOneFormatString( 126 | format_strings=format_strings, 127 | command=command) 128 | 129 | def test_alias_deployment_statuses_full(self): 130 | format_string = self.action_alias_db.formats[0]['representation'][0] 131 | format_strings = self.action_alias_db.get_format_strings() 132 | 133 | command = "github deployment statuses type online st2contrib id 1" 134 | expected_parameters = { 135 | 'repository': "st2contrib", 136 | 'github_type': "online", 137 | 'deployment_id': "1", 138 | } 139 | 140 | self.assertExtractedParametersMatch(format_string=format_string, 141 | command=command, 142 | parameters=expected_parameters) 143 | self.assertCommandMatchesExactlyOneFormatString( 144 | format_strings=format_strings, 145 | command=command) 146 | 147 | 148 | class GetUser(BaseActionAliasTestCase): 149 | action_alias_name = "get_user" 150 | 151 | def test_alias_get_user_simple(self): 152 | format_string = self.action_alias_db.formats[0]['representation'][0] 153 | format_strings = self.action_alias_db.get_format_strings() 154 | 155 | command = "github get user stanley" 156 | expected_parameters = { 157 | 'user': "stanley", 158 | 'github_type': None 159 | } 160 | 161 | self.assertExtractedParametersMatch(format_string=format_string, 162 | command=command, 163 | parameters=expected_parameters) 164 | self.assertCommandMatchesExactlyOneFormatString( 165 | format_strings=format_strings, 166 | command=command) 167 | 168 | def test_alias_get_user_full(self): 169 | format_string = self.action_alias_db.formats[0]['representation'][0] 170 | format_strings = self.action_alias_db.get_format_strings() 171 | 172 | command = "github get user type online stanley" 173 | expected_parameters = { 174 | 'user': "stanley", 175 | 'github_type': "online" 176 | } 177 | 178 | self.assertExtractedParametersMatch(format_string=format_string, 179 | command=command, 180 | parameters=expected_parameters) 181 | self.assertCommandMatchesExactlyOneFormatString( 182 | format_strings=format_strings, 183 | command=command) 184 | 185 | 186 | class LatestRelease(BaseActionAliasTestCase): 187 | action_alias_name = "latest_release" 188 | 189 | def test_alias_latest_release_simple(self): 190 | format_string = self.action_alias_db.formats[0]['representation'][0] 191 | format_strings = self.action_alias_db.get_format_strings() 192 | 193 | command = "github release latest st2contrib" 194 | expected_parameters = { 195 | 'repository': "st2contrib", 196 | 'github_type': None 197 | } 198 | 199 | self.assertExtractedParametersMatch(format_string=format_string, 200 | command=command, 201 | parameters=expected_parameters) 202 | self.assertCommandMatchesExactlyOneFormatString( 203 | format_strings=format_strings, 204 | command=command) 205 | 206 | def test_alias_latest_release_full(self): 207 | format_string = self.action_alias_db.formats[0]['representation'][0] 208 | format_strings = self.action_alias_db.get_format_strings() 209 | 210 | command = "github release latest type online st2contrib" 211 | expected_parameters = { 212 | 'repository': "st2contrib", 213 | 'github_type': "online" 214 | } 215 | 216 | self.assertExtractedParametersMatch(format_string=format_string, 217 | command=command, 218 | parameters=expected_parameters) 219 | self.assertCommandMatchesExactlyOneFormatString( 220 | format_strings=format_strings, 221 | command=command) 222 | 223 | 224 | class ListRelease(BaseActionAliasTestCase): 225 | action_alias_name = "list_releases" 226 | 227 | def test_alias_list_release_simple(self): 228 | format_string = self.action_alias_db.formats[0]['representation'][0] 229 | format_strings = self.action_alias_db.get_format_strings() 230 | 231 | command = "github releases list st2contrib" 232 | expected_parameters = { 233 | 'repository': "st2contrib", 234 | 'github_type': None 235 | } 236 | 237 | self.assertExtractedParametersMatch(format_string=format_string, 238 | command=command, 239 | parameters=expected_parameters) 240 | self.assertCommandMatchesExactlyOneFormatString( 241 | format_strings=format_strings, 242 | command=command) 243 | 244 | def test_alias_list_release_full(self): 245 | format_string = self.action_alias_db.formats[0]['representation'][0] 246 | format_strings = self.action_alias_db.get_format_strings() 247 | 248 | command = "github releases list type online st2contrib" 249 | expected_parameters = { 250 | 'repository': "st2contrib", 251 | 'github_type': "online" 252 | } 253 | 254 | self.assertExtractedParametersMatch(format_string=format_string, 255 | command=command, 256 | parameters=expected_parameters) 257 | self.assertCommandMatchesExactlyOneFormatString( 258 | format_strings=format_strings, 259 | command=command) 260 | 261 | 262 | class StoreOauthToken(BaseActionAliasTestCase): 263 | action_alias_name = "store_oauth_token" 264 | 265 | def test_alias_store_oauth_token_default(self): 266 | format_string = self.action_alias_db.formats[0]['representation'][0] 267 | format_strings = self.action_alias_db.get_format_strings() 268 | 269 | command = "github store token foobar" 270 | expected_parameters = { 271 | 'token': "foobar", 272 | 'github_type': None 273 | } 274 | 275 | self.assertExtractedParametersMatch(format_string=format_string, 276 | command=command, 277 | parameters=expected_parameters) 278 | self.assertCommandMatchesExactlyOneFormatString( 279 | format_strings=format_strings, 280 | command=command) 281 | 282 | def test_alias_store_oauth_token_online(self): 283 | format_string = self.action_alias_db.formats[0]['representation'][0] 284 | format_strings = self.action_alias_db.get_format_strings() 285 | 286 | command = "github store type online token foobar" 287 | expected_parameters = { 288 | 'token': "foobar", 289 | 'github_type': "online" 290 | } 291 | 292 | self.assertExtractedParametersMatch(format_string=format_string, 293 | command=command, 294 | parameters=expected_parameters) 295 | self.assertCommandMatchesExactlyOneFormatString( 296 | format_strings=format_strings, 297 | command=command) 298 | 299 | def test_alias_store_oauth_token_enterprise(self): 300 | format_string = self.action_alias_db.formats[0]['representation'][0] 301 | format_strings = self.action_alias_db.get_format_strings() 302 | 303 | command = "github store type enterprise token foobar" 304 | expected_parameters = { 305 | 'token': "foobar", 306 | 'github_type': "enterprise" 307 | } 308 | 309 | self.assertExtractedParametersMatch(format_string=format_string, 310 | command=command, 311 | parameters=expected_parameters) 312 | self.assertCommandMatchesExactlyOneFormatString( 313 | format_strings=format_strings, 314 | command=command) 315 | -------------------------------------------------------------------------------- /tests/test_action_check_deployment_env.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from check_deployment_env import CheckDeploymentEnvAction 20 | 21 | 22 | class CheckDeploymentEnvActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = CheckDeploymentEnvAction 25 | 26 | def test_run_matching_env(self): 27 | action = self.get_action_instance(self.full_config) 28 | self.assertTrue(action.run("production")) 29 | 30 | def test_run_non_matching_env(self): 31 | action = self.get_action_instance(self.full_config) 32 | self.assertRaises(ValueError, 33 | action.run, 34 | "staging") 35 | -------------------------------------------------------------------------------- /tests/test_action_create_deployment.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from create_deployment import CreateDeploymentAction 20 | 21 | 22 | class CreateDeploymentActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = CreateDeploymentAction 25 | -------------------------------------------------------------------------------- /tests/test_action_create_deployment_status.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from create_deployment_status import CreateDeploymentAction 20 | 21 | 22 | class CreateDeploymentActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = CreateDeploymentAction 25 | -------------------------------------------------------------------------------- /tests/test_action_create_file.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | from github_base_action_test_case import GitHubBaseActionTestCase 4 | from create_file import CreateFileAction 5 | 6 | from github import Github 7 | from unittest.mock import patch 8 | from unittest.mock import Mock 9 | 10 | 11 | class CreateFileTest(GitHubBaseActionTestCase): 12 | __test__ = True 13 | action_cls = CreateFileAction 14 | 15 | def setUp(self): 16 | super(CreateFileTest, self).setUp() 17 | 18 | self._test_data = {} 19 | # This is base parameters for running this action 20 | self.action_params = { 21 | 'user': 'st2-test', 22 | 'repo': 'StackStorm-Test', 23 | 'path': 'file-test', 24 | 'message': 'commit message', 25 | 'content': 'file content', 26 | } 27 | 28 | def test_create_file(self): 29 | def _side_effect(*args, **kwargs): 30 | self._test_data['content'] = kwargs['content'] 31 | return {'commit': Mock()} 32 | 33 | action = self.get_action_instance(self.full_config) 34 | 35 | with patch.object(Github, 'get_user', return_value=Mock()) as mock_user: 36 | mock_user.return_value.get_repo.return_value.create_file.side_effect = _side_effect 37 | action.run(**self.action_params) 38 | 39 | # This chceks sending content value is expected value 40 | self.assertEqual(self._test_data['content'], self.action_params['content']) 41 | 42 | def test_create_file_with_encoding_param(self): 43 | def _side_effect(*args, **kwargs): 44 | self._test_data['content'] = kwargs['content'] 45 | return {'commit': Mock()} 46 | 47 | action = self.get_action_instance(self.full_config) 48 | 49 | with patch.object(Github, 'get_user', return_value=Mock()) as mock_user: 50 | mock_user.return_value.get_repo.return_value.create_file.side_effect = _side_effect 51 | action.run(**dict(self.action_params, **{'encoding': 'base64'})) 52 | 53 | # This chceks sending content value is expected value 54 | self.assertEqual(self._test_data['content'], 55 | base64.b64encode(self.action_params['content'].encode('utf-8'))) 56 | -------------------------------------------------------------------------------- /tests/test_action_create_issue.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from create_issue import CreateIssueAction 20 | 21 | 22 | class CreateIssueActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = CreateIssueAction 25 | -------------------------------------------------------------------------------- /tests/test_action_create_release.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from create_release import CreateReleaseAction 20 | 21 | 22 | class CreateReleaseActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = CreateReleaseAction 25 | -------------------------------------------------------------------------------- /tests/test_action_get_clone_stats.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from get_clone_stats import GetCloneStatsAction 20 | 21 | 22 | class GetCloneStatsActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = GetCloneStatsAction 25 | -------------------------------------------------------------------------------- /tests/test_action_get_contents.py: -------------------------------------------------------------------------------- 1 | from github_base_action_test_case import GitHubBaseActionTestCase 2 | from get_contents import GetContentsAction 3 | 4 | from github import Github 5 | from unittest.mock import patch 6 | from unittest.mock import Mock 7 | 8 | 9 | class AddCommentActionTestCase(GitHubBaseActionTestCase): 10 | __test__ = True 11 | action_cls = GetContentsAction 12 | 13 | def setUp(self): 14 | super(AddCommentActionTestCase, self).setUp() 15 | 16 | # This is base parameters for running this action 17 | self.action_params = { 18 | 'user': 'st2-test', 19 | 'repo': 'StackStorm-Test', 20 | 'ref': 'HEAD', 21 | 'path': 'file-test', 22 | } 23 | 24 | # There are mock data-set of returned contents 25 | _mock_data_base = { 26 | 'size': 'test-size', 27 | 'name': 'test-name', 28 | 'path': 'test-path', 29 | 'sha': 'test-sha', 30 | 'url': 'https://example.com/test-url', 31 | 'git_url': 'https://example.com/test-git_url', 32 | 'html_url': 'https://example.com/test-html_url', 33 | 'download_url': 'https://example.com/test-download_url', 34 | } 35 | self.mock_data_for_file = dict(_mock_data_base, **{ 36 | 'type': 'file', 37 | 'encoding': 'base64', 38 | 'content': 'test-content', 39 | }) 40 | self.mock_data_for_symlink = dict(_mock_data_base, **{ 41 | 'type': 'symlink', 42 | 'target': 'test-target', 43 | }) 44 | self.mock_data_for_submodule = dict(_mock_data_base, **{ 45 | 'type': 'submodule', 46 | 'submodule_git_url': 'https://example.com/submodule_git_url', 47 | }) 48 | self.mock_data_for_directory = dict(_mock_data_base, **{ 49 | 'type': 'directory', 50 | }) 51 | 52 | def _confirm_returned_contents(self, action_params, mock_content, expected_content): 53 | """ 54 | This run github.get_contents action and confirm returned value is expected one. 55 | """ 56 | action = self.get_action_instance(self.full_config) 57 | 58 | # Configure mocks not to send requests to the GitHub 59 | with patch.object(Github, 'get_user', return_value=Mock()) as mock_user: 60 | mock_user.return_value.get_repo.return_value.get_contents.return_value = mock_content 61 | 62 | # Run github.get_contents action with decode parameter 63 | result = action.run(**action_params) 64 | 65 | self.assertEqual(result, expected_content) 66 | 67 | def test_get_file(self): 68 | mock_content = Mock() 69 | for (key, value) in self.mock_data_for_file.items(): 70 | setattr(mock_content, key, value) 71 | 72 | # run action and check returned contents 73 | self._confirm_returned_contents(self.action_params, mock_content, self.mock_data_for_file) 74 | 75 | def test_get_file_with_decode_param(self): 76 | mock_content = Mock() 77 | for (key, value) in self.mock_data_for_file.items(): 78 | setattr(mock_content, key, value) 79 | 80 | # This is in case of calling ContentFile.decoded_content 81 | mock_content.decoded_content = b'test-decoded-content' 82 | 83 | # run action and check returned contents 84 | params = dict(self.action_params, **{'decode': True}) 85 | self._confirm_returned_contents(params, mock_content, dict(self.mock_data_for_file, **{ 86 | 'content': 'test-decoded-content' 87 | })) 88 | 89 | def test_get_directory(self): 90 | mock_content = Mock() 91 | for (key, value) in self.mock_data_for_directory.items(): 92 | setattr(mock_content, key, value) 93 | 94 | # run action and check returned contents 95 | self._confirm_returned_contents(self.action_params, [mock_content], 96 | [self.mock_data_for_directory]) 97 | 98 | def test_get_symlink(self): 99 | mock_content = Mock() 100 | for (key, value) in self.mock_data_for_symlink.items(): 101 | setattr(mock_content, key, value) 102 | 103 | # run action and check returned contents 104 | self._confirm_returned_contents(self.action_params, [mock_content], 105 | [self.mock_data_for_symlink]) 106 | 107 | def test_get_submodule(self): 108 | mock_content = Mock() 109 | for (key, value) in self.mock_data_for_submodule.items(): 110 | setattr(mock_content, key, value) 111 | 112 | # run action and check returned contents 113 | self._confirm_returned_contents(self.action_params, [mock_content], 114 | [self.mock_data_for_submodule]) 115 | -------------------------------------------------------------------------------- /tests/test_action_get_deployment_statuses.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from get_deployment_statuses import GetDeploymentStatusesAction 20 | 21 | 22 | class GetDeploymentStatusesActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = GetDeploymentStatusesAction 25 | -------------------------------------------------------------------------------- /tests/test_action_get_issue.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from get_issue import GetIssueAction 20 | 21 | 22 | class GetIssueActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = GetIssueAction 25 | -------------------------------------------------------------------------------- /tests/test_action_get_traffic_stats.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from get_traffic_stats import GetTrafficStatsAction 20 | 21 | 22 | class GetTrafficStatsActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = GetTrafficStatsAction 25 | -------------------------------------------------------------------------------- /tests/test_action_get_user.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from get_user import GetUserAction 20 | 21 | 22 | class GetUserActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = GetUserAction 25 | -------------------------------------------------------------------------------- /tests/test_action_latest_release.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from latest_release import LatestReleaseAction 20 | 21 | 22 | class LatestReleaseActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = LatestReleaseAction 25 | -------------------------------------------------------------------------------- /tests/test_action_list_deployments.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from list_deployments import ListDeploymentsAction 20 | 21 | 22 | class ListDeploymentsActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = ListDeploymentsAction 25 | -------------------------------------------------------------------------------- /tests/test_action_list_issues.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from list_issues import ListIssuesAction 20 | 21 | 22 | class ListIssuesActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = ListIssuesAction 25 | -------------------------------------------------------------------------------- /tests/test_action_list_releases.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from list_releases import ListReleasesAction 20 | 21 | 22 | class ListReleasesActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = ListReleasesAction 25 | -------------------------------------------------------------------------------- /tests/test_action_list_teams.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from list_teams import ListTeamsAction 20 | 21 | 22 | class ListTeamsActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = ListTeamsAction 25 | -------------------------------------------------------------------------------- /tests/test_action_store_oauth_token.py: -------------------------------------------------------------------------------- 1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | 15 | # from mock import MagicMock 16 | 17 | from github_base_action_test_case import GitHubBaseActionTestCase 18 | 19 | from store_oauth_token import StoreOauthTokenAction 20 | 21 | 22 | class StoreOauthTokenActionTestCase(GitHubBaseActionTestCase): 23 | __test__ = True 24 | action_cls = StoreOauthTokenAction 25 | 26 | def test_run_uses_online(self): 27 | expected = {'github_type': "online"} 28 | action = self.get_action_instance(self.enterprise_config) 29 | 30 | results = action.run(user="octocat", 31 | token="foo", 32 | github_type="online") 33 | 34 | self.assertEqual(results, expected) 35 | self.assertEqual("foo", 36 | action.action_service.get_value("token_octocat")) 37 | 38 | def test_run_uses_enterprise(self): 39 | expected = {'github_type': "enterprise"} 40 | action = self.get_action_instance(self.enterprise_config) 41 | 42 | results = action.run(user="octocat", 43 | token="foo", 44 | github_type="enterprise") 45 | 46 | self.assertEqual(results, expected) 47 | self.assertEqual("foo", 48 | action.action_service.get_value("token_enterprise_octocat")) 49 | 50 | def test_run_token_string_whitespace_start(self): 51 | expected = {'github_type': "online"} 52 | action = self.get_action_instance(self.full_config) 53 | 54 | results = action.run(user="octocat", 55 | token=" foo", 56 | github_type="online") 57 | 58 | self.assertEqual(results, expected) 59 | self.assertEqual("foo", 60 | action.action_service.get_value("token_octocat")) 61 | 62 | def test_run_token_string_whitespace_end(self): 63 | expected = {'github_type': "online"} 64 | action = self.get_action_instance(self.full_config) 65 | 66 | results = action.run(user="octocat", 67 | token="foo ", 68 | github_type="online") 69 | 70 | self.assertEqual(results, expected) 71 | self.assertEqual("foo", 72 | action.action_service.get_value("token_octocat")) 73 | 74 | def test_run_token_string_whitespace_both(self): 75 | expected = {'github_type': "online"} 76 | action = self.get_action_instance(self.full_config) 77 | 78 | results = action.run(user="octocat", 79 | token=" foo ", 80 | github_type="online") 81 | 82 | self.assertEqual(results, expected) 83 | self.assertEqual("foo", 84 | action.action_service.get_value("token_octocat")) 85 | -------------------------------------------------------------------------------- /tests/test_action_update_file.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | from github_base_action_test_case import GitHubBaseActionTestCase 4 | from update_file import UpdateFileAction 5 | 6 | from github import Github 7 | from unittest.mock import patch 8 | from unittest.mock import Mock 9 | 10 | 11 | class UpdateFileTest(GitHubBaseActionTestCase): 12 | __test__ = True 13 | action_cls = UpdateFileAction 14 | 15 | def setUp(self): 16 | super(UpdateFileTest, self).setUp() 17 | 18 | self._test_data = {} 19 | # This is base parameters for running this action 20 | self.action_params = { 21 | 'user': 'st2-test', 22 | 'repo': 'StackStorm-Test', 23 | 'path': 'file-test', 24 | 'sha': 'test-key', 25 | 'message': 'commit message', 26 | 'content': 'file content', 27 | } 28 | 29 | def test_update_file(self): 30 | def _side_effect(*args, **kwargs): 31 | self._test_data['content'] = kwargs['content'] 32 | return {'commit': Mock()} 33 | 34 | action = self.get_action_instance(self.full_config) 35 | 36 | with patch.object(Github, 'get_user', return_value=Mock()) as mock_user: 37 | mock_user.return_value.get_repo.return_value.update_file.side_effect = _side_effect 38 | action.run(**self.action_params) 39 | 40 | # This chceks sending content value is expected value 41 | self.assertEqual(self._test_data['content'], self.action_params['content']) 42 | 43 | def test_update_file_with_encoding_param(self): 44 | def _side_effect(*args, **kwargs): 45 | self._test_data['content'] = kwargs['content'] 46 | return {'commit': Mock()} 47 | 48 | action = self.get_action_instance(self.full_config) 49 | 50 | with patch.object(Github, 'get_user', return_value=Mock()) as mock_user: 51 | mock_user.return_value.get_repo.return_value.update_file.side_effect = _side_effect 52 | action.run(**dict(self.action_params, **{'encoding': 'base64'})) 53 | 54 | # This chceks sending content value is expected value 55 | self.assertEqual(self._test_data['content'], 56 | base64.b64encode(self.action_params['content'].encode('utf-8'))) 57 | --------------------------------------------------------------------------------