.. program-output:: lftools github list --help
+
+update-repo
+-----------
+
+.. program-output:: lftools github update-repo --help
+
+
+submit-pr
+---------
+
+.. program-output:: lftools github submit-pr --help
+
+
user
----
.. program-output:: lftools github user --help
-API requires an [github] section in ~/.config/lftools/lftools.ini:
+
+votes
+-----
+
+.. program-output:: lftools github votes --help
+
+
+
+API requires a [github] section in ~/.config/lftools/lftools.ini:
.. code-block:: bash
---------------
.. program-output:: lftools infofile sync-committers --help
+
+
+API for check votes requires a [github] section in ~/.config/lftools/lftools.ini:
+
+.. code-block:: bash
+
+ [github]
+ token = REDACTED
+
from github import GithubException
from lftools import config
+from lftools.github_helper import helper_list
+from lftools.github_helper import helper_user_github
+from lftools.github_helper import prvotes
@click.group()
pass
+@click.command(name='submit-pr')
+@click.argument('organization')
+@click.argument('repo')
+@click.argument('pr', type=int)
+@click.pass_context
+def submit_pr(ctx, organization, repo, pr):
+ """Submit a pr if mergeable."""
+ token = config.get_setting("github", "token")
+ g = Github(token)
+ orgName = organization
+ try:
+ org = g.get_organization(orgName)
+ except GithubException as ghe:
+ print(ghe)
+
+ repo = org.get_repo(repo)
+ pr_mergable = repo.get_pull(pr).mergeable
+
+ if pr_mergable:
+ print(pr_mergable)
+ repo.get_pull(pr).merge(commit_message="Vote Completed, merging INFO file")
+ else:
+ print("PR NOT MERGABLE {}".format(pr_mergable))
+ sys.exit(1)
+
+
+@click.command(name='votes')
+@click.argument('organization')
+@click.argument('repo')
+@click.argument('pr', type=int)
+@click.pass_context
+def votes(ctx, organization, repo, pr):
+ """Helper for votes."""
+ approval_list = prvotes(organization, repo, pr)
+ print("Approvals:", approval_list)
+
+
@click.command(name='list')
@click.argument('organization')
@click.option('--audit', is_flag=True, required=False,
help='All members and their respective teams')
@click.option('--teams', is_flag=True, required=False,
help='List avaliable teams')
+@click.option('--team', type=str, required=False,
+ help='List members of a team')
@click.option('--repofeatures', is_flag=True, required=False,
help='List enabled features for repos in an org')
@click.pass_context
-def list(ctx, organization, repos, audit, full, teams, repofeatures):
- """List an Organization's GitHub repos."""
- token = config.get_setting("github", "token")
- g = Github(token)
- orgName = organization
-
- try:
- org = g.get_organization(orgName)
- except GithubException as ghe:
- print(ghe)
-
- if repos:
- print("All repos for organization: ", orgName)
- repos = org.get_repos()
- for repo in repos:
- print(repo.name)
-
- if audit:
- print("{} members without 2fa:".format(orgName))
- try:
- members = org.get_members(filter_="2fa_disabled")
- except GithubException as ghe:
- print(ghe)
- for member in members:
- print(member.login)
- print("{} outside collaborators without 2fa:".format(orgName))
- try:
- collaborators = org.get_outside_collaborators(filter_="2fa_disabled")
- except GithubException as ghe:
- print(ghe)
- for collaborator in collaborators:
- print(collaborator.login)
-
- if repofeatures:
- repos = org.get_repos()
- for repo in repos:
- print("{} wiki:{} issues:{}".format(repo.name, repo.has_wiki, repo.has_issues))
- issues = repo.get_issues
- for issue in issues():
- print("{}".format(issue))
-
- if full:
- print("---")
- print("# All owners for {}:".format(orgName))
- print("{}-owners:".format(orgName))
-
- try:
- members = org.get_members(role="admin")
- except GithubException as ghe:
- print(ghe)
- for member in members:
- print(" - '{}'".format(member.login))
- print("# All members for {}".format(orgName))
- print("{}-members:".format(orgName))
-
- try:
- members = org.get_members()
- except GithubException as ghe:
- print(ghe)
- for member in members:
- print(" - '{}'".format(member.login))
- print("# All members and all teams for {}".format(orgName))
-
- try:
- teams = org.get_teams
- except GithubException as ghe:
- print(ghe)
- for team in teams():
- print("{}:".format(team.name))
- for user in team.get_members():
- print(" - '{}'".format(user.login))
- print("")
- teams = None
-
- if teams:
- try:
- teams = org.get_teams
- except GithubException as ghe:
- print(ghe)
- for team in teams():
- print("{}".format(team.name))
+def list(ctx, organization, repos, audit, full, teams, team, repofeatures):
+ """List options for github org repos."""
+ helper_list(ctx, organization, repos, audit, full, teams, team, repofeatures)
@click.command(name='create-repo')
print(ghe)
-@click.command(name='modify-repo')
+@click.command(name='update-repo')
@click.argument('organization')
@click.argument('repository')
@click.option('--has_issues', is_flag=True, required=False,
help='Repo should have projects')
@click.option('--has_wiki', is_flag=True, required=False,
help='Repo should have wiki')
+@click.option('--add_team', type=str, required=False,
+ help='Add team to repo')
+@click.option('--remove_team', type=str, required=False,
+ help='remove team from repo')
@click.pass_context
-def modifyrepo(ctx, organization, repository, has_issues, has_projects, has_wiki):
- """Modify a Github repo within an Organization.
+def updaterepo(ctx, organization, repository, has_issues, has_projects, has_wiki, add_team, remove_team):
+ """Update a Github repo within an Organization.
By default has_issues has_wiki and has_projects is set to false.
See --help to use this command to enable these options.
repos = org.get_repos()
- for repo in repos:
- if repo.name == repository:
- repo.edit(has_issues=has_issues)
- repo.edit(has_wiki=has_wiki)
- repo.edit(has_wiki=has_projects)
+ if has_wiki or has_issues or has_projects:
+ for repo in repos:
+ if repo.name == repository:
+ repo.edit(has_issues=has_issues)
+ repo.edit(has_wiki=has_wiki)
+ repo.edit(has_wiki=has_projects)
+
+ if add_team or remove_team:
+ teams = org.get_teams
+
+ for repo in repos:
+ if repo.name == repository:
+ repo_actual = (repo)
+
+ for team in teams():
+ if team.name == add_team:
+ print(team.id)
+ team.add_to_repos(repo_actual)
+ team.set_repo_permission(repo_actual, "write")
+ if team.name == remove_team:
+ print(team.id)
+ team.remove_from_repos(repo_actual)
@click.command(name='create-team')
@click.pass_context
def user(ctx, organization, user, team, delete, admin):
"""Add and Remove users from an org team."""
- token = config.get_setting("github", "token")
- g = Github(token)
- orgName = organization
- try:
- org = g.get_organization(orgName)
- except GithubException as ghe:
- print(ghe)
- try:
- user_object = g.get_user(user)
- print(user_object)
- except GithubException as ghe:
- print(ghe)
- print("user {} not found".format(user))
- sys.exit(1)
- # check if user is a member
- try:
- is_member = org.has_in_members(user_object)
- print("Is {} a member of org {}".format(user, is_member))
- except GithubException as ghe:
- print(ghe)
- # get teams
- try:
- teams = org.get_teams
- except GithubException as ghe:
- print(ghe)
-
- my_teams = [team]
- teams = [team for team in teams() if team.name in my_teams]
-
- if delete:
- if is_member:
- for t in teams:
- team_id = (t.id)
- try:
- team = org.get_team(team_id)
- team.remove_membership(user_object)
- except GithubException as ghe:
- print(ghe)
- else:
- print("{} is not a member of org cannot delete".format(user))
-
- if user and not delete:
- if admin and is_member:
- try:
- team.add_membership(member=user_object, role="maintainer")
- except GithubException as ghe:
- print(ghe)
- if admin and not is_member:
- try:
- org.invite_user(user=user_object, role="admin", teams=teams)
- except GithubException as ghe:
- print(ghe)
-
- if not admin and is_member:
- try:
- team.add_membership(member=user_object, role="member")
- except GithubException as ghe:
- print(ghe)
-
- if not admin and not is_member:
- try:
- org.invite_user(user=user_object, teams=teams)
- except GithubException as ghe:
- print(ghe)
+ helper_user_github(ctx, organization, user, team, delete, admin)
+github_cli.add_command(submit_pr)
+github_cli.add_command(votes)
github_cli.add_command(list)
github_cli.add_command(createteam)
github_cli.add_command(createrepo)
-github_cli.add_command(modifyrepo)
+github_cli.add_command(updaterepo)
github_cli.add_command(user)
import ruamel.yaml
import yaml
+from lftools.github_helper import prvotes
+
log = logging.getLogger(__name__)
@click.command(name='check-votes')
@click.argument('info_file')
-@click.argument('gerrit_url')
-@click.argument('change_number')
+@click.argument('endpoint', type=str)
+@click.argument('change_number', type=int)
@click.option('--tsc', type=str, required=False,
help='path to TSC INFO file')
+@click.option('--github_repo', type=str, required=False,
+ help='Provide github repo to Check against a Github Change')
@click.pass_context
-def check_votes(ctx, info_file, gerrit_url, change_number, tsc):
+def check_votes(ctx, info_file, endpoint, change_number, tsc, github_repo):
"""Check votes on an INFO.yaml change.
- Check for Majority of votes on a gerrit patchset
+ Check for Majority of votes on a gerrit or github patchset
which changes an INFO.yaml file.
+
+ For Gerrit endpoint is the gerrit url
+ For Github the enpoint is the organization name
+
+ Examples:
+ lftools infofile check-votes /tmp/test/INFO.yaml lfit-sandbox 18 --github_repo test
+
+ lftools infofile check-votes ~/lf/allrepos/onosfw/INFO.yaml https://gerrit.opnfv.org/gerrit/ 67302
+
"""
- def main(ctx, info_file, gerrit_url, change_number, tsc, majority_of_committers):
+ def main(ctx, info_file, endpoint, change_number, tsc, github_repo, majority_of_committers):
"""Function so we can iterate into TSC members after commiter vote has happend."""
with open(info_file) as file:
try:
log.error(exc)
committer_info = info_data['committers']
-
info_committers = []
- for count, item in enumerate(committer_info):
- committer = committer_info[count]['id']
- info_committers.append(committer)
-
- rest = GerritRestAPI(url=gerrit_url)
- changes = rest.get("changes/{}/reviewers".format(change_number))
info_change = []
- for change in changes:
- line = (change['username'], change['approvals']['Code-Review'])
- if '+1' in line[1] or '+2' in line[1]:
- info_change.append(change['username'])
+ if github_repo:
+ id = 'github_id'
+ githubvotes = prvotes(endpoint, github_repo, change_number)
+ for vote in githubvotes:
+ info_change.append(vote)
+
+ else:
+ id = 'id'
+ rest = GerritRestAPI(url=endpoint)
+ changes = rest.get("changes/{}/reviewers".format(change_number))
+ for change in changes:
+ line = (change['username'], change['approvals']['Code-Review'])
+ if '+1' in line[1] or '+2' in line[1]:
+ info_change.append(change['username'])
+
+ for count, item in enumerate(committer_info):
+ committer = committer_info[count][id]
+ info_committers.append(committer)
have_not_voted = [item for item in info_committers if item not in info_change]
have_not_voted_length = (len(have_not_voted))
-
have_voted = [item for item in info_committers if item in info_change]
have_voted_length = (len(have_voted))
-
log.info("Number of Committers:")
log.info(len(info_committers))
committer_lenght = (len(info_committers))
-
log.info("Committers that have voted:")
log.info(have_voted)
log.info(have_voted_length)
if (have_voted_length != 0):
majority = (committer_lenght / have_voted_length)
-
- if (majority == 1):
+ if (majority >= 1):
log.info("Majority committer vote reached")
if (tsc):
log.info("Need majority of tsc")
if majority_of_committers == 2:
log.info("TSC majority reached auto merging commit")
else:
- main(ctx, info_file, gerrit_url, change_number, tsc, majority_of_committers)
+ main(ctx, info_file, endpoint, change_number, tsc, github_repo, majority_of_committers)
else:
log.info("majority not yet reached")
sys.exit(1)
-
majority_of_committers = 0
- main(ctx, info_file, gerrit_url, change_number, tsc, majority_of_committers)
+ main(ctx, info_file, endpoint, change_number, tsc, github_repo, majority_of_committers)
infofile.add_command(get_committers)
@click.command()
@click.argument('info_file')
@click.argument('group')
+@click.option('--githuborg', type=str, required=False,
+ help='github org name')
@click.option('--noop', is_flag=True, required=False,
help='show what would be changed')
@click.pass_context
-def match_ldap_to_info(ctx, info_file, group, noop):
- """Match an LDAP groups membership to an INFO.yaml file."""
- helper_match_ldap_to_info(info_file, group, noop)
+def match_ldap_to_info(ctx, info_file, group, githuborg, noop):
+ """Match an LDAP or GITHUB group membership to an INFO.yaml file."""
+ helper_match_ldap_to_info(info_file, group, githuborg, noop)
lfidapi.add_command(search_members)
--- /dev/null
+# SPDX-License-Identifier: EPL-1.0
+##############################################################################
+# Copyright (c) 2019 The Linux Foundation and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+##############################################################################
+"""Github stub."""
+
+from __future__ import print_function
+
+import logging
+import sys
+
+from github import Github
+from github import GithubException
+
+from lftools import config
+
+log = logging.getLogger(__name__)
+
+
+def helper_list(ctx, organization, repos, audit, full, teams, team, repofeatures):
+ """List options for github org repos."""
+ token = config.get_setting("github", "token")
+ g = Github(token)
+ orgName = organization
+
+ try:
+ org = g.get_organization(orgName)
+ except GithubException as ghe:
+ log.error(ghe)
+
+ if repos:
+ log.info("All repos for organization: ", orgName)
+ repos = org.get_repos()
+ for repo in repos:
+ log.info(repo.name)
+
+ if audit:
+ log.info("{} members without 2fa:".format(orgName))
+ try:
+ members = org.get_members(filter_="2fa_disabled")
+ except GithubException as ghe:
+ log.error(ghe)
+ for member in members:
+ log.info(member.login)
+ log.info("{} outside collaborators without 2fa:".format(orgName))
+ try:
+ collaborators = org.get_outside_collaborators(filter_="2fa_disabled")
+ except GithubException as ghe:
+ log.error(ghe)
+ for collaborator in collaborators:
+ log.info(collaborator.login)
+
+ if repofeatures:
+ repos = org.get_repos()
+ for repo in repos:
+ log.info("{} wiki:{} issues:{}".format(repo.name, repo.has_wiki, repo.has_issues))
+ issues = repo.get_issues
+ for issue in issues():
+ log.info("{}".format(issue))
+
+ if full:
+ log.info("---")
+ log.info("# All owners for {}:".format(orgName))
+ log.info("{}-owners:".format(orgName))
+
+ try:
+ members = org.get_members(role="admin")
+ except GithubException as ghe:
+ log.error(ghe)
+ for member in members:
+ log.info(" - '{}'".format(member.login))
+ log.info("# All members for {}".format(orgName))
+ log.info("{}-members:".format(orgName))
+
+ try:
+ members = org.get_members()
+ except GithubException as ghe:
+ log.error(ghe)
+ for member in members:
+ log.info(" - '{}'".format(member.login))
+ log.info("# All members and all teams for {}".format(orgName))
+
+ try:
+ teams = org.get_teams
+ except GithubException as ghe:
+ log.error(ghe)
+ for team in teams():
+ log.info("{}:".format(team.name))
+ for user in team.get_members():
+ log.info(" - '{}'".format(user.login))
+ log.info("")
+ teams = None
+
+ if teams:
+ try:
+ teams = org.get_teams
+ except GithubException as ghe:
+ log.error(ghe)
+ for team in teams():
+ log.info("{}".format(team.name))
+
+ if team:
+ try:
+ teams = org.get_teams
+ except GithubException as ghe:
+ log.error(ghe)
+
+ team_members = []
+
+ for t in teams():
+ if t.name == team:
+ log.info("{}".format(t.name))
+ for user in t.get_members():
+ team_members.append(user.login)
+ log.info(" - '{}'".format(user.login))
+
+ return(team_members)
+
+
+def prvotes(organization, repo, pr):
+ """Get votes on a github pr."""
+ token = config.get_setting("github", "token")
+ g = Github(token)
+ orgName = organization
+ try:
+ org = g.get_organization(orgName)
+ except GithubException as ghe:
+ log.error(ghe)
+
+ repo = org.get_repo(repo)
+ approval_list = []
+ author = repo.get_pull(pr).user.login
+ approval_list.append(author)
+
+ pr_mergable = repo.get_pull(pr).mergeable
+ log.info("MERGEABLE: {}".format(pr_mergable))
+
+ approvals = repo.get_pull(pr).get_reviews()
+ for approve in approvals:
+ if approve.state == ("APPROVED"):
+ approval_list.append(approve.user.login)
+ return(approval_list)
+
+
+def helper_user_github(ctx, organization, user, team, delete, admin):
+ """Add and Remove users from an org team."""
+ token = config.get_setting("github", "token")
+ g = Github(token)
+ orgName = organization
+ try:
+ org = g.get_organization(orgName)
+ except GithubException as ghe:
+ log.error(ghe)
+ try:
+ user_object = g.get_user(user)
+ log.info(user_object)
+ except GithubException as ghe:
+ log.error(ghe)
+ log.info("user {} not found".format(user))
+ sys.exit(1)
+ # check if user is a member
+ try:
+ is_member = org.has_in_members(user_object)
+ log.info("Is {} a member of org {}".format(user, is_member))
+ except GithubException as ghe:
+ log.error(ghe)
+ # get teams
+ try:
+ teams = org.get_teams
+ except GithubException as ghe:
+ log.error(ghe)
+
+ # set team to proper object
+ my_teams = [team]
+ this_team = [team for team in teams() if team.name in my_teams]
+ for t in this_team:
+ team_id = (t.id)
+ team = org.get_team(team_id)
+ teams = []
+ teams.append(team)
+
+ if delete:
+ if is_member:
+ try:
+ team.remove_membership(user_object)
+ except GithubException as ghe:
+ log.error(ghe)
+ log.info("Removing user {} from {}".format(user_object, team))
+ else:
+ log.info("{} is not a member of org cannot delete".format(user))
+ # TODO add revoke invite
+ log.info("Code does not handle revoking invitations.")
+
+ if user and not delete:
+ if admin and is_member:
+ try:
+ team.add_membership(member=user_object, role="maintainer")
+ except GithubException as ghe:
+ log.error(ghe)
+ if admin and not is_member:
+ try:
+ org.invite_user(user=user_object, role="admin", teams=teams)
+ except GithubException as ghe:
+ log.error(ghe)
+ log.info("Sending Admin invite to {} for {}".format(user_object, team))
+
+ if not admin and is_member:
+ try:
+ team.add_membership(member=user_object, role="member")
+ except GithubException as ghe:
+ log.error(ghe)
+
+ if not admin and not is_member:
+ try:
+ org.invite_user(user=user_object, teams=teams)
+ except GithubException as ghe:
+ log.error(ghe)
+ log.info("Sending invite to {} for {}".format(user_object, team))
from six.moves import urllib
import yaml
+from lftools.github_helper import helper_list
+from lftools.github_helper import helper_user_github
from lftools.oauth2_helper import oauth_helper
log = logging.getLogger(__name__)
log.debug(json.dumps(result, indent=4, sort_keys=True))
-def helper_match_ldap_to_info(info_file, group, noop):
- """Helper only to be used in automation."""
+def helper_match_ldap_to_info(info_file, group, githuborg, noop):
+ """Helper matches ldap or github group to users in an info file.
+
+ Used in automation.
+ """
with open(info_file) as file:
try:
info_data = yaml.safe_load(file)
except yaml.YAMLError as exc:
print(exc)
+ id = 'id'
+ if githuborg:
+ id = 'github_id'
+ ldap_data = helper_list(ctx=False, organization=githuborg, repos=False, audit=False,
+ full=False, teams=False, repofeatures=False, team=group)
+ else:
+ ldap_data = helper_search_members(group)
- ldap_data = helper_search_members(group)
committer_info = info_data['committers']
info_committers = []
for count, item in enumerate(committer_info):
- committer = committer_info[count]['id']
+ committer = committer_info[count][id]
info_committers.append(committer)
ldap_committers = []
- for count, item in enumerate(ldap_data):
- committer = ldap_data[count]['username']
- ldap_committers.append(committer)
+ if githuborg:
+ for x in ldap_data:
+ committer = x
+ ldap_committers.append(committer)
+
+ else:
+ for count, item in enumerate(ldap_data):
+ committer = ldap_data[count]['username']
+ ldap_committers.append(committer)
all_users = ldap_committers + info_committers
- all_users.remove("lfservices_releng")
+
+ if not githuborg:
+ all_users.remove("lfservices_releng")
+
+ log.info("All users in org group")
all_users = sorted(set(all_users))
+ for x in all_users:
+ log.info(x)
for user in all_users:
removed_by_patch = [item for item in ldap_committers if item not in info_committers]
if (user in removed_by_patch):
log.info("%s found in group %s " % (user, group))
if noop is False:
- log.info(" removing user %s from group %s" % (user, group))
- helper_user(user, group, "--delete")
+ log.info("Removing user %s from group %s" % (user, group))
+ if githuborg:
+ helper_user_github(ctx=False, organization=githuborg, user=user,
+ team=group, delete=True, admin=False)
+ else:
+ helper_user(user, group, "--delete")
added_by_patch = [item for item in info_committers if item not in ldap_committers]
if (user in added_by_patch):
log.info("%s not found in group %s" % (user, group))
if noop is False:
- log.info(" adding user %s to group %s" % (user, group))
- helper_user(user, group, "")
+ log.info("Adding user %s to group %s" % (user, group))
+ if githuborg:
+ helper_user_github(ctx=False, organization=githuborg, user=user,
+ team=group, delete=False, admin=False)
+
+ else:
+ helper_user(user, group, "")
--- /dev/null
+---
+features:
+ - |
+ --team now lists members of a specific team
+
+fixes:
+ - |
+ Fixes invite to team
+
+adds:
+ - |
+ prvotes counts votes on a github pull request
--- /dev/null
+---
+features:
+ - |
+ check_votes now takes click.option('--github_repo')
+ Used in automation to determine is 50% of committers
+ have voted on an INFO.yaml change
--- /dev/null
+---
+adds:
+ - |
+ --githuborg
+ lfidapi info match-ldap-to-info now works for github groups.
+ expects group to be called $reponame-committers
+ id field for github users is github_id