From aaa0f39f72bb1dc66fb74e0474dbfa1bd1aa3ca7 Mon Sep 17 00:00:00 2001 From: Maxime Brunet Date: Sun, 31 Oct 2021 11:09:25 -0700 Subject: [PATCH] bitbucket: Support Basic Auth (#2045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bitbucket: Support Basic Auth * Rename username to user * Document user/password options * Rename username to workspace * Deprecate username * Fix credentials_required error_message * Fix credentials_required error_message * Test user/password/workspace options and env vars * Update a test to use user/password/workspace for each module * Fix check auth arguments * Use required_one_of/required_together * Fix required typo * Fix fetch_access_token * Fix tests 🤞 * Switch things up in test_bitbucket_access_key * Fix username/password are None * Remove username/password properties, use params directly * Update plugins/doc_fragments/bitbucket.py Co-authored-by: Felix Fontein * Update plugins/module_utils/source_control/bitbucket.py Co-authored-by: Felix Fontein * Update plugins/module_utils/source_control/bitbucket.py Co-authored-by: Felix Fontein * Update plugins/module_utils/source_control/bitbucket.py Co-authored-by: Felix Fontein * Update plugins/modules/source_control/bitbucket/bitbucket_access_key.py Co-authored-by: Felix Fontein * Update plugins/modules/source_control/bitbucket/bitbucket_pipeline_key_pair.py Co-authored-by: Felix Fontein * Update plugins/modules/source_control/bitbucket/bitbucket_pipeline_known_host.py Co-authored-by: Felix Fontein * Update plugins/modules/source_control/bitbucket/bitbucket_pipeline_known_host.py Co-authored-by: Felix Fontein * Update plugins/modules/source_control/bitbucket/bitbucket_pipeline_variable.py Co-authored-by: Felix Fontein * Update plugins/modules/source_control/bitbucket/bitbucket_pipeline_variable.py Co-authored-by: Felix Fontein * Update plugins/modules/source_control/bitbucket/bitbucket_access_key.py Co-authored-by: Felix Fontein * Update plugins/modules/source_control/bitbucket/bitbucket_pipeline_key_pair.py Co-authored-by: Felix Fontein * Document OAuth/Basic Auth precedence * Apply suggestions from code review Co-authored-by: Felix Fontein * Remove no_log=False from user argument * Add changelog fragment * Correct wording and formatting in changelog Co-authored-by: Felix Fontein * Update changelogs/fragments/2045-bitbucket_support_basic_auth.yaml Co-authored-by: Felix Fontein Co-authored-by: Felix Fontein --- .../2045-bitbucket_support_basic_auth.yaml | 7 +++ plugins/doc_fragments/bitbucket.py | 41 +++++++++++++ .../module_utils/source_control/bitbucket.py | 58 ++++++++++--------- .../bitbucket/bitbucket_access_key.py | 48 ++++++++------- .../bitbucket/bitbucket_pipeline_key_pair.py | 36 ++++++------ .../bitbucket_pipeline_known_host.py | 42 +++++++------- .../bitbucket/bitbucket_pipeline_variable.py | 42 +++++++------- .../bitbucket/test_bitbucket_access_key.py | 7 +-- .../test_bitbucket_pipeline_key_pair.py | 7 +-- .../test_bitbucket_pipeline_known_host.py | 7 +-- .../test_bitbucket_pipeline_variable.py | 26 +++++++-- 11 files changed, 187 insertions(+), 134 deletions(-) create mode 100644 changelogs/fragments/2045-bitbucket_support_basic_auth.yaml create mode 100644 plugins/doc_fragments/bitbucket.py diff --git a/changelogs/fragments/2045-bitbucket_support_basic_auth.yaml b/changelogs/fragments/2045-bitbucket_support_basic_auth.yaml new file mode 100644 index 0000000000..f6e7fa9e48 --- /dev/null +++ b/changelogs/fragments/2045-bitbucket_support_basic_auth.yaml @@ -0,0 +1,7 @@ +--- +minor_changes: + - bitbucket_* modules - add ``user`` and ``password`` options for Basic authentication (https://github.com/ansible-collections/community.general/pull/2045). +deprecated_features: + - bitbucket_* modules - ``username`` options have been deprecated in favor of ``workspace`` and will be removed in community.general 6.0.0 (https://github.com/ansible-collections/community.general/pull/2045). +major_changes: + - "bitbucket_* modules - ``client_id`` is no longer marked as ``no_log=true``. If you relied on its value not showing up in logs and output, please mark the whole tasks with ``no_log: true`` (https://github.com/ansible-collections/community.general/pull/2045)." diff --git a/plugins/doc_fragments/bitbucket.py b/plugins/doc_fragments/bitbucket.py new file mode 100644 index 0000000000..28489356b1 --- /dev/null +++ b/plugins/doc_fragments/bitbucket.py @@ -0,0 +1,41 @@ +# -*- 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 + + +class ModuleDocFragment(object): + + # Standard documentation fragment + DOCUMENTATION = r''' +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 + user: + description: + - The username. + - If not set the environment variable C(BITBUCKET_USERNAME) will be used. + type: str + version_added: 4.0.0 + password: + description: + - The App password. + - If not set the environment variable C(BITBUCKET_PASSWORD) will be used. + type: str + version_added: 4.0.0 +notes: + - Bitbucket OAuth consumer key and secret can be obtained from Bitbucket profile -> Settings -> Access Management -> OAuth. + - Bitbucket App password can be created from Bitbucket profile -> Personal Settings -> App passwords. + - If both OAuth and Basic Auth credentials are passed, OAuth credentials take precedence. +''' diff --git a/plugins/module_utils/source_control/bitbucket.py b/plugins/module_utils/source_control/bitbucket.py index c24a25074a..1d584391d9 100644 --- a/plugins/module_utils/source_control/bitbucket.py +++ b/plugins/module_utils/source_control/bitbucket.py @@ -15,13 +15,6 @@ from ansible.module_utils.urls import fetch_url, basic_auth_header class BitbucketHelper: BITBUCKET_API_URL = 'https://api.bitbucket.org' - error_messages = { - 'required_client_id': '`client_id` must be specified as a parameter or ' - 'BITBUCKET_CLIENT_ID environment variable', - 'required_client_secret': '`client_secret` must be specified as a parameter or ' - 'BITBUCKET_CLIENT_SECRET environment variable', - } - def __init__(self, module): self.module = module self.access_token = None @@ -29,35 +22,40 @@ class BitbucketHelper: @staticmethod def bitbucket_argument_spec(): return dict( - client_id=dict(type='str', no_log=True, fallback=(env_fallback, ['BITBUCKET_CLIENT_ID'])), + client_id=dict(type='str', fallback=(env_fallback, ['BITBUCKET_CLIENT_ID'])), client_secret=dict(type='str', no_log=True, fallback=(env_fallback, ['BITBUCKET_CLIENT_SECRET'])), + # TODO: + # - Rename user to username once current usage of username is removed + # - Alias user to username and deprecate it + user=dict(type='str', fallback=(env_fallback, ['BITBUCKET_USERNAME'])), + password=dict(type='str', no_log=True, fallback=(env_fallback, ['BITBUCKET_PASSWORD'])), ) - def check_arguments(self): - if self.module.params['client_id'] is None: - self.module.fail_json(msg=self.error_messages['required_client_id']) + @staticmethod + def bitbucket_required_one_of(): + return [['client_id', 'client_secret', 'user', 'password']] - if self.module.params['client_secret'] is None: - self.module.fail_json(msg=self.error_messages['required_client_secret']) + @staticmethod + def bitbucket_required_together(): + return [['client_id', 'client_secret'], ['user', 'password']] def fetch_access_token(self): - self.check_arguments() + if self.module.params['client_id'] and self.module.params['client_secret']: + headers = { + 'Authorization': basic_auth_header(self.module.params['client_id'], self.module.params['client_secret']), + } - headers = { - 'Authorization': basic_auth_header(self.module.params['client_id'], self.module.params['client_secret']) - } + info, content = self.request( + api_url='https://bitbucket.org/site/oauth2/access_token', + method='POST', + data='grant_type=client_credentials', + headers=headers, + ) - info, content = self.request( - api_url='https://bitbucket.org/site/oauth2/access_token', - method='POST', - data='grant_type=client_credentials', - headers=headers, - ) - - if info['status'] == 200: - self.access_token = content['access_token'] - else: - self.module.fail_json(msg='Failed to retrieve access token: {0}'.format(info)) + if info['status'] == 200: + self.access_token = content['access_token'] + else: + self.module.fail_json(msg='Failed to retrieve access token: {0}'.format(info)) def request(self, api_url, method, data=None, headers=None): headers = headers or {} @@ -66,6 +64,10 @@ class BitbucketHelper: headers.update({ 'Authorization': 'Bearer {0}'.format(self.access_token), }) + elif self.module.params['user'] and self.module.params['password']: + headers.update({ + 'Authorization': basic_auth_header(self.module.params['user'], self.module.params['password']), + }) if isinstance(data, dict): data = self.module.jsonify(data) diff --git a/plugins/modules/source_control/bitbucket/bitbucket_access_key.py b/plugins/modules/source_control/bitbucket/bitbucket_access_key.py index 6e16b267ea..6451d72909 100644 --- a/plugins/modules/source_control/bitbucket/bitbucket_access_key.py +++ b/plugins/modules/source_control/bitbucket/bitbucket_access_key.py @@ -15,27 +15,21 @@ description: - Manages Bitbucket repository access keys (also called deploy keys). author: - Evgeniy Krysanov (@catcombo) +extends_documentation_fragment: + - community.general.bitbucket 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: + workspace: description: - The repository owner. + - Alias I(username) has been deprecated and will become an alias of I(user) in community.general 6.0.0. type: str required: true + aliases: [ username ] key: description: - The SSH public key. @@ -52,8 +46,7 @@ options: 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. + - Bitbucket OAuth consumer or App password should have permissions to read and administrate account repositories. - Check mode is supported. ''' @@ -61,7 +54,7 @@ EXAMPLES = r''' - name: Create access key community.general.bitbucket_access_key: repository: 'bitbucket-repo' - username: bitbucket_username + workspace: bitbucket_workspace key: '{{lookup("file", "bitbucket.pub") }}' label: 'Bitbucket' state: present @@ -69,7 +62,7 @@ EXAMPLES = r''' - name: Delete access key community.general.bitbucket_access_key: repository: bitbucket-repo - username: bitbucket_username + workspace: bitbucket_workspace label: Bitbucket state: absent ''' @@ -82,13 +75,13 @@ from ansible_collections.community.general.plugins.module_utils.source_control.b 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_workspace_or_repo': 'Invalid `repository` or `workspace`', '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, + 'deploy-key-list': '%s/2.0/repositories/{workspace}/{repo_slug}/deploy-keys/' % BitbucketHelper.BITBUCKET_API_URL, + 'deploy-key-detail': '%s/2.0/repositories/{workspace}/{repo_slug}/deploy-keys/{key_id}' % BitbucketHelper.BITBUCKET_API_URL, } @@ -138,7 +131,7 @@ def get_existing_deploy_key(module, bitbucket): """ content = { 'next': BITBUCKET_API_ENDPOINTS['deploy-key-list'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], ) } @@ -151,7 +144,7 @@ def get_existing_deploy_key(module, bitbucket): ) if info['status'] == 404: - module.fail_json(msg=error_messages['invalid_username_or_repo']) + module.fail_json(msg=error_messages['invalid_workspace_or_repo']) if info['status'] == 403: module.fail_json(msg=error_messages['required_permission']) @@ -170,7 +163,7 @@ def get_existing_deploy_key(module, bitbucket): def create_deploy_key(module, bitbucket): info, content = bitbucket.request( api_url=BITBUCKET_API_ENDPOINTS['deploy-key-list'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], ), method='POST', @@ -181,7 +174,7 @@ def create_deploy_key(module, bitbucket): ) if info['status'] == 404: - module.fail_json(msg=error_messages['invalid_username_or_repo']) + module.fail_json(msg=error_messages['invalid_workspace_or_repo']) if info['status'] == 403: module.fail_json(msg=error_messages['required_permission']) @@ -199,7 +192,7 @@ def create_deploy_key(module, bitbucket): 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'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], key_id=key_id, ), @@ -207,7 +200,7 @@ def delete_deploy_key(module, bitbucket, key_id): ) if info['status'] == 404: - module.fail_json(msg=error_messages['invalid_username_or_repo']) + module.fail_json(msg=error_messages['invalid_workspace_or_repo']) if info['status'] == 403: module.fail_json(msg=error_messages['required_permission']) @@ -223,7 +216,10 @@ def main(): argument_spec = BitbucketHelper.bitbucket_argument_spec() argument_spec.update( repository=dict(type='str', required=True), - username=dict(type='str', required=True), + workspace=dict( + type='str', aliases=['username'], required=True, + deprecated_aliases=[dict(name='username', version='6.0.0', collection_name='community.general')], + ), key=dict(type='str', no_log=False), label=dict(type='str', required=True), state=dict(type='str', choices=['present', 'absent'], required=True), @@ -231,6 +227,8 @@ def main(): module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, + required_one_of=BitbucketHelper.bitbucket_required_one_of(), + required_together=BitbucketHelper.bitbucket_required_together(), ) bitbucket = BitbucketHelper(module) diff --git a/plugins/modules/source_control/bitbucket/bitbucket_pipeline_key_pair.py b/plugins/modules/source_control/bitbucket/bitbucket_pipeline_key_pair.py index ab3b7ec4b7..5d42419dfa 100644 --- a/plugins/modules/source_control/bitbucket/bitbucket_pipeline_key_pair.py +++ b/plugins/modules/source_control/bitbucket/bitbucket_pipeline_key_pair.py @@ -15,27 +15,21 @@ description: - Manages Bitbucket pipeline SSH key pair. author: - Evgeniy Krysanov (@catcombo) +extends_documentation_fragment: + - community.general.bitbucket options: - client_id: - description: - - OAuth consumer key. - - If not set the environment variable C(BITBUCKET_CLIENT_ID) will be used. - type: str - client_secret: - description: - - 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: + workspace: description: - The repository owner. + - Alias I(username) has been deprecated and will become an alias of I(user) in community.general 6.0.0. type: str required: true + aliases: [ username ] public_key: description: - The public key. @@ -51,7 +45,6 @@ options: required: true choices: [ absent, present ] notes: - - Bitbucket OAuth consumer key and secret can be obtained from Bitbucket profile -> Settings -> Access Management -> OAuth. - Check mode is supported. ''' @@ -59,7 +52,7 @@ EXAMPLES = r''' - name: Create or update SSH key pair community.general.bitbucket_pipeline_key_pair: repository: 'bitbucket-repo' - username: bitbucket_username + workspace: bitbucket_workspace public_key: '{{lookup("file", "bitbucket.pub") }}' private_key: '{{lookup("file", "bitbucket") }}' state: present @@ -67,7 +60,7 @@ EXAMPLES = r''' - name: Remove SSH key pair community.general.bitbucket_pipeline_key_pair: repository: bitbucket-repo - username: bitbucket_username + workspace: bitbucket_workspace state: absent ''' @@ -82,7 +75,7 @@ error_messages = { } BITBUCKET_API_ENDPOINTS = { - 'ssh-key-pair': '%s/2.0/repositories/{username}/{repo_slug}/pipelines_config/ssh/key_pair' % BitbucketHelper.BITBUCKET_API_URL, + 'ssh-key-pair': '%s/2.0/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/key_pair' % BitbucketHelper.BITBUCKET_API_URL, } @@ -104,7 +97,7 @@ def get_existing_ssh_key_pair(module, bitbucket): } """ api_url = BITBUCKET_API_ENDPOINTS['ssh-key-pair'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], ) @@ -123,7 +116,7 @@ def get_existing_ssh_key_pair(module, bitbucket): def update_ssh_key_pair(module, bitbucket): info, content = bitbucket.request( api_url=BITBUCKET_API_ENDPOINTS['ssh-key-pair'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], ), method='PUT', @@ -143,7 +136,7 @@ def update_ssh_key_pair(module, bitbucket): def delete_ssh_key_pair(module, bitbucket): info, content = bitbucket.request( api_url=BITBUCKET_API_ENDPOINTS['ssh-key-pair'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], ), method='DELETE', @@ -160,7 +153,10 @@ def main(): argument_spec = BitbucketHelper.bitbucket_argument_spec() argument_spec.update( repository=dict(type='str', required=True), - username=dict(type='str', required=True), + workspace=dict( + type='str', aliases=['username'], required=True, + deprecated_aliases=[dict(name='username', version='6.0.0', collection_name='community.general')], + ), public_key=dict(type='str'), private_key=dict(type='str', no_log=True), state=dict(type='str', choices=['present', 'absent'], required=True), @@ -168,6 +164,8 @@ def main(): module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, + required_one_of=BitbucketHelper.bitbucket_required_one_of(), + required_together=BitbucketHelper.bitbucket_required_together(), ) bitbucket = BitbucketHelper(module) diff --git a/plugins/modules/source_control/bitbucket/bitbucket_pipeline_known_host.py b/plugins/modules/source_control/bitbucket/bitbucket_pipeline_known_host.py index 356b09c035..9f4f2b9498 100644 --- a/plugins/modules/source_control/bitbucket/bitbucket_pipeline_known_host.py +++ b/plugins/modules/source_control/bitbucket/bitbucket_pipeline_known_host.py @@ -16,29 +16,23 @@ description: - The host fingerprint will be retrieved automatically, but in case of an error, one can use I(key) field to specify it manually. author: - Evgeniy Krysanov (@catcombo) +extends_documentation_fragment: + - community.general.bitbucket requirements: - paramiko 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: + workspace: description: - The repository owner. + - Alias I(username) has been deprecated and will become an alias of I(user) in community.general 6.0.0. type: str required: true + aliases: [ username ] name: description: - The FQDN of the known host. @@ -55,7 +49,6 @@ options: required: true choices: [ absent, present ] notes: - - Bitbucket OAuth consumer key and secret can be obtained from Bitbucket profile -> Settings -> Access Management -> OAuth. - Check mode is supported. ''' @@ -63,7 +56,7 @@ EXAMPLES = r''' - name: Create known hosts from the list community.general.bitbucket_pipeline_known_host: repository: 'bitbucket-repo' - username: bitbucket_username + workspace: bitbucket_workspace name: '{{ item }}' state: present with_items: @@ -73,14 +66,14 @@ EXAMPLES = r''' - name: Remove known host community.general.bitbucket_pipeline_known_host: repository: bitbucket-repo - username: bitbucket_username + workspace: bitbucket_workspace name: bitbucket.org state: absent - name: Specify public key file community.general.bitbucket_pipeline_known_host: repository: bitbucket-repo - username: bitbucket_username + workspace: bitbucket_workspace name: bitbucket.org key: '{{lookup("file", "bitbucket.pub") }}' state: absent @@ -105,8 +98,8 @@ error_messages = { } BITBUCKET_API_ENDPOINTS = { - 'known-host-list': '%s/2.0/repositories/{username}/{repo_slug}/pipelines_config/ssh/known_hosts/' % BitbucketHelper.BITBUCKET_API_URL, - 'known-host-detail': '%s/2.0/repositories/{username}/{repo_slug}/pipelines_config/ssh/known_hosts/{known_host_uuid}' % BitbucketHelper.BITBUCKET_API_URL, + 'known-host-list': '%s/2.0/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts/' % BitbucketHelper.BITBUCKET_API_URL, + 'known-host-detail': '%s/2.0/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts/{known_host_uuid}' % BitbucketHelper.BITBUCKET_API_URL, } @@ -137,7 +130,7 @@ def get_existing_known_host(module, bitbucket): """ content = { 'next': BITBUCKET_API_ENDPOINTS['known-host-list'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], ) } @@ -150,7 +143,7 @@ def get_existing_known_host(module, bitbucket): ) if info['status'] == 404: - module.fail_json(msg='Invalid `repository` or `username`.') + module.fail_json(msg='Invalid `repository` or `workspace`.') if info['status'] != 200: module.fail_json(msg='Failed to retrieve list of known hosts: {0}'.format(info)) @@ -214,7 +207,7 @@ def create_known_host(module, bitbucket): info, content = bitbucket.request( api_url=BITBUCKET_API_ENDPOINTS['known-host-list'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], ), method='POST', @@ -240,7 +233,7 @@ def create_known_host(module, bitbucket): def delete_known_host(module, bitbucket, known_host_uuid): info, content = bitbucket.request( api_url=BITBUCKET_API_ENDPOINTS['known-host-detail'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], known_host_uuid=known_host_uuid, ), @@ -261,7 +254,10 @@ def main(): argument_spec = BitbucketHelper.bitbucket_argument_spec() argument_spec.update( repository=dict(type='str', required=True), - username=dict(type='str', required=True), + workspace=dict( + type='str', aliases=['username'], required=True, + deprecated_aliases=[dict(name='username', version='6.0.0', collection_name='community.general')], + ), name=dict(type='str', required=True), key=dict(type='str', no_log=False), state=dict(type='str', choices=['present', 'absent'], required=True), @@ -269,6 +265,8 @@ def main(): module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, + required_one_of=BitbucketHelper.bitbucket_required_one_of(), + required_together=BitbucketHelper.bitbucket_required_together(), ) if (module.params['key'] is None) and (not HAS_PARAMIKO): diff --git a/plugins/modules/source_control/bitbucket/bitbucket_pipeline_variable.py b/plugins/modules/source_control/bitbucket/bitbucket_pipeline_variable.py index 735db8b784..e5701184c3 100644 --- a/plugins/modules/source_control/bitbucket/bitbucket_pipeline_variable.py +++ b/plugins/modules/source_control/bitbucket/bitbucket_pipeline_variable.py @@ -15,27 +15,21 @@ description: - Manages Bitbucket pipeline variables. author: - Evgeniy Krysanov (@catcombo) +extends_documentation_fragment: + - community.general.bitbucket 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: + workspace: description: - The repository owner. + - Alias I(username) has been deprecated and will become an alias of I(user) in community.general 6.0.0. type: str required: true + aliases: [ username ] name: description: - The pipeline variable name. @@ -57,7 +51,6 @@ options: required: true choices: [ absent, present ] notes: - - Bitbucket OAuth consumer key and secret can be obtained from Bitbucket profile -> Settings -> Access Management -> OAuth. - Check mode is supported. - For secured values return parameter C(changed) is always C(True). ''' @@ -66,7 +59,7 @@ EXAMPLES = r''' - name: Create or update pipeline variables from the list community.general.bitbucket_pipeline_variable: repository: 'bitbucket-repo' - username: bitbucket_username + workspace: bitbucket_workspace name: '{{ item.name }}' value: '{{ item.value }}' secured: '{{ item.secured }}' @@ -78,7 +71,7 @@ EXAMPLES = r''' - name: Remove pipeline variable community.general.bitbucket_pipeline_variable: repository: bitbucket-repo - username: bitbucket_username + workspace: bitbucket_workspace name: AWS_ACCESS_KEY state: absent ''' @@ -93,8 +86,8 @@ error_messages = { } BITBUCKET_API_ENDPOINTS = { - 'pipeline-variable-list': '%s/2.0/repositories/{username}/{repo_slug}/pipelines_config/variables/' % BitbucketHelper.BITBUCKET_API_URL, - 'pipeline-variable-detail': '%s/2.0/repositories/{username}/{repo_slug}/pipelines_config/variables/{variable_uuid}' % BitbucketHelper.BITBUCKET_API_URL, + 'pipeline-variable-list': '%s/2.0/repositories/{workspace}/{repo_slug}/pipelines_config/variables/' % BitbucketHelper.BITBUCKET_API_URL, + 'pipeline-variable-detail': '%s/2.0/repositories/{workspace}/{repo_slug}/pipelines_config/variables/{variable_uuid}' % BitbucketHelper.BITBUCKET_API_URL, } @@ -120,7 +113,7 @@ def get_existing_pipeline_variable(module, bitbucket): The `value` key in dict is absent in case of secured variable. """ variables_base_url = BITBUCKET_API_ENDPOINTS['pipeline-variable-list'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], ) # Look through the all response pages in search of variable we need @@ -133,7 +126,7 @@ def get_existing_pipeline_variable(module, bitbucket): ) if info['status'] == 404: - module.fail_json(msg='Invalid `repository` or `username`.') + module.fail_json(msg='Invalid `repository` or `workspace`.') if info['status'] != 200: module.fail_json(msg='Failed to retrieve the list of pipeline variables: {0}'.format(info)) @@ -153,7 +146,7 @@ def get_existing_pipeline_variable(module, bitbucket): def create_pipeline_variable(module, bitbucket): info, content = bitbucket.request( api_url=BITBUCKET_API_ENDPOINTS['pipeline-variable-list'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], ), method='POST', @@ -174,7 +167,7 @@ def create_pipeline_variable(module, bitbucket): def update_pipeline_variable(module, bitbucket, variable_uuid): info, content = bitbucket.request( api_url=BITBUCKET_API_ENDPOINTS['pipeline-variable-detail'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], variable_uuid=variable_uuid, ), @@ -195,7 +188,7 @@ def update_pipeline_variable(module, bitbucket, variable_uuid): def delete_pipeline_variable(module, bitbucket, variable_uuid): info, content = bitbucket.request( api_url=BITBUCKET_API_ENDPOINTS['pipeline-variable-detail'].format( - username=module.params['username'], + workspace=module.params['workspace'], repo_slug=module.params['repository'], variable_uuid=variable_uuid, ), @@ -221,7 +214,10 @@ def main(): argument_spec = BitbucketHelper.bitbucket_argument_spec() argument_spec.update( repository=dict(type='str', required=True), - username=dict(type='str', required=True), + workspace=dict( + type='str', aliases=['username'], required=True, + deprecated_aliases=[dict(name='username', version='6.0.0', collection_name='community.general')], + ), name=dict(type='str', required=True), value=dict(type='str'), secured=dict(type='bool', default=False), @@ -230,6 +226,8 @@ def main(): module = BitBucketPipelineVariable( argument_spec=argument_spec, supports_check_mode=True, + required_one_of=BitbucketHelper.bitbucket_required_one_of(), + required_together=BitbucketHelper.bitbucket_required_together(), ) bitbucket = BitbucketHelper(module) diff --git a/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_access_key.py b/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_access_key.py index 654f565cc4..f92ec8e16d 100644 --- a/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_access_key.py +++ b/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_access_key.py @@ -29,15 +29,14 @@ class TestBucketAccessKeyModule(ModuleTestCase): 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', + 'user': 'ABC', + 'password': 'XXX', + 'workspace': 'name', 'repository': 'repo', 'key': 'public_key', 'label': 'key name', diff --git a/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_key_pair.py b/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_key_pair.py index 6c5ee0152e..50dda90258 100644 --- a/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_key_pair.py +++ b/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_key_pair.py @@ -28,15 +28,14 @@ class TestBucketPipelineKeyPairModule(ModuleTestCase): self.assertEqual(exec_info.exception.args[0]['msg'], self.module.error_messages['required_keys']) - @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') @patch.object(bitbucket_pipeline_key_pair, 'get_existing_ssh_key_pair', return_value=None) def test_create_keys(self, *args): with patch.object(self.module, 'update_ssh_key_pair') as update_ssh_key_pair_mock: with self.assertRaises(AnsibleExitJson) as exec_info: set_module_args({ - 'client_id': 'ABC', - 'client_secret': 'XXX', - 'username': 'name', + 'user': 'ABC', + 'password': 'XXX', + 'workspace': 'name', 'repository': 'repo', 'public_key': 'public', 'private_key': 'PRIVATE', diff --git a/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_known_host.py b/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_known_host.py index 5d797f41c4..199ec0b96a 100644 --- a/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_known_host.py +++ b/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_known_host.py @@ -37,16 +37,15 @@ class TestBucketPipelineKnownHostModule(ModuleTestCase): self.assertEqual(create_known_host_mock.call_count, 1) self.assertEqual(exec_info.exception.args[0]['changed'], True) - @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') @patch.object(BitbucketHelper, 'request', return_value=(dict(status=201), dict())) @patch.object(bitbucket_pipeline_known_host, 'get_existing_known_host', return_value=None) def test_create_known_host_with_key(self, *args): with patch.object(self.module, 'get_host_key') as get_host_key_mock: with self.assertRaises(AnsibleExitJson) as exec_info: set_module_args({ - 'client_id': 'ABC', - 'client_secret': 'XXX', - 'username': 'name', + 'user': 'ABC', + 'password': 'XXX', + 'workspace': 'name', 'repository': 'repo', 'name': 'bitbucket.org', 'key': 'ssh-rsa public', diff --git a/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_variable.py b/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_variable.py index e291b09f40..df085fb237 100644 --- a/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_variable.py +++ b/tests/unit/plugins/modules/source_control/bitbucket/test_bitbucket_pipeline_variable.py @@ -25,7 +25,7 @@ class TestBucketPipelineVariableModule(ModuleTestCase): }) self.module.main() - self.assertEqual(exec_info.exception.args[0]['msg'], BitbucketHelper.error_messages['required_client_id']) + self.assertEqual(exec_info.exception.args[0]['failed'], True) def test_missing_value_with_present_state(self): with self.assertRaises(AnsibleFailJson) as exec_info: @@ -47,7 +47,7 @@ class TestBucketPipelineVariableModule(ModuleTestCase): }) @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') @patch.object(bitbucket_pipeline_variable, 'get_existing_pipeline_variable', return_value=None) - def test_env_vars_params(self, *args): + def test_oauth_env_vars_params(self, *args): with self.assertRaises(AnsibleExitJson): set_module_args({ 'username': 'name', @@ -57,15 +57,29 @@ class TestBucketPipelineVariableModule(ModuleTestCase): }) self.module.main() - @patch.object(BitbucketHelper, 'fetch_access_token', return_value='token') + @patch.dict('os.environ', { + 'BITBUCKET_USERNAME': 'ABC', + 'BITBUCKET_PASSWORD': 'XXX', + }) + @patch.object(bitbucket_pipeline_variable, 'get_existing_pipeline_variable', return_value=None) + def test_basic_auth_env_vars_params(self, *args): + with self.assertRaises(AnsibleExitJson): + set_module_args({ + 'workspace': 'name', + 'repository': 'repo', + 'name': 'PIPELINE_VAR_NAME', + 'state': 'absent', + }) + self.module.main() + @patch.object(bitbucket_pipeline_variable, 'get_existing_pipeline_variable', return_value=None) def test_create_variable(self, *args): with patch.object(self.module, 'create_pipeline_variable') as create_pipeline_variable_mock: with self.assertRaises(AnsibleExitJson) as exec_info: set_module_args({ - 'client_id': 'ABC', - 'client_secret': 'XXX', - 'username': 'name', + 'user': 'ABC', + 'password': 'XXX', + 'workspace': 'name', 'repository': 'repo', 'name': 'PIPELINE_VAR_NAME', 'value': '42',