Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: github_repo does not apply defaults on existing repos #2386

Merged
merged 11 commits into from Nov 22, 2021
@@ -0,0 +1,2 @@
bugfixes:
- github_repo - ``private`` and ``description`` attributes should not be set to default values when the repo already exists (https://github.com/ansible-collections/community.general/pull/2386).
51 changes: 35 additions & 16 deletions plugins/modules/source_control/github/github_repo.py
Expand Up @@ -42,16 +42,18 @@
description:
description:
- Description for the repository.
- Defaults to empty if I(force_defaults=true), which is the default in this module.
- Defaults to empty if I(force_defaults=false) when creating a new repository.
- This is only used when I(state) is C(present).
type: str
default: ''
required: false
private:
description:
- Whether the new repository should be private or not.
- Whether the repository should be private or not.
- Defaults to C(false) if I(force_defaults=true), which is the default in this module.
- Defaults to C(false) if I(force_defaults=false) when creating a new repository.
- This is only used when I(state) is C(present).
type: bool
default: no
required: false
state:
description:
Expand All @@ -72,6 +74,13 @@
type: str
default: 'https://api.github.com'
version_added: "3.5.0"
force_defaults:
description:
- Overwrite current description and private attributes with defaults if not set.
atorrescogollo marked this conversation as resolved.
Show resolved Hide resolved
type: bool
default: true
required: false
atorrescogollo marked this conversation as resolved.
Show resolved Hide resolved
version_added: 4.1.0
requirements:
- PyGithub>=1.54
notes:
Expand All @@ -92,6 +101,7 @@
description: "Just for fun"
private: yes
state: present
force_defaults: no
register: result

- name: Delete the repository
Expand All @@ -117,7 +127,7 @@

GITHUB_IMP_ERR = None
try:
from github import Github, GithubException
from github import Github, GithubException, GithubObject
from github.GithubException import UnknownObjectException
HAS_GITHUB_PACKAGE = True
except Exception:
Expand All @@ -135,7 +145,7 @@ def authenticate(username=None, password=None, access_token=None, api_url=None):
return Github(base_url=api_url, login_or_token=username, password=password)


def create_repo(gh, name, organization=None, private=False, description='', check_mode=False):
def create_repo(gh, name, organization=None, private=None, description=None, check_mode=False):
result = dict(
changed=False,
repo=dict())
Expand All @@ -151,16 +161,21 @@ def create_repo(gh, name, organization=None, private=False, description='', chec
except UnknownObjectException:
if not check_mode:
repo = target.create_repo(
name=name, private=private, description=description)
name=name,
private=GithubObject.NotSet if private is None else private,
description=GithubObject.NotSet if description is None else description,
)
result['repo'] = repo.raw_data

result['changed'] = True

changes = {}
if repo is None or repo.raw_data['private'] != private:
changes['private'] = private
if repo is None or repo.raw_data['description'] != description:
changes['description'] = description
if private is not None:
if repo is None or repo.raw_data['private'] != private:
changes['private'] = private
if description is not None:
if repo is None or repo.raw_data['description'] not in (description, description or None):
changes['description'] = description

if changes:
if not check_mode:
Expand Down Expand Up @@ -193,6 +208,10 @@ def delete_repo(gh, name, organization=None, check_mode=False):


def run_module(params, check_mode=False):
if params['force_defaults']:
params['description'] = params['description'] or ''
params['private'] = params['private'] or False

gh = authenticate(
username=params['username'], password=params['password'], access_token=params['access_token'],
api_url=params['api_url'])
Expand All @@ -216,17 +235,17 @@ def run_module(params, check_mode=False):

def main():
module_args = dict(
username=dict(type='str', required=False, default=None),
password=dict(type='str', required=False, default=None, no_log=True),
access_token=dict(type='str', required=False,
default=None, no_log=True),
username=dict(type='str'),
password=dict(type='str', no_log=True),
access_token=dict(type='str', no_log=True),
name=dict(type='str', required=True),
state=dict(type='str', required=False, default="present",
choices=["present", "absent"]),
organization=dict(type='str', required=False, default=None),
private=dict(type='bool', required=False, default=False),
description=dict(type='str', required=False, default=''),
private=dict(type='bool'),
description=dict(type='str'),
api_url=dict(type='str', required=False, default='https://api.github.com'),
force_defaults=dict(type='bool', default=True),
)
module = AnsibleModule(
argument_spec=module_args,
Expand Down
Expand Up @@ -71,6 +71,28 @@ def get_repo_mock(url, request):
return response(200, content, headers, None, 5, request)


@urlmatch(netloc=r'api\.github\.com(:[0-9]+)?$', path=r'/repos/.*/.*', method="get")
def get_private_repo_mock(url, request):
match = re.search(
r"api\.github\.com(:[0-9]+)?/repos/(?P<org>[^/]+)/(?P<repo>[^/]+)", request.url)
org = match.group("org")
repo = match.group("repo")

# https://docs.github.com/en/rest/reference/repos#get-a-repository
headers = {'content-type': 'application/json'}
content = {
"name": repo,
"full_name": "{0}/{1}".format(org, repo),
"url": "https://api.github.com/repos/{0}/{1}".format(org, repo),
"private": True,
"description": "This your first repo!",
"default_branch": "master",
"allow_rebase_merge": True
}
content = json.dumps(content).encode("utf-8")
return response(200, content, headers, None, 5, request)


@urlmatch(netloc=r'api\.github\.com(:[0-9]+)?$', path=r'/orgs/.*/repos', method="post")
def create_new_org_repo_mock(url, request):
match = re.search(
Expand All @@ -83,8 +105,8 @@ def create_new_org_repo_mock(url, request):
content = {
"name": repo['name'],
"full_name": "{0}/{1}".format(org, repo['name']),
"private": repo['private'],
"description": repo['description']
"private": repo.get('private', False),
"description": repo.get('description')
}
content = json.dumps(content).encode("utf-8")
return response(201, content, headers, None, 5, request)
Expand All @@ -99,8 +121,8 @@ def create_new_user_repo_mock(url, request):
content = {
"name": repo['name'],
"full_name": "{0}/{1}".format("octocat", repo['name']),
"private": repo['private'],
"description": repo['description']
"private": repo.get('private', False),
"description": repo.get('description')
}
content = json.dumps(content).encode("utf-8")
return response(201, content, headers, None, 5, request)
Expand All @@ -120,8 +142,8 @@ def patch_repo_mock(url, request):
"name": repo,
"full_name": "{0}/{1}".format(org, repo),
"url": "https://api.github.com/repos/{0}/{1}".format(org, repo),
"private": body['private'],
"description": body['description'],
"private": body.get('private', False),
"description": body.get('description'),
"default_branch": "master",
"allow_rebase_merge": True
}
Expand Down Expand Up @@ -160,11 +182,34 @@ def test_create_new_org_repo(self):
"description": "Just for fun",
"private": False,
"state": "present",
"api_url": "https://api.github.com"
"api_url": "https://api.github.com",
"force_defaults": False,
})

