diff --git a/changelogs/fragments/8989-github-app-token-from-fact.yml b/changelogs/fragments/8989-github-app-token-from-fact.yml new file mode 100644 index 00000000000..6b36d95a62b --- /dev/null +++ b/changelogs/fragments/8989-github-app-token-from-fact.yml @@ -0,0 +1,2 @@ +minor_changes: + - github_app_access_token lookup plugin - adds new ``private_key`` parameter (https://github.com/ansible-collections/community.general/pull/8989). diff --git a/plugins/lookup/github_app_access_token.py b/plugins/lookup/github_app_access_token.py index 5cd99b81c75..1d3c526c334 100644 --- a/plugins/lookup/github_app_access_token.py +++ b/plugins/lookup/github_app_access_token.py @@ -19,7 +19,7 @@ key_path: description: - Path to your private key. - required: true + - Either O(key_path) or O(private_key) must be specified. type: path app_id: description: @@ -34,6 +34,12 @@ - Alternatively, you can use PyGithub (U(https://github.com/PyGithub/PyGithub)) to get your installation ID. required: true type: str + private_key: + description: + - GitHub App private key in PEM file format as string. + - Either O(key_path) or O(private_key) must be specified. + type: str + version_added: 10.0.0 token_expiry: description: - How long the token should last for in seconds. @@ -71,7 +77,7 @@ import json from ansible.module_utils.urls import open_url from ansible.module_utils.six.moves.urllib.error import HTTPError -from ansible.errors import AnsibleError +from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.plugins.lookup import LookupBase from ansible.utils.display import Display @@ -84,8 +90,10 @@ display = Display() -def read_key(path): +def read_key(path, private_key=None): try: + if private_key: + return jwk_from_pem(private_key.encode('utf-8')) with open(path, 'rb') as pem_file: return jwk_from_pem(pem_file.read()) except Exception as e: @@ -132,8 +140,8 @@ def post_request(generated_jwt, installation_id): return json_data.get('token') -def get_token(key_path, app_id, installation_id, expiry=600): - jwk = read_key(key_path) +def get_token(key_path, app_id, installation_id, private_key, expiry=600): + jwk = read_key(key_path, private_key) generated_jwt = encode_jwt(app_id, jwk, exp=expiry) return post_request(generated_jwt, installation_id) @@ -146,10 +154,16 @@ def run(self, terms, variables=None, **kwargs): self.set_options(var_options=variables, direct=kwargs) + if not (self.get_option("key_path") or self.get_option("private_key")): + raise AnsibleOptionsError("One of key_path or private_key is required") + if self.get_option("key_path") and self.get_option("private_key"): + raise AnsibleOptionsError("key_path and private_key are mutually exclusive") + t = get_token( self.get_option('key_path'), self.get_option('app_id'), self.get_option('installation_id'), + self.get_option('private_key'), self.get_option('token_expiry'), ) diff --git a/tests/integration/targets/github_app_access_token/files/app-private-key.pem b/tests/integration/targets/github_app_access_token/files/app-private-key.pem new file mode 100644 index 00000000000..06c6786ee6b --- /dev/null +++ b/tests/integration/targets/github_app_access_token/files/app-private-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAr/EjKUujUdliSX79ZlDwq/+RCnOF1JCrekWGOOK4YGqgfJBM +Z/CLHYTW+BQAH172NTEwLlegwJpXtalae9WVhyMs4sFm7nxSZsFjRK7Gof1tuFbD +i4+GGlu4kci7xVcxzoZoVvswX4Xw/9MCg/Je35H8xbugwsWYg+ou79e0wx0fYU4d +dwiUte8K+/d1l5acMQuqcnUfJLRmXvw3w7hyemB51EPTGqkpcA4KmYns1W12ianD +Zo9/d2kLC2mcyxDkHmqWCv9vfUVrKB7yIC8DU5uY/acFtBaVE1yyvI+1lCCkxNWX +5IDbpP/xRJk68B0WXKaU2IdFUVYSD48u3nSZoQIDAQABAoIBAGLL9KOevqIagK+m +qKKItuzOgOKuhisb5b6uRbWx0jkKBv6LhOwkzemQi6oYiQ0UpQqviU+sky80PCZd +Z9r7z5Bn9y+JzMQEeb0LwTNzNUUHa1JFHl9DA9nPQXBTmOUyllxTa0nUmZA6RV9S +XSo8snu2nYtnVdmpXYBNw3eY1/9rb1blXEZHLJbCTaTX3MuWDYuJ4G0K6EArjSwG +DDGhOWIWfkk3zZAHqdsrJxgqXIx2Cv9m40hmC0XMwqh8/H3j4kZZhdglJhNbvnBM +8ZKRzpMOP1hbGATmi9A1HU+o6BpdIl1dyMRiod3WjSS7CKvs8BVR0XMK/SXDV9Wl +Jy6kwYUCgYEA4HwzV/YT+cTb61VL5ICj871m5VMaGJD96dOrnX33QYRNw5aLRc35 +HMaJ1t5Bp0d2J5h0mPoQkSvQxuPYfaytTYknSUE/bObYNMEnII3XeTmA8ILlG7kV +8OQah66GMKjyHocie2PxUuu9BWtuPvZJDrOuR9Pmw5aH6+oiXBSM0YcCgYEAyKRW +FHtDGC8ZHgBaytaGvlbVo3RTKboQgYqzf9HdvzWHlSbeZVuCk6MWtSNU+5+26RBK +FCI8FTBxqY/vai9zRgp/1u3jY3N1WIsowBgBV7C84IP6gEr/FAJlx++Eqzfmx1W7 +lU3/0IJ/jYS7D6C4aADifo4aGF0mFHFBk7sfpZcCgYBaIyTOnf15XgVcIjy9/LVY +amXFkS+6S4XY/Og87dZ5VTGQZoN3vPPZDRNN1qKQE46q6Xlv74D1eZ10Lwq/s7VG +m9rNfEiGZs7Lp/8ZADtT7rYKXNS35AKeXkkU0AwLv9qwTVyYJRJCVGvqoC99UpEV +OSqyprBTOr9LCBFR3eKJQwKBgHXRqoqUZy3IWmN3qdj6aF1U+Fbnc/5IuHCZVhZ0 +0lX5xQgcrvOt7NttJWRwvvKTMwFhA18XS1jV/aioUNp1yqcSe0dmoeRAZGP+M4u5 +jPBFZGQim/LCF09UqRfi2nEAfpAHFAP0rYdvWh9sFbxzkFXiTx4pq8Eq0bWnW+64 +Lzk5AoGAVeV9KgqJZLbl2Vbii3bJOuvtNeHOkIYPIoU6kgox9qp1derccWuMtwLT +PjhnWuCAX5dN1d7Rve4EovkjvuomDuZy6NCQlmLA6ff2pxtcJAkGy8Blc5VQeWs9 +i9DUFz2Sx6olEO7MDykj4B6O2YNlAwb8xq1oivE24laZPufprwI= +-----END RSA PRIVATE KEY----- diff --git a/tests/integration/targets/github_app_access_token/files/app-private-key.pem.license b/tests/integration/targets/github_app_access_token/files/app-private-key.pem.license new file mode 100644 index 00000000000..a1390a69ed9 --- /dev/null +++ b/tests/integration/targets/github_app_access_token/files/app-private-key.pem.license @@ -0,0 +1,3 @@ +Copyright (c) Ansible Project +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/tests/integration/targets/github_app_access_token/tasks/main.yml b/tests/integration/targets/github_app_access_token/tasks/main.yml new file mode 100644 index 00000000000..9b7ba5d2c1a --- /dev/null +++ b/tests/integration/targets/github_app_access_token/tasks/main.yml @@ -0,0 +1,30 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Test code for the github_app_access_token plugin. +# +# Copyright (c) 2017-2018, Abhijeet Kasurde +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install JWT + ansible.builtin.pip: + name: + - jwt + +- name: Read file + ansible.builtin.set_fact: + github_app_private_key: "{{ lookup('ansible.builtin.file', 'app-private-key.pem') }}" + +- name: Generate Github App Token + register: github_app_access_token + ignore_errors: true + ansible.builtin.set_fact: + github_app_token: "{{ lookup('community.general.github_app_access_token', app_id=github_app_id, installation_id=github_app_installation_id, private_key=github_app_private_key) }}" + +- assert: + that: + - github_app_access_token is failed + - '"Github return error" in github_app_access_token.msg' diff --git a/tests/integration/targets/github_app_access_token/vars/main.yml b/tests/integration/targets/github_app_access_token/vars/main.yml new file mode 100644 index 00000000000..35bd49cd3e0 --- /dev/null +++ b/tests/integration/targets/github_app_access_token/vars/main.yml @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +github_app_id: 123456 +github_app_installation_id: 123456 diff --git a/tests/unit/plugins/lookup/test_github_app_access_token.py b/tests/unit/plugins/lookup/test_github_app_access_token.py index 4bf9c7e704e..7971335a476 100644 --- a/tests/unit/plugins/lookup/test_github_app_access_token.py +++ b/tests/unit/plugins/lookup/test_github_app_access_token.py @@ -32,7 +32,7 @@ def read(self): class TestLookupModule(unittest.TestCase): - def test_get_token(self): + def test_get_token_with_file(self): with patch.multiple("ansible_collections.community.general.plugins.lookup.github_app_access_token", open=mock_open(read_data="foo_bar"), open_url=MagicMock(return_value=MockResponse()), @@ -50,3 +50,21 @@ def test_get_token(self): token_expiry=600 ) ) + + def test_get_token_with_fact(self): + with patch.multiple("ansible_collections.community.general.plugins.lookup.github_app_access_token", + open_url=MagicMock(return_value=MockResponse()), + jwk_from_pem=MagicMock(return_value='private_key'), + jwt_instance=MockJWT(), + HAS_JWT=True): + lookup = lookup_loader.get('community.general.github_app_access_token') + self.assertListEqual( + [MockResponse.response_token], + lookup.run( + [], + app_id="app_id", + installation_id="installation_id", + private_key="foo_bar", + token_expiry=600 + ) + )