mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Bug fixes and privacy disable feature for github state
This commit is contained in:
parent
b433e88d03
commit
f1e293c28a
@ -24,6 +24,10 @@ For example:
|
||||
# optional: some functions require a repo_name, which
|
||||
# can be set in the config file, or passed in at the CLI.
|
||||
repo_name: my_repo
|
||||
|
||||
# optional: it can be dangerous to change the privacy of a repository
|
||||
# in an automated way. set this to True to allow privacy modifications
|
||||
allow_repo_privacy_changes: False
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
@ -81,7 +85,7 @@ def _get_config_value(profile, config_name):
|
||||
)
|
||||
|
||||
config_value = config.get(config_name)
|
||||
if not config_value:
|
||||
if config_value is None:
|
||||
raise CommandExecutionError(
|
||||
'The \'{0}\' parameter was not found in the \'{1}\' '
|
||||
'profile.'.format(
|
||||
@ -106,6 +110,7 @@ def _get_client(profile):
|
||||
if key not in __context__:
|
||||
__context__[key] = github.Github(
|
||||
token,
|
||||
per_page=100
|
||||
)
|
||||
return __context__[key]
|
||||
|
||||
@ -121,9 +126,8 @@ def _get_members(organization, params=None):
|
||||
|
||||
def _get_repos(profile, params=None, ignore_cache=False):
|
||||
# Use cache when no params are given
|
||||
key = 'github.{0}:repos'.format(
|
||||
_get_config_value(profile, 'org_name')
|
||||
)
|
||||
org_name = _get_config_value(profile, 'org_name')
|
||||
key = 'github.{0}:repos'.format(org_name)
|
||||
|
||||
if key not in __context__ or ignore_cache or params is not None:
|
||||
org_name = _get_config_value(profile, 'org_name')
|
||||
@ -136,9 +140,22 @@ def _get_repos(profile, params=None, ignore_cache=False):
|
||||
organization.url + '/repos',
|
||||
params
|
||||
)
|
||||
|
||||
# Only cache results if no params were given (full scan)
|
||||
if params is not None:
|
||||
return result
|
||||
__context__[key] = list(result) # force evaluation of all pages
|
||||
|
||||
next_result = []
|
||||
|
||||
for repo in result:
|
||||
next_result.append(repo)
|
||||
|
||||
# Cache a copy of each repo for single lookups
|
||||
repo_key = "github.{0}:{1}:repo_info".format(org_name, repo.name.lower())
|
||||
__context__[repo_key] = _repo_to_dict(repo)
|
||||
|
||||
__context__[key] = next_result
|
||||
|
||||
return __context__[key]
|
||||
|
||||
|
||||
@ -706,7 +723,33 @@ def get_milestone(number=None,
|
||||
return ret
|
||||
|
||||
|
||||
def get_repo_info(repo_name, profile='github'):
|
||||
def _repo_to_dict(repo):
|
||||
ret = {}
|
||||
ret['id'] = repo.id
|
||||
ret['name'] = repo.name
|
||||
ret['full_name'] = repo.full_name
|
||||
ret['owner'] = repo.owner.login
|
||||
ret['private'] = repo.private
|
||||
ret['html_url'] = repo.html_url
|
||||
ret['description'] = repo.description
|
||||
ret['fork'] = repo.fork
|
||||
ret['homepage'] = repo.homepage
|
||||
ret['size'] = repo.size
|
||||
ret['stargazers_count'] = repo.stargazers_count
|
||||
ret['watchers_count'] = repo.watchers_count
|
||||
ret['language'] = repo.language
|
||||
ret['open_issues_count'] = repo.open_issues_count
|
||||
ret['forks'] = repo.forks
|
||||
ret['open_issues'] = repo.open_issues
|
||||
ret['watchers'] = repo.watchers
|
||||
ret['default_branch'] = repo.default_branch
|
||||
ret['has_issues'] = repo.has_issues
|
||||
ret['has_wiki'] = repo.has_wiki
|
||||
ret['has_downloads'] = repo.has_downloads
|
||||
return ret
|
||||
|
||||
|
||||
def get_repo_info(repo_name, profile='github', ignore_cache=False):
|
||||
'''
|
||||
Return information for a given repo.
|
||||
|
||||
@ -725,46 +768,80 @@ def get_repo_info(repo_name, profile='github'):
|
||||
salt myminion github.get_repo_info salt
|
||||
salt myminion github.get_repo_info salt profile='my-github-profile'
|
||||
'''
|
||||
ret = {}
|
||||
|
||||
org_name = _get_config_value(profile, 'org_name')
|
||||
key = "github.{0}:{1}:repo_info".format(
|
||||
_get_config_value(profile, 'org_name'),
|
||||
repo_name.lower()
|
||||
)
|
||||
|
||||
if key not in __context__ or ignore_cache:
|
||||
client = _get_client(profile)
|
||||
try:
|
||||
repo = client.get_repo('/'.join([org_name, repo_name]))
|
||||
if not repo:
|
||||
return {}
|
||||
|
||||
# client.get_repo can return a github.Repository.Repository object,
|
||||
# even if the repo is invalid. We need to catch the exception when
|
||||
# we try to perform actions on the repo object, rather than above
|
||||
# the if statement.
|
||||
ret = _repo_to_dict(repo)
|
||||
|
||||
__context__[key] = ret
|
||||
except github.UnknownObjectException:
|
||||
raise CommandExecutionError(
|
||||
'The \'{0}\' repository under the \'{1}\' organization could not '
|
||||
'be found.'.format(
|
||||
repo_name,
|
||||
org_name
|
||||
)
|
||||
)
|
||||
return __context__[key]
|
||||
|
||||
|
||||
def get_repo_teams(repo_name, profile='github'):
|
||||
'''
|
||||
Return teams belonging to a repository.
|
||||
|
||||
.. versionadded:: Nitrogen
|
||||
|
||||
repo_name
|
||||
The name of the repository from which to retrieve teams.
|
||||
|
||||
profile
|
||||
The name of the profile configuration to use. Defaults to ``github``.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion github.get_repo_teams salt
|
||||
salt myminion github.get_repo_teams salt profile='my-github-profile'
|
||||
'''
|
||||
ret = []
|
||||
org_name = _get_config_value(profile, 'org_name')
|
||||
client = _get_client(profile)
|
||||
|
||||
try:
|
||||
repo = client.get_repo('/'.join([org_name, repo_name]))
|
||||
if repo:
|
||||
# client.get_repo can return a github.Repository.Repository object,
|
||||
# even if the repo is invalid. We need to catch the exception when
|
||||
# we try to perform actions on the repo object, rather than above
|
||||
# the if statement.
|
||||
ret['id'] = repo.id
|
||||
|
||||
ret['name'] = repo.name
|
||||
ret['full_name'] = repo.full_name
|
||||
ret['owner'] = repo.owner.login
|
||||
ret['private'] = repo.private
|
||||
ret['html_url'] = repo.html_url
|
||||
ret['description'] = repo.description
|
||||
ret['fork'] = repo.fork
|
||||
ret['homepage'] = repo.homepage
|
||||
ret['size'] = repo.size
|
||||
ret['stargazers_count'] = repo.stargazers_count
|
||||
ret['watchers_count'] = repo.watchers_count
|
||||
ret['language'] = repo.language
|
||||
ret['open_issues_count'] = repo.open_issues_count
|
||||
ret['forks'] = repo.forks
|
||||
ret['open_issues'] = repo.open_issues
|
||||
ret['watchers'] = repo.watchers
|
||||
ret['default_branch'] = repo.default_branch
|
||||
ret['has_issues'] = repo.has_issues
|
||||
ret['has_wiki'] = repo.has_wiki
|
||||
ret['has_downloads'] = repo.has_downloads
|
||||
except github.UnknownObjectException:
|
||||
raise CommandExecutionError(
|
||||
'The \'{0}\' repository under the \'{1}\' organization could not '
|
||||
'be found.'.format(
|
||||
repo_name,
|
||||
org_name
|
||||
)
|
||||
'be found.'.format(repo_name, org_name)
|
||||
)
|
||||
try:
|
||||
teams = repo.get_teams()
|
||||
for team in teams:
|
||||
ret.append({
|
||||
'id': team.id,
|
||||
'name': team.name,
|
||||
'permission': team.permission
|
||||
})
|
||||
except github.UnknownObjectException:
|
||||
raise CommandExecutionError(
|
||||
'Unable to retrieve teams for repository \'{0}\' under the \'{1}\' '
|
||||
'organization.'.format(repo_name, org_name)
|
||||
)
|
||||
return ret
|
||||
|
||||
@ -969,6 +1046,17 @@ def edit_repo(name,
|
||||
|
||||
.. versionadded:: Carbon
|
||||
'''
|
||||
|
||||
try:
|
||||
allow_private_change = _get_config_value(profile, 'allow_repo_privacy_changes')
|
||||
except CommandExecutionError:
|
||||
allow_private_change = False
|
||||
|
||||
if private is not None and not allow_private_change:
|
||||
raise CommandExecutionError("The private field is set to be changed for "
|
||||
"repo {0} but allow_repo_privacy_changes "
|
||||
"disallows this.".format(name))
|
||||
|
||||
try:
|
||||
client = _get_client(profile)
|
||||
organization = client.get_organization(
|
||||
@ -994,6 +1082,7 @@ def edit_repo(name,
|
||||
repo.url,
|
||||
input=parameters
|
||||
)
|
||||
get_repo_info(name, profile=profile, ignore_cache=True) # Refresh cache
|
||||
return True
|
||||
except github.GithubException as e:
|
||||
log.exception('Error editing a repo: {0}'.format(str(e)))
|
||||
@ -1222,7 +1311,8 @@ def remove_team(name, profile="github"):
|
||||
|
||||
def list_team_repos(team_name, profile="github", ignore_cache=False):
|
||||
'''
|
||||
Gets the names of team repos in lower case.
|
||||
Gets the repo details for a given team as a dict from repo_name to repo details.
|
||||
Note that repo names are always in lower case.
|
||||
|
||||
team_name
|
||||
The name of the team from which to list repos.
|
||||
@ -1259,14 +1349,25 @@ def list_team_repos(team_name, profile="github", ignore_cache=False):
|
||||
except UnknownObjectException as e:
|
||||
log.exception('Resource not found: {0}'.format(cached_team['id']))
|
||||
try:
|
||||
cached_team['repos'] = [repo.name.lower() for repo in team.get_repos()]
|
||||
return cached_team['repos']
|
||||
repos = {}
|
||||
for repo in team.get_repos():
|
||||
permission = 'pull'
|
||||
if repo.permissions.admin:
|
||||
permission = 'admin'
|
||||
elif repo.permissions.push:
|
||||
permission = 'push'
|
||||
|
||||
repos[repo.name.lower()] = {
|
||||
'permission': permission
|
||||
}
|
||||
cached_team['repos'] = repos
|
||||
return repos
|
||||
except UnknownObjectException as e:
|
||||
log.exception('Resource not found: {0}'.format(cached_team['id']))
|
||||
return []
|
||||
|
||||
|
||||
def add_team_repo(repo_name, team_name, profile="github"):
|
||||
def add_team_repo(repo_name, team_name, profile="github", permission=None):
|
||||
'''
|
||||
Adds a repository to a team with team_name.
|
||||
|
||||
@ -1279,6 +1380,13 @@ def add_team_repo(repo_name, team_name, profile="github"):
|
||||
profile
|
||||
The name of the profile configuration to use. Defaults to ``github``.
|
||||
|
||||
permission
|
||||
The permission for team members within the repository, can be 'pull',
|
||||
'push' or 'admin'. If not specified, the default permission specified on
|
||||
the team will be used.
|
||||
|
||||
.. versionadded:: Nitrogen
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
@ -1301,8 +1409,18 @@ def add_team_repo(repo_name, team_name, profile="github"):
|
||||
except UnknownObjectException as e:
|
||||
log.exception('Resource not found: {0}'.format(team['id']))
|
||||
return False
|
||||
team.add_to_repos(repo)
|
||||
return repo_name in list_team_repos(team_name, profile=profile, ignore_cache=True)
|
||||
params = None
|
||||
if permission is not None:
|
||||
params = {'permission': permission}
|
||||
|
||||
headers, data = team._requester.requestJsonAndCheck(
|
||||
"PUT",
|
||||
team.url + "/repos/" + repo._identity,
|
||||
input=params
|
||||
)
|
||||
# Try to refresh cache
|
||||
list_team_repos(team_name, profile=profile, ignore_cache=True)
|
||||
return True
|
||||
|
||||
|
||||
def remove_team_repo(repo_name, team_name, profile="github"):
|
||||
@ -1502,7 +1620,7 @@ def add_team_member(name, team_name, profile="github"):
|
||||
except github.GithubException as e:
|
||||
log.exception('Error in adding a member to a team: {0}'.format(str(e)))
|
||||
return False
|
||||
return team.has_in_members(member)
|
||||
return True
|
||||
|
||||
|
||||
def remove_team_member(name, team_name, profile="github"):
|
||||
@ -1580,12 +1698,16 @@ def list_teams(profile="github", ignore_cache=False):
|
||||
teams_data = organization.get_teams()
|
||||
teams = {}
|
||||
for team in teams_data:
|
||||
# Note that _rawData is used to access some properties here as they
|
||||
# are not exposed in older versions of PyGithub. It's VERY important
|
||||
# to use team._rawData instead of team.raw_data, as the latter forces
|
||||
# an API call to retrieve team details again.
|
||||
teams[team.name] = {
|
||||
'id': team.id,
|
||||
'slug': team.slug,
|
||||
'description': team.raw_data['description'],
|
||||
'description': team._rawData['description'],
|
||||
'permission': team.permission,
|
||||
'privacy': team.raw_data['privacy']
|
||||
'privacy': team._rawData['privacy']
|
||||
}
|
||||
__context__[key] = teams
|
||||
|
||||
|
@ -17,12 +17,14 @@ This state is used to ensure presence of users in the Organization.
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import
|
||||
import time
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.ext.six as six
|
||||
from salt.exceptions import CommandExecutionError
|
||||
from salt.ext.six.moves import range
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -254,7 +256,8 @@ def team_present(
|
||||
return ret
|
||||
|
||||
manage_repos = repo_names is not None
|
||||
current_repos = set(__salt__['github.list_team_repos'](name, profile=profile))
|
||||
current_repos = set(__salt__['github.list_team_repos'](name, profile=profile)
|
||||
.keys())
|
||||
repo_names = set(repo_names or [])
|
||||
|
||||
repos_to_add = repo_names - current_repos
|
||||
@ -473,13 +476,14 @@ def repo_present(
|
||||
name,
|
||||
description=None,
|
||||
homepage=None,
|
||||
private=False,
|
||||
has_issues=True,
|
||||
has_wiki=True,
|
||||
has_downloads=True,
|
||||
private=None,
|
||||
has_issues=None,
|
||||
has_wiki=None,
|
||||
has_downloads=None,
|
||||
auto_init=False,
|
||||
gitignore_template=None,
|
||||
license_template=None,
|
||||
teams=None,
|
||||
profile="github",
|
||||
**kwargs):
|
||||
'''
|
||||
@ -516,6 +520,12 @@ def repo_present(
|
||||
license_template
|
||||
The desired LICENSE template to apply, e.g "mit" or "mozilla".
|
||||
|
||||
teams
|
||||
The teams for which this repo should belong to, specified as a dict of
|
||||
team name to permission ('pull', 'push' or 'admin').
|
||||
|
||||
.. versionadded:: Nitrogen
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
@ -534,6 +544,11 @@ def repo_present(
|
||||
'comment': ''
|
||||
}
|
||||
|
||||
# This is an optimization to cache all repos in the organization up front.
|
||||
# The first use of this state will collect all of the repos and save a bunch
|
||||
# of API calls for future use.
|
||||
__salt__['github.list_repos'](profile=profile)
|
||||
|
||||
try:
|
||||
target = __salt__['github.get_repo_info'](name, profile=profile, **kwargs)
|
||||
except CommandExecutionError:
|
||||
@ -551,6 +566,9 @@ def repo_present(
|
||||
'license_template': license_template
|
||||
}
|
||||
|
||||
# Keep track of current_teams if we've fetched them after creating a new repo
|
||||
current_teams = None
|
||||
|
||||
if target: # Repo already exists
|
||||
# Some params are only valid on repo creation
|
||||
ignore_params = ['auto_init', 'gitignore_template', 'license_template']
|
||||
@ -564,45 +582,147 @@ def repo_present(
|
||||
old_parameters[param_name] = target[param_name]
|
||||
|
||||
if len(parameters) > 0:
|
||||
repo_change = {
|
||||
'old': 'Repo properties were {0}'.format(old_parameters),
|
||||
'new': 'Repo properties (that changed) are {0}'.format(parameters)
|
||||
}
|
||||
if __opts__['test']:
|
||||
ret['comment'] = 'Repo properties are set to be edited.'
|
||||
ret['changes']['repo'] = repo_change
|
||||
ret['result'] = None
|
||||
return ret
|
||||
else:
|
||||
result = __salt__['github.edit_repo'](name, profile=profile,
|
||||
**parameters)
|
||||
if result:
|
||||
ret['changes']['repo'] = {
|
||||
'old': 'Repo properties were {0}'.format(old_parameters),
|
||||
'new': 'Repo properties (that changed) are {0}'.format(parameters)
|
||||
}
|
||||
ret['changes']['repo'] = repo_change
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to update repo properties.'
|
||||
return ret
|
||||
|
||||
else: # Repo does not exist - it will be created.
|
||||
repo_change = {
|
||||
'old': None,
|
||||
'new': 'Repo {0} has been created'.format(name)
|
||||
}
|
||||
if __opts__['test']:
|
||||
ret['comment'] = 'Repo {0} is set to be created.'.format(name)
|
||||
ret['changes']['repo'] = repo_change
|
||||
ret['result'] = None
|
||||
return ret
|
||||
|
||||
add_params = dict(given_params)
|
||||
add_params.update(kwargs)
|
||||
result = __salt__['github.add_repo'](
|
||||
name,
|
||||
**add_params
|
||||
)
|
||||
if result:
|
||||
ret['changes']['repo'] = {
|
||||
'old': None,
|
||||
'new': 'Repo {0} has been created'.format(name)
|
||||
}
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to create repo {0}.'.format(name)
|
||||
return ret
|
||||
add_params = dict(given_params)
|
||||
add_params.update(kwargs)
|
||||
result = __salt__['github.add_repo'](
|
||||
name,
|
||||
**add_params
|
||||
)
|
||||
|
||||
if not result:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to create repo {0}.'.format(name)
|
||||
return ret
|
||||
|
||||
# Turns out that trying to fetch teams for a new repo can 404 immediately
|
||||
# after repo creation, so this waits until we can fetch teams successfully
|
||||
# before continuing.
|
||||
for attempt in range(3):
|
||||
time.sleep(1)
|
||||
try:
|
||||
current_teams = __salt__['github.get_repo_teams'](
|
||||
name, profile=profile, **kwargs)
|
||||
break
|
||||
except CommandExecutionError as e:
|
||||
log.info("Attempt {0} to fetch new repo {1} failed".format(
|
||||
attempt, name))
|
||||
|
||||
if current_teams is None:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to verify repo {0} after creation.'.format(name)
|
||||
return ret
|
||||
|
||||
ret['changes']['repo'] = repo_change
|
||||
|
||||
if teams is not None:
|
||||
if __opts__['test'] and not target:
|
||||
# Assume no teams if we're in test mode and the repo doesn't exist
|
||||
current_teams = []
|
||||
elif current_teams is None:
|
||||
current_teams = __salt__['github.get_repo_teams'](name, profile=profile)
|
||||
current_team_names = set([t['name'] for t in current_teams])
|
||||
|
||||
# First remove any teams that aren't present
|
||||
for team_name in current_team_names:
|
||||
if team_name not in teams:
|
||||
team_change = {
|
||||
'old': 'Repo {0} is in team {1}'.format(name, team_name),
|
||||
'new': 'Repo {0} is not in team {1}'.format(name, team_name)
|
||||
}
|
||||
|
||||
if __opts__['test']:
|
||||
ret['changes'][team_name] = team_change
|
||||
ret['result'] = None
|
||||
else:
|
||||
result = __salt__['github.remove_team_repo'](name, team_name,
|
||||
profile=profile)
|
||||
if result:
|
||||
ret['changes'][team_name] = team_change
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = ('Failed to remove repo {0} from team {1}.'
|
||||
.format(name, team_name))
|
||||
return ret
|
||||
|
||||
# Next add or modify any necessary teams
|
||||
for team_name, permission in six.iteritems(teams):
|
||||
if team_name not in current_team_names: # Need to add repo to team
|
||||
team_change = {
|
||||
'old': 'Repo {0} is not in team {1}'.format(name, team_name),
|
||||
'new': 'Repo {0} is in team {1}'.format(name, team_name)
|
||||
}
|
||||
if __opts__['test']:
|
||||
ret['changes'][team_name] = team_change
|
||||
ret['result'] = None
|
||||
else:
|
||||
result = __salt__['github.add_team_repo'](name, team_name,
|
||||
permission,
|
||||
profile=profile)
|
||||
if result:
|
||||
ret['changes'][team_name] = team_change
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = ('Failed to remove repo {0} from team {1}.'
|
||||
.format(name, team_name))
|
||||
return ret
|
||||
else:
|
||||
current_permission = (__salt__['github.list_team_repos']
|
||||
(team_name, profile=profile)
|
||||
.get(name.lower(), {})
|
||||
.get('permission'))
|
||||
if not current_permission:
|
||||
ret['result'] = False
|
||||
ret['comment'] = ('Failed to determine current permission for team '
|
||||
'{0} in repo {1}'.format(team_name, name))
|
||||
return ret
|
||||
elif current_permission != permission:
|
||||
team_change = {
|
||||
'old': ('Repo {0} in team {1} has permission {2}'
|
||||
.format(name, team_name, current_permission)),
|
||||
'new': ('Repo {0} in team {1} has permission {2}'
|
||||
.format(name, team_name, permission))
|
||||
}
|
||||
if __opts__['test']:
|
||||
ret['changes'][team_name] = team_change
|
||||
ret['result'] = None
|
||||
else:
|
||||
result = __salt__['github.add_team_repo'](name, team_name,
|
||||
permission,
|
||||
profile=profile)
|
||||
if result:
|
||||
ret['changes'][team_name] = team_change
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = ('Failed to set permission on repo {0} from '
|
||||
'team {1} to {2}.'
|
||||
.format(name, team_name, permission))
|
||||
return ret
|
||||
return ret
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user