self.assertEqual(result['changed'], True)
self.assertEqual(result['repo']['private'], False)
self.assertEqual(result['repo']['description'], 'Just for fun')

@with_httmock(get_orgs_mock)
@with_httmock(get_repo_notfound_mock)
@with_httmock(create_new_org_repo_mock)
def test_create_new_org_repo_incomplete(self):
result = github_repo.run_module({
'username': None,
'password': None,
"access_token": "mytoken",
"organization": "MyOrganization",
"name": "myrepo",
"description": None,
"private": None,
"state": "present",
"api_url": "https://api.github.com",
"force_defaults": False,
})

self.assertEqual(result['changed'], True)
self.assertEqual(result['repo']['private'], False)
self.assertEqual(result['repo']['description'], None)

@with_httmock(get_user_mock)
@with_httmock(get_repo_notfound_mock)
Expand All @@ -179,7 +224,8 @@ def test_create_new_user_repo(self):
"description": "Just for fun",
"private": True,
"state": "present",
"api_url": "https://api.github.com"
"api_url": "https://api.github.com",
"force_defaults": False,
})
self.assertEqual(result['changed'], True)
self.assertEqual(result['repo']['private'], True)
Expand All @@ -197,11 +243,31 @@ def test_patch_existing_org_repo(self):
"description": "Just for fun",
"private": True,
"state": "present",
"api_url": "https://api.github.com"
"api_url": "https://api.github.com",
"force_defaults": False,
})
self.assertEqual(result['changed'], True)
self.assertEqual(result['repo']['private'], True)

@with_httmock(get_orgs_mock)
@with_httmock(get_private_repo_mock)
def test_idempotency_existing_org_private_repo(self):
result = github_repo.run_module({
'username': None,
'password': None,
"access_token": "mytoken",
"organization": "MyOrganization",
"name": "myrepo",
"description": None,
"private": None,
"state": "present",
"api_url": "https://api.github.com",
"force_defaults": False,
})
self.assertEqual(result['changed'], False)
self.assertEqual(result['repo']['private'], True)
self.assertEqual(result['repo']['description'], 'This your first repo!')

@with_httmock(get_orgs_mock)
@with_httmock(get_repo_mock)
@with_httmock(delete_repo_mock)
Expand All @@ -215,7 +281,8 @@ def test_delete_org_repo(self):
"description": "Just for fun",
"private": False,
"state": "absent",
"api_url": "https://api.github.com"
"api_url": "https://api.github.com",
"force_defaults": False,
})
self.assertEqual(result['changed'], True)

Expand All @@ -232,7 +299,8 @@ def test_delete_user_repo(self):
"description": "Just for fun",
"private": False,
"state": "absent",
"api_url": "https://api.github.com"
"api_url": "https://api.github.com",
"force_defaults": False,
})
self.assertEqual(result['changed'], True)

Expand All @@ -249,7 +317,8 @@ def test_delete_org_repo_notfound(self):
"description": "Just for fun",
"private": True,
"state": "absent",
"api_url": "https://api.github.com"
"api_url": "https://api.github.com",
"force_defaults": False,
})
self.assertEqual(result['changed'], False)

Expand Down