Skip to content

Commit

Permalink
github_members_keys module
Browse files Browse the repository at this point in the history
  • Loading branch information
sestrella committed Aug 29, 2019
1 parent ed46f83 commit a1a1d34
Show file tree
Hide file tree
Showing 3 changed files with 348 additions and 0 deletions.
203 changes: 203 additions & 0 deletions lib/ansible/modules/source_control/github_members_keys.py
@@ -0,0 +1,203 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2019, Sebastián Estrella <sestrella.me@gmail.com>
# 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 = '''
---
module: github_members_keys
short_description: Fetches GitHub team members SSH keys
description:
- Fetches GitHub team members SSH keys
version_added: '2.9'
author:
- Sebastián Estrella (@sestrella)
options:
token:
description:
- GitHub API access token
type: str
required: true
organization:
description:
- GitHub organization name
type: str
required: true
team:
description:
- GitHub team name
type: str
required: true
mandatory_members:
description:
- List of members that must be part of the team
- If a member is not part of the team it raises an error
- If a member has no keys it raises an error
- Each member corresponds to a GitHub username
- Check used to avoid locking members out of a server
type: list
required: false
default: []
requirements:
- PyGithub
'''

EXAMPLES = '''
- name: Fetch team members SSH keys
local_action:
module: github_members_keys
token: token
organization: organization
team: team
mandatory_members:
- admin
register: result
- name: Set authorized_key taken from GitHub
authorized_key:
user: user
key: "{{ result.members_keys | join('\n') }}"
exclusive: yes
'''

RETURN = '''
members_keys:
description: A list of team members keys
type: list
returned: success
sample: ["ssh-rsa AAA... user1-1", "ssh-rsa BBB... user1-2"]
'''

import traceback

GITHUB_IMP_ERR = None
try:
import github
HAS_GITHUB = True
except ImportError:
HAS_GITHUB = False
GITHUB_IMP_ERR = traceback.format_exc()

import json

from ansible.module_utils.basic import AnsibleModule, missing_required_lib


class GithubClient:
def __init__(self, token):
self.client = github.Github(token)

def get_organization_team(self, organization, team):
organization = self.client.get_organization(organization)
return OrganizationTeam(organization.get_team_by_slug(team))


class OrganizationTeam:
def __init__(self, team):
self.team = team

def get_members_keys(self):
return MembersKeys(self.team.get_members())


class MembersKeys(object):
def __init__(self, members):
self.members = iter(members)

def __iter__(self):
return self

def __next__(self):
member = next(self.members)
return MemberKeys(member, member.get_keys())

def next(self):
return self.__next__()


class MemberKeys:
def __init__(self, member, keys):
self.member = member
self.keys = keys

@property
def login(self):
return self.member.login

def is_mandatory_without_keys(self, mandatory_members):
not_have_keys = len(list(self.keys)) == 0
return self.login in mandatory_members and not_have_keys


def main():
argument_spec = dict(
token=dict(type='str', required=True, no_log=True),
organization=dict(type='str', required=True),
team=dict(type='str', required=True),
mandatory_members=dict(type='list', required=False, default=[])
)

module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)

if not HAS_GITHUB:
module.fail_json(
msg=missing_required_lib('PyGithub'),
exception=GITHUB_IMP_ERR
)

client = GithubClient(module.params['token'])
team = client.get_organization_team(
module.params['organization'],
module.params['team']
)

mandatory_members = module.params['mandatory_members']
included_members = []
members_keys = []

for member_keys in team.get_members_keys():
login = member_keys.login
included_members.append(login)

if member_keys.is_mandatory_without_keys(mandatory_members):
module.fail_json(msg='Mandatory member %s has no keys' % login)

for key in member_keys.keys:
members_keys.append('%s %s-%s' % (key.key, login, key.id))

if mandatory_members:
missing_members = [
member for member in mandatory_members
if member not in included_members
]

if missing_members:
module.fail_json(msg='%s does not include all mandatory members %s' % (
included_members,
missing_members
))

module.exit_json(changed=False, members_keys=members_keys)


if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions test/lib/ansible_test/_data/requirements/units.txt
Expand Up @@ -47,3 +47,6 @@ httmock

# requirment for kubevirt modules
openshift ; python_version >= '2.7'

# requirement for github_members_keys
PyGithub
142 changes: 142 additions & 0 deletions test/units/modules/source_control/test_github_members_keys.py
@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-

# Copyright: (c) 2019, Sebastián Estrella <sestrella.me@gmail.com>
# 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

from ansible.modules.source_control import github_members_keys
from units.compat import mock
from units.modules import utils


class TestGithubMembersKeys(utils.ModuleTestCase):
def setUp(self):
super(TestGithubMembersKeys, self).setUp()
self.module = github_members_keys

def test_token_is_required(self):
with self.assertRaises(utils.AnsibleFailJson) as exec_info:
utils.set_module_args({
'organization': 'organization',
'team': 'team'
})
self.module.main()

self.assertEqual(
exec_info.exception.args[0]['msg'],
'missing required arguments: token'
)

def test_organization_is_required(self):
with self.assertRaises(utils.AnsibleFailJson) as exec_info:
utils.set_module_args({
'token': 'token',
'team': 'team'
})
self.module.main()

self.assertEqual(
exec_info.exception.args[0]['msg'],
'missing required arguments: organization'
)

def test_team_is_required(self):
with self.assertRaises(utils.AnsibleFailJson) as exec_info:
utils.set_module_args({
'token': 'token',
'organization': 'organization'
})
self.module.main()

self.assertEqual(
exec_info.exception.args[0]['msg'],
'missing required arguments: team'
)

@mock.patch.object(github_members_keys.GithubClient, 'get_organization_team')
def test_members_keys(self, team):
team.return_value.get_members_keys.return_value = [
github_members_keys.MemberKeys(
member=mock.Mock(login='user1'),
keys=[
mock.Mock(id=1, key='ssh-rsa AAA...'),
mock.Mock(id=2, key='ssh-rsa BBB...')
]
),
github_members_keys.MemberKeys(
member=mock.Mock(login='user2'),
keys=[
mock.MagicMock(id=3, key='ssh-rsa CCC...')
]
)
]

with self.assertRaises(utils.AnsibleExitJson) as exec_info:
utils.set_module_args({
'token': 'token',
'organization': 'organization',
'team': 'team'
})
self.module.main()

self.assertEqual(exec_info.exception.args[0]['members_keys'], [
'ssh-rsa AAA... user1-1',
'ssh-rsa BBB... user1-2',
'ssh-rsa CCC... user2-3'
])

@mock.patch.object(github_members_keys.GithubClient, 'get_organization_team')
def test_missing_members(self, team):
team.return_value.get_members_keys.return_value = [
github_members_keys.MemberKeys(
member=mock.Mock(login='user1'),
keys=[
mock.Mock(id=1, key='ssh-rsa AAA...'),
]
)
]

with self.assertRaises(utils.AnsibleFailJson) as exec_info:
utils.set_module_args({
'token': 'token',
'organization': 'organization',
'team': 'team',
'mandatory_members': ['user1', 'user2']
})
self.module.main()

self.assertEqual(
exec_info.exception.args[0]['msg'],
'[\'user1\'] does not include all mandatory members [\'user2\']'
)

@mock.patch.object(github_members_keys.GithubClient, 'get_organization_team')
def test_mandatory_member_has_no_keys(self, team):
team.return_value.get_members_keys.return_value = [
github_members_keys.MemberKeys(
member=mock.Mock(login='user1'),
keys=[]
),
github_members_keys.MemberKeys(
member=mock.Mock(login='user2'),
keys=[
mock.Mock(id=1, key='ssh-rsa CCC...'),
]
)
]

with self.assertRaises(utils.AnsibleFailJson) as exec_info:
utils.set_module_args({
'token': 'token',
'organization': 'organization',
'team': 'team',
'mandatory_members': ['user1', 'user2']
})
self.module.main()

self.assertEqual(
exec_info.exception.args[0]['msg'],
'Mandatory member user1 has no keys'
)

0 comments on commit a1a1d34

Please sign in to comment.