From c914df354baa40049fe8ee7c7254b8a581931500 Mon Sep 17 00:00:00 2001 From: "Yury V. Zaytsev" Date: Fri, 29 Mar 2019 14:12:17 +0100 Subject: [PATCH] Add Bitbucket access key module (#54592) * Add Bitbucket access key module * Add Bitbucket access key tests * Remove superseded `bitbucket_deploy_key` module * Apply suggestions from code review --- .../bitbucket/bitbucket_access_key.py | 285 +++++++++++++++ .../source_control/bitbucket_deploy_key.py | 272 -------------- .../test_bitbucket_access_key.py | 337 ++++++++++++++++++ 3 files changed, 622 insertions(+), 272 deletions(-) create mode 100644 lib/ansible/modules/source_control/bitbucket/bitbucket_access_key.py delete mode 100644 lib/ansible/modules/source_control/bitbucket_deploy_key.py create mode 100644 test/units/modules/source_control/test_bitbucket_access_key.py diff --git a/lib/ansible/modules/source_control/bitbucket/bitbucket_access_key.py b/lib/ansible/modules/source_control/bitbucket/bitbucket_access_key.py new file mode 100644 index 0000000000..e69760b342 --- /dev/null +++ b/lib/ansible/modules/source_control/bitbucket/bitbucket_access_key.py @@ -0,0 +1,285 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Evgeniy Krysanov +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community', +} + +DOCUMENTATION = r''' +--- +module: bitbucket_access_key +short_description: Manages Bitbucket repository access keys +description: + - Manages Bitbucket repository access keys (also called deploy keys). +version_added: "2.8" +author: + - Evgeniy Krysanov (@catcombo) +options: + client_id: + description: + - The OAuth consumer key. + - If not set the environment variable C(BITBUCKET_CLIENT_ID) will be used. + type: str + client_secret: + description: + - The OAuth consumer secret. + - If not set the environment variable C(BITBUCKET_CLIENT_SECRET) will be used. + type: str + repository: + description: + - The repository name. + type: str + required: true + username: + description: + - The repository owner. + type: str + required: true + key: + description: + - The SSH public key. + type: str + label: + description: + - The key label. + type: str + required: true + state: + description: + - Indicates desired state of the access key. + type: str + required: true + choices: [ absent, present ] +notes: + - Bitbucket OAuth consumer key and secret can be obtained from Bitbucket profile -> Settings -> Access Management -> OAuth. + - Bitbucket OAuth consumer should have permissions to read and administrate account repositories. + - Check mode is supported. +''' + +EXAMPLES = r''' +- name: Create access key + bitbucket_access_key: + repository: 'bitbucket-repo' + username: bitbucket_username + key: '{{lookup("file", "bitbucket.pub") }}' + label: 'Bitbucket' + state: present + +- name: Delete access key + bitbucket_access_key: + repository: bitbucket-repo + username: bitbucket_username + label: Bitbucket + state: absent +''' + +RETURN = r''' # ''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.source_control.bitbucket import BitbucketHelper + +error_messages = { + 'required_key': '`key` is required when the `state` is `present`', + 'required_permission': 'OAuth consumer `client_id` should have permissions to read and administrate the repository', + 'invalid_username_or_repo': 'Invalid `repository` or `username`', + 'invalid_key': 'Invalid SSH key or key is already in use', +} + +BITBUCKET_API_ENDPOINTS = { + 'deploy-key-list': '%s/2.0/repositories/{username}/{repo_slug}/deploy-keys/' % BitbucketHelper.BITBUCKET_API_URL, + 'deploy-key-detail': '%s/2.0/repositories/{username}/{repo_slug}/deploy-keys/{key_id}' % BitbucketHelper.BITBUCKET_API_URL, +} + + +def get_existing_deploy_key(module, bitbucket): + """ + Search for an existing deploy key on Bitbucket + with the label specified in module param `label` + + :param module: instance of the :class:`AnsibleModule` + :param bitbucket: instance of the :class:`BitbucketHelper` + :return: existing deploy key or None if not found + :rtype: dict or None + + Return example:: + + { + "id": 123, + "label": "mykey", + "created_on": "2019-03-23T10:15:21.517377+00:00", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADA...AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5", + "type": "deploy_key", + "comment": "", + "last_used": None, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test" + }, + "html": { + "href": "https://bitbucket.org/mleu/test" + }, + "avatar": { + "href": "..." + } + }, + "type": "repository", + "name": "test", + "full_name": "mleu/test", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123" + } + }, + } + """ + content = { + 'next': BITBUCKET_API_ENDPOINTS['deploy-key-list'].format( + username=module.params['username'], + repo_slug=module.params['repository'], + ) + } + + # Look through the all response pages in search of deploy key we need + while 'next' in content: + info, content = bitbucket.request( + api_url=content['next'], + method='GET', + ) + + if info['status'] == 404: + module.fail_json(msg=error_messages['invalid_username_or_repo']) + + if info['status'] == 403: + module.fail_json(msg=error_messages['required_permission']) + + if info['status'] != 200: + module.fail_json(msg='Failed to retrieve the list of deploy keys: {0}'.format(info)) + + res = next(filter(lambda v: v['label'] == module.params['label'], content['values']), None) + + if res is not None: + return res + + return None + + +def create_deploy_key(module, bitbucket): + info, content = bitbucket.request( + api_url=BITBUCKET_API_ENDPOINTS['deploy-key-list'].format( + username=module.params['username'], + repo_slug=module.params['repository'], + ), + method='POST', + data={ + 'key': module.params['key'], + 'label': module.params['label'], + }, + ) + + if info['status'] == 404: + module.fail_json(msg=error_messages['invalid_username_or_repo']) + + if info['status'] == 403: + module.fail_json(msg=error_messages['required_permission']) + + if info['status'] == 400: + module.fail_json(msg=error_messages['invalid_key']) + + if info['status'] != 200: + module.fail_json(msg='Failed to create deploy key `{label}`: {info}'.format( + label=module.params['label'], + info=info, + )) + + +def delete_deploy_key(module, bitbucket, key_id): + info, content = bitbucket.request( + api_url=BITBUCKET_API_ENDPOINTS['deploy-key-detail'].format( + username=module.params['username'], + repo_slug=module.params['repository'], + key_id=key_id, + ), + method='DELETE', + ) + + if info['status'] == 404: + module.fail_json(msg=error_messages['invalid_username_or_repo']) + + if info['status'] == 403: + module.fail_json(msg=error_messages['required_permission']) + + if info['status'] != 204: + module.fail_json(msg='Failed to delete deploy key `{label}`: {info}'.format( + label=module.params['label'], + info=info, + )) + + +def main(): + argument_spec = BitbucketHelper.bitbucket_argument_spec() + argument_spec.update( + repository=dict(type='str', required=True), + username=dict(type='str', required=True), + key=dict(type='str'), + label=dict(type='str', required=True), + state=dict(type='str', choices=['present', 'absent'], required=True), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + bitbucket = BitbucketHelper(module) + + key = module.params['key'] + state = module.params['state'] + + # Check parameters + if (key is None) and (state == 'present'): + module.fail_json(msg=error_messages['required_key']) + + # Retrieve access token for authorized API requests + bitbucket.fetch_access_token() + + # Retrieve existing deploy key (if any) + existing_deploy_key = get_existing_deploy_key(module, bitbucket) + changed = False + + # Create new deploy key in case it doesn't exists + if not existing_deploy_key and (state == 'present'): + if not module.check_mode: + create_deploy_key(module, bitbucket) + changed = True + + # Update deploy key if the old value does not match the new one + elif existing_deploy_key and (state == 'present'): + if not key.startswith(existing_deploy_key.get('key')): + if not module.check_mode: + # Bitbucket doesn't support update key for the same label, + # so we need to delete the old one first + delete_deploy_key(module, bitbucket, existing_deploy_key['id']) + create_deploy_key(module, bitbucket) + changed = True + + # Delete deploy key + elif existing_deploy_key and (state == 'absent'): + if not module.check_mode: + delete_deploy_key(module, bitbucket, existing_deploy_key['id']) + changed = True + + module.exit_json(changed=changed) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/source_control/bitbucket_deploy_key.py b/lib/ansible/modules/source_control/bitbucket_deploy_key.py deleted file mode 100644 index 292beb203c..0000000000 --- a/lib/ansible/modules/source_control/bitbucket_deploy_key.py +++ /dev/null @@ -1,272 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - -ANSIBLE_METADATA = { - 'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['preview'] -} - -DOCUMENTATION = ''' ---- -module: bitbucket_deploy_key -version_added: "2.8" -author: "Ali (@bincyber)" -short_description: Manages deploy keys for BitBucket repositories. -description: - - "Adds or removes deploy keys for Bitbucket repositories. Supports authentication to the BitBucket API - using username and password." -options: - account_name: - description: - - The name of the team or individual account that owns the BitBucket repository. - required: True - type: str - aliases: [ 'account', 'organization' ] - repository: - description: - - The name of the BitBucket repository. - required: True - type: str - aliases: [ 'repo' ] - label: - description: - - The user-visible label on the deploy key. - required: True - type: str - key: - description: - - The SSH public key to add to the repository as a deploy key. - required: True - type: str - state: - description: - - The state of the deploy key. - required: False - default: "present" - choices: [ "present", "absent" ] - type: str - force: - description: - - If C(true), forcefully adds the deploy key by deleting any existing deploy key with the same public key or title. - required: False - default: False - choices: [ True, False ] - type: bool - username: - description: - - The username to authenticate with. - required: True - type: str - password: - description: - - The password to authenticate with. You can use app passwords here. - required: True - type: str -requirements: - - python-requests -notes: - - "Refer to BitBucket's API documentation here: U(https://confluence.atlassian.com/bitbucket/deploy-keys-resource-296095243.html)" -''' - -EXAMPLES = ''' -# add a new deploy key to a BitBucket repository -- bitbucket_deploy_key: - account_name: "johndoe" - repository: "example" - label: "new-deploy-key" - key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAwXxn7kIMNWzcDfou..." - username: "johndoe" - password: "supersecretpassword" - -# remove an existing deploy key from a BitBucket repository -- bitbucket_deploy_key: - account_name: "johndoe" - repository: "example" - label: "new-deploy-key" - key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAwXxn7kIMNWzcDfou..." - force: yes - username: "johndoe" - password: "supersecretpassword" - state: absent - -# add a new deploy key to a BitBucket repository, replace an existing key -- bitbucket_deploy_key: - account_name: "johndoe" - repository: "example" - label: "new-deploy-key" - key: "{{ lookup('file', '~/.ssh/bitbucket.pub') }}" - force: yes - username: "johndoe" - password: "supersecretpassword" - -# re-add a deploy key to a BitBucket repository but with a different label -- bitbucket_deploy_key: - account_name: "johndoe" - repository: "example" - label: "replace-deploy-key" - key: "{{ lookup('file', '~/.ssh/bitbucket.pub') }}" - username: "johndoe" - password: "supersecretpassword" -''' - -RETURN = ''' -msg: - description: the status message describing what occurred - returned: always - type: str - sample: "Deploy key added successfully" - -http_status_code: - description: the HTTP status code returned by the BitBucket API - returned: failed - type: int - sample: 400 - -pk: - description: the key identifier assigned by BitBucket for the deploy key - returned: changed - type: int - sample: 243819 -''' - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.urls import fetch_url -from ansible.module_utils.six.moves.urllib.parse import urlencode -import json - - -class BitBucketDeployKey(object): - def __init__(self, module=None, url=None, state=None): - self.module = module - self.url = url - self.state = state - - def get_existing_key(self, label, key, force): - resp, info = fetch_url(self.module, self.url, method="GET") - - status_code = info["status"] - - if status_code == 200: - response_body = json.loads(resp.read()) - - if response_body: - for i in response_body: - existing_key = str(i["pk"]) - if i["key"] == key: - return existing_key - elif i['label'] == label and force: - return existing_key - else: - if self.state == 'absent': - self.module.exit_json(changed=False, msg="Deploy key does not exist") - else: - return None - elif status_code == 401: - self.module.fail_json(msg="Failed to connect to bitbucket.org due to invalid credentials", http_status_code=status_code) - elif status_code == 404: - self.module.fail_json(msg="BitBucket repository does not exist", http_status_code=status_code) - else: - self.module.fail_json(msg="Failed to retrieve existing deploy keys", http_status_code=status_code) - - def add_new_key(self, label, key): - request_body = { - "label": label, - "key": key - } - - resp, info = fetch_url(self.module, self.url, data=urlencode(request_body), method="POST") - - status_code = info["status"] - - if status_code == 200: - response_body = json.loads(resp.read()) - key_id = response_body["pk"] - self.module.exit_json(changed=True, msg="Deploy key successfully added", pk=key_id) - elif status_code == 400: - existing_key_msg = "Someone has already added that access key to this repository" - if existing_key_msg in info["body"]: - self.module.exit_json(changed=False, msg="Deploy key already exists") - else: - self.module.fail_json(msg="Bad request", error=info["body"], data=json.dumps(request_body)) - elif status_code == 401: - self.module.fail_json(msg="Failed to connect to bitbucket.org due to invalid credentials", http_status_code=status_code) - elif status_code == 404: - self.module.fail_json(msg="BitBucket repository does not exist", http_status_code=status_code) - else: - self.module.fail_json(msg="Failed to add deploy key", http_status_code=status_code, error=info["body"]) - - def remove_existing_key(self, pk): - resp, info = fetch_url(self.module, self.url + '/' + pk, method="DELETE") - - status_code = info["status"] - - if status_code == 204: - if self.state == 'absent': - self.module.exit_json(changed=True, msg="Deploy key successfully deleted", pk=pk) - elif status_code == 401: - self.module.fail_json(msg="Failed to connect to bitbucket.org due to invalid credentials", http_status_code=status_code) - elif status_code == 404: - self.module.fail_json(msg="BitBucket repository does not exist", http_status_code=status_code) - else: - self.module.fail_json(msg="Failed to delete existing deploy key", pk=pk, http_status_code=status_code) - - -def main(): - - module = AnsibleModule( - argument_spec=dict( - account_name=dict(required=True, type='str', aliases=['account', 'organization']), - repository=dict(required=True, type='str', aliases=['repo']), - label=dict(required=True, type='str'), - key=dict(required=True, type='str'), - state=dict(default='present', choices=['present', 'absent']), - force=dict(required=False, type='bool', default=False, choices=[True, False]), - username=dict(required=True, type='str'), - password=dict(required=True, type='str', no_log=True), - ), - required_together=[ - ['username', 'password'] - ], - supports_check_mode=True, - ) - - account_name = module.params['account_name'] - repository = module.params['repository'] - label = module.params['label'] - key = module.params['key'] - state = module.params['state'] - force = module.params.get('force', False) - - module.params['url_username'] = module.params['username'] - module.params['url_password'] = module.params['password'] - module.params['force_basic_auth'] = True - - BITBUCKET_API_URL = "https://api.bitbucket.org/1.0/repositories/{0}/{1}/deploy-keys".format(account_name, repository) - - deploy_key = BitBucketDeployKey(module, BITBUCKET_API_URL, state) - - if module.check_mode: - pk = deploy_key.get_existing_key(label, key, force) - if state == "present" and pk is None: - module.exit_json(changed=True) - elif state == "present" and pk is not None: - module.exit_json(changed=False) - - # to forcefully modify an existing key, the existing key must be deleted first - if state == 'absent' or force: - pk = deploy_key.get_existing_key(label, key, force) - - if pk is not None: - deploy_key.remove_existing_key(pk) - - deploy_key.add_new_key(label, key) - - -if __name__ == '__main__': - main() diff --git a/test/units/modules/source_control/test_bitbucket_access_key.py b/test/units/modules/source_control/test_bitbucket_access_key.py new file mode 100644 index 0000000000..f1cb4fe71b --- /dev/null +++ b/test/units/modules/source_control/test_bitbucket_access_key.py @@ -0,0 +1,337 @@ +from ansible.module_utils.source_control.bitbucket import BitbucketHelper +from ansible.modules.source_control.bitbucket import bitbucket_access_key +from units.compat import unittest +from units.compat.mock import patch +from units.modules.utils import AnsibleFailJson, AnsibleExitJson, ModuleTestCase, set_module_args + + +class TestBucketAccessKeyModule(ModuleTestCase): + def setUp(self): + super(TestBucketAccessKeyModule, self).setUp() + self.module = bitbucket_access_key + + def test_missing_key_with_present_state(self): + with self.assertRaises(AnsibleFailJson) as exec_info: + set_module_args({ + 'client_id': 'ABC', + 'client_secret': 'XXX', + 'username': 'name', + 'repository': 'repo', + 'label': 'key name', + 'state': 'present', + }) + self.module.main() + + self.assertEqual(exec_info.exception.args[0]['msg'], self.module.error_messages['required_key']) + + @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') + @patch.object(bitbucket_access_key, 'get_existing_deploy_key', return_value=None) + def test_create_deploy_key(self, *args): + with patch.object(self.module, 'create_deploy_key') as create_deploy_key_mock: + with self.assertRaises(AnsibleExitJson) as exec_info: + set_module_args({ + 'client_id': 'ABC', + 'client_secret': 'XXX', + 'username': 'name', + 'repository': 'repo', + 'key': 'public_key', + 'label': 'key name', + 'state': 'present', + }) + self.module.main() + + self.assertEqual(create_deploy_key_mock.call_count, 1) + self.assertEqual(exec_info.exception.args[0]['changed'], True) + + @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') + @patch.object(bitbucket_access_key, 'get_existing_deploy_key', return_value=None) + def test_create_deploy_key_check_mode(self, *args): + with patch.object(self.module, 'create_deploy_key') as create_deploy_key_mock: + with self.assertRaises(AnsibleExitJson) as exec_info: + set_module_args({ + 'client_id': 'ABC', + 'client_secret': 'XXX', + 'username': 'name', + 'repository': 'repo', + 'key': 'public_key', + 'label': 'key name', + 'state': 'present', + '_ansible_check_mode': True, + }) + self.module.main() + + self.assertEqual(create_deploy_key_mock.call_count, 0) + self.assertEqual(exec_info.exception.args[0]['changed'], True) + + @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') + @patch.object(bitbucket_access_key, 'get_existing_deploy_key', return_value={ + "id": 123, + "label": "mykey", + "created_on": "2019-03-23T10:15:21.517377+00:00", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADA...AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5", + "type": "deploy_key", + "comment": "", + "last_used": None, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test" + }, + "html": { + "href": "https://bitbucket.org/mleu/test" + }, + "avatar": { + "href": "..." + } + }, + "type": "repository", + "name": "test", + "full_name": "mleu/test", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123" + } + }, + }) + def test_update_deploy_key(self, *args): + with patch.object(self.module, 'delete_deploy_key') as delete_deploy_key_mock: + with patch.object(self.module, 'create_deploy_key') as create_deploy_key_mock: + with self.assertRaises(AnsibleExitJson) as exec_info: + set_module_args({ + 'client_id': 'ABC', + 'client_secret': 'XXX', + 'username': 'name', + 'repository': 'repo', + 'key': 'new public key', + 'label': 'mykey', + 'state': 'present', + }) + self.module.main() + + self.assertEqual(delete_deploy_key_mock.call_count, 1) + self.assertEqual(create_deploy_key_mock.call_count, 1) + self.assertEqual(exec_info.exception.args[0]['changed'], True) + + @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') + @patch.object(bitbucket_access_key, 'get_existing_deploy_key', return_value={ + "id": 123, + "label": "mykey", + "created_on": "2019-03-23T10:15:21.517377+00:00", + "key": "new public key", + "type": "deploy_key", + "comment": "", + "last_used": None, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test" + }, + "html": { + "href": "https://bitbucket.org/mleu/test" + }, + "avatar": { + "href": "..." + } + }, + "type": "repository", + "name": "test", + "full_name": "mleu/test", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123" + } + }, + }) + def test_dont_update_same_value(self, *args): + with patch.object(self.module, 'delete_deploy_key') as delete_deploy_key_mock: + with patch.object(self.module, 'create_deploy_key') as create_deploy_key_mock: + with self.assertRaises(AnsibleExitJson) as exec_info: + set_module_args({ + 'client_id': 'ABC', + 'client_secret': 'XXX', + 'username': 'name', + 'repository': 'repo', + 'key': 'new public key', + 'label': 'mykey', + 'state': 'present', + }) + self.module.main() + + self.assertEqual(delete_deploy_key_mock.call_count, 0) + self.assertEqual(create_deploy_key_mock.call_count, 0) + self.assertEqual(exec_info.exception.args[0]['changed'], False) + + @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') + @patch.object(bitbucket_access_key, 'get_existing_deploy_key', return_value={ + "id": 123, + "label": "mykey", + "created_on": "2019-03-23T10:15:21.517377+00:00", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADA...AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5", + "type": "deploy_key", + "comment": "", + "last_used": None, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test" + }, + "html": { + "href": "https://bitbucket.org/mleu/test" + }, + "avatar": { + "href": "..." + } + }, + "type": "repository", + "name": "test", + "full_name": "mleu/test", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123" + } + }, + }) + def test_update_deploy_key_check_mode(self, *args): + with patch.object(self.module, 'delete_deploy_key') as delete_deploy_key_mock: + with patch.object(self.module, 'create_deploy_key') as create_deploy_key_mock: + with self.assertRaises(AnsibleExitJson) as exec_info: + set_module_args({ + 'client_id': 'ABC', + 'client_secret': 'XXX', + 'username': 'name', + 'repository': 'repo', + 'key': 'new public key', + 'label': 'mykey', + 'state': 'present', + '_ansible_check_mode': True, + }) + self.module.main() + + self.assertEqual(delete_deploy_key_mock.call_count, 0) + self.assertEqual(create_deploy_key_mock.call_count, 0) + self.assertEqual(exec_info.exception.args[0]['changed'], True) + + @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') + @patch.object(bitbucket_access_key, 'get_existing_deploy_key', return_value={ + "id": 123, + "label": "mykey", + "created_on": "2019-03-23T10:15:21.517377+00:00", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADA...AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5", + "type": "deploy_key", + "comment": "", + "last_used": None, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test" + }, + "html": { + "href": "https://bitbucket.org/mleu/test" + }, + "avatar": { + "href": "..." + } + }, + "type": "repository", + "name": "test", + "full_name": "mleu/test", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123" + } + }, + }) + def test_delete_deploy_key(self, *args): + with patch.object(self.module, 'delete_deploy_key') as delete_deploy_key_mock: + with self.assertRaises(AnsibleExitJson) as exec_info: + set_module_args({ + 'client_id': 'ABC', + 'client_secret': 'XXX', + 'username': 'name', + 'repository': 'repo', + 'label': 'mykey', + 'state': 'absent', + }) + self.module.main() + + self.assertEqual(delete_deploy_key_mock.call_count, 1) + self.assertEqual(exec_info.exception.args[0]['changed'], True) + + @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') + @patch.object(bitbucket_access_key, 'get_existing_deploy_key', return_value=None) + def test_delete_absent_deploy_key(self, *args): + with patch.object(self.module, 'delete_deploy_key') as delete_deploy_key_mock: + with self.assertRaises(AnsibleExitJson) as exec_info: + set_module_args({ + 'client_id': 'ABC', + 'client_secret': 'XXX', + 'username': 'name', + 'repository': 'repo', + 'label': 'mykey', + 'state': 'absent', + }) + self.module.main() + + self.assertEqual(delete_deploy_key_mock.call_count, 0) + self.assertEqual(exec_info.exception.args[0]['changed'], False) + + @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') + @patch.object(bitbucket_access_key, 'get_existing_deploy_key', return_value={ + "id": 123, + "label": "mykey", + "created_on": "2019-03-23T10:15:21.517377+00:00", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADA...AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5", + "type": "deploy_key", + "comment": "", + "last_used": None, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test" + }, + "html": { + "href": "https://bitbucket.org/mleu/test" + }, + "avatar": { + "href": "..." + } + }, + "type": "repository", + "name": "test", + "full_name": "mleu/test", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123" + } + }, + }) + def test_delete_deploy_key_check_mode(self, *args): + with patch.object(self.module, 'delete_deploy_key') as delete_deploy_key_mock: + with self.assertRaises(AnsibleExitJson) as exec_info: + set_module_args({ + 'client_id': 'ABC', + 'client_secret': 'XXX', + 'username': 'name', + 'repository': 'repo', + 'label': 'mykey', + 'state': 'absent', + '_ansible_check_mode': True, + }) + self.module.main() + + self.assertEqual(delete_deploy_key_mock.call_count, 0) + self.assertEqual(exec_info.exception.args[0]['changed'], True) + + +if __name__ == '__main__': + unittest.main()