Strip out the things we don't need from jeepyb
Change-Id: I129b6d66840604898592de31df11b3e396cd6048
This commit is contained in:
parent
384c02d41f
commit
c28dde9d68
@ -1,4 +1,3 @@
|
|||||||
include jeepyb/versioninfo
|
|
||||||
include AUTHORS
|
include AUTHORS
|
||||||
include ChangeLog
|
include ChangeLog
|
||||||
|
|
||||||
|
17
README.rst
17
README.rst
@ -1,7 +1,12 @@
|
|||||||
===============================
|
====================
|
||||||
Tools to Manage Gerrit Projects
|
Partial PyPI Mirrors
|
||||||
===============================
|
====================
|
||||||
|
|
||||||
jeepyb is a collection of tools which make managing a gerrit easier.
|
Sometimes you want a PyPI mirror, but you don't want the whole thing. You
|
||||||
Specifically, management of gerrit projects and their associated upstream
|
certainly don't want external links. What you want are the things that you
|
||||||
integration with things like github and launchpad.
|
need and nothing more. What's more, you often know exactly what you need
|
||||||
|
because you already have a pip requirements.txt file containing the list of
|
||||||
|
things you expect to download from PyPI.
|
||||||
|
|
||||||
|
pypi-mirror will build a local static mirror for you based on requirements
|
||||||
|
files in git repos.
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
# Copyright (C) 2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# Github pull requests closer reads a project config file called projects.yaml
|
|
||||||
# It should look like:
|
|
||||||
|
|
||||||
# - homepage: http://openstack.org
|
|
||||||
# team-id: 153703
|
|
||||||
# has-wiki: False
|
|
||||||
# has-issues: False
|
|
||||||
# has-downloads: False
|
|
||||||
# ---
|
|
||||||
# - project: PROJECT_NAME
|
|
||||||
# options:
|
|
||||||
# - has-pull-requests
|
|
||||||
|
|
||||||
# Github authentication information is read from github.secure.config,
|
|
||||||
# which should look like:
|
|
||||||
|
|
||||||
# [github]
|
|
||||||
# username = GITHUB_USERNAME
|
|
||||||
# password = GITHUB_PASSWORD
|
|
||||||
#
|
|
||||||
# or
|
|
||||||
#
|
|
||||||
# [github]
|
|
||||||
# oauth_token = GITHUB_OAUTH_TOKEN
|
|
||||||
|
|
||||||
import ConfigParser
|
|
||||||
import github
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
MESSAGE = """Thank you for contributing to %(project)s!
|
|
||||||
|
|
||||||
%(project)s uses Gerrit for code review.
|
|
||||||
|
|
||||||
Please visit http://wiki.openstack.org/GerritWorkflow and follow the
|
|
||||||
instructions there to upload your change to Gerrit.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.ERROR)
|
|
||||||
|
|
||||||
PROJECTS_YAML = os.environ.get('PROJECTS_YAML',
|
|
||||||
'/home/gerrit2/projects.yaml')
|
|
||||||
GITHUB_SECURE_CONFIG = os.environ.get('GITHUB_SECURE_CONFIG',
|
|
||||||
'/etc/github/github.secure.config')
|
|
||||||
|
|
||||||
secure_config = ConfigParser.ConfigParser()
|
|
||||||
secure_config.read(GITHUB_SECURE_CONFIG)
|
|
||||||
(defaults, config) = [config for config in
|
|
||||||
yaml.load_all(open(PROJECTS_YAML))]
|
|
||||||
|
|
||||||
if secure_config.has_option("github", "oauth_token"):
|
|
||||||
ghub = github.Github(secure_config.get("github", "oauth_token"))
|
|
||||||
else:
|
|
||||||
ghub = github.Github(secure_config.get("github", "username"),
|
|
||||||
secure_config.get("github", "password"))
|
|
||||||
|
|
||||||
orgs = ghub.get_user().get_orgs()
|
|
||||||
orgs_dict = dict(zip([o.login.lower() for o in orgs], orgs))
|
|
||||||
for section in config:
|
|
||||||
project = section['project']
|
|
||||||
|
|
||||||
# Make sure we're supposed to close pull requests for this project:
|
|
||||||
if 'options' in section and 'has-pull-requests' in section['options']:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Find the project's repo
|
|
||||||
project_split = project.split('/', 1)
|
|
||||||
if len(project_split) > 1:
|
|
||||||
org = orgs_dict[project_split[0].lower()]
|
|
||||||
repo = org.get_repo(project_split[1])
|
|
||||||
else:
|
|
||||||
repo = ghub.get_user().get_repo(project)
|
|
||||||
|
|
||||||
# Close each pull request
|
|
||||||
pull_requests = repo.get_pulls("open")
|
|
||||||
for req in pull_requests:
|
|
||||||
vars = dict(project=project)
|
|
||||||
issue_data = {"url": repo.url + "/issues/" + str(req.number)}
|
|
||||||
issue = github.Issue.Issue(req._requester,
|
|
||||||
issue_data,
|
|
||||||
completed=True)
|
|
||||||
issue.create_comment(MESSAGE % vars)
|
|
||||||
req.edit(state="closed")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,72 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
#
|
|
||||||
# create_cgitrepos.py reads the project config file called projects.yaml
|
|
||||||
# and generates a cgitrepos configuration file which is then copied to
|
|
||||||
# the cgit server.
|
|
||||||
#
|
|
||||||
# It also creates the necessary top-level directories for each project
|
|
||||||
# organization (openstack, stackforge, etc)
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
PROJECTS_YAML = os.environ.get('PROJECTS_YAML',
|
|
||||||
'/home/cgit/projects.yaml')
|
|
||||||
CGIT_REPOS = os.environ.get('CGIT_REPOS',
|
|
||||||
'/etc/cgitrepos')
|
|
||||||
REPO_PATH = os.environ.get('REPO_PATH',
|
|
||||||
'/var/lib/git')
|
|
||||||
CGIT_USER = os.environ.get('CGIT_USER', 'cgit')
|
|
||||||
CGIT_GROUP = os.environ.get('CGIT_GROUP', 'cgit')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
(defaults, config) = tuple(yaml.safe_load_all(open(PROJECTS_YAML)))
|
|
||||||
gitorgs = {}
|
|
||||||
names = set()
|
|
||||||
for entry in config:
|
|
||||||
(org, name) = entry['project'].split('/')
|
|
||||||
description = entry.get('description', name)
|
|
||||||
assert name not in names
|
|
||||||
names.add(name)
|
|
||||||
gitorgs.setdefault(org, []).append((name, description))
|
|
||||||
for org in gitorgs:
|
|
||||||
if not os.path.isdir('%s/%s' % (REPO_PATH, org)):
|
|
||||||
os.makedirs('%s/%s' % (REPO_PATH, org))
|
|
||||||
with open(CGIT_REPOS, 'w') as cgit_file:
|
|
||||||
cgit_file.write('# Autogenerated by create_cgitrepos.py\n')
|
|
||||||
for org in sorted(gitorgs):
|
|
||||||
cgit_file.write('\n')
|
|
||||||
cgit_file.write('section=%s\n' % (org))
|
|
||||||
org_dir = os.path.join(REPO_PATH, org)
|
|
||||||
projects = gitorgs[org]
|
|
||||||
projects.sort()
|
|
||||||
for (name, description) in projects:
|
|
||||||
project_repo = "%s.git" % os.path.join(org_dir, name)
|
|
||||||
cgit_file.write('\n')
|
|
||||||
cgit_file.write('repo.url=%s/%s\n' % (org, name))
|
|
||||||
cgit_file.write('repo.path=%s/\n' % (project_repo))
|
|
||||||
cgit_file.write('repo.desc=%s\n' % (description))
|
|
||||||
if not os.path.exists(project_repo):
|
|
||||||
subprocess.call(['git', 'init', '--bare', project_repo])
|
|
||||||
subprocess.call(['chown', '-R', '%s:%s'
|
|
||||||
% (CGIT_USER, CGIT_GROUP), project_repo])
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,87 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2012 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# This script is designed to expire old code reviews that have not been touched
|
|
||||||
# using the following rules:
|
|
||||||
# 1. if negative comment and no activity in 1 week, expire
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import paramiko
|
|
||||||
|
|
||||||
logger = logging.getLogger('expire_reviews')
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
def expire_patch_set(ssh, patch_id, patch_subject):
|
|
||||||
message = ('code review expired after 1 week of no activity'
|
|
||||||
' after a negative review, it can be restored using'
|
|
||||||
' the \`Restore Change\` button under the Patch Set'
|
|
||||||
' on the web interface')
|
|
||||||
command = ('gerrit review --abandon '
|
|
||||||
'--message="{message}" {patch_id}').format(
|
|
||||||
message=message,
|
|
||||||
patch_id=patch_id)
|
|
||||||
|
|
||||||
logger.info('Expiring: %s - %s: %s', patch_id, patch_subject, message)
|
|
||||||
stdin, stdout, stderr = ssh.exec_command(command)
|
|
||||||
if stdout.channel.recv_exit_status() != 0:
|
|
||||||
logger.error(stderr.read())
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('user', help='The gerrit admin user')
|
|
||||||
parser.add_argument('ssh_key', help='The gerrit admin SSH key file')
|
|
||||||
options = parser.parse_args()
|
|
||||||
|
|
||||||
GERRIT_USER = options.user
|
|
||||||
GERRIT_SSH_KEY = options.ssh_key
|
|
||||||
|
|
||||||
logging.basicConfig(format='%(asctime)-6s: %(name)s - %(levelname)s'
|
|
||||||
' - %(message)s',
|
|
||||||
filename='/var/log/gerrit/expire_reviews.log')
|
|
||||||
|
|
||||||
logger.info('Starting expire reviews')
|
|
||||||
logger.info('Connecting to Gerrit')
|
|
||||||
|
|
||||||
ssh = paramiko.SSHClient()
|
|
||||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect('localhost', username=GERRIT_USER,
|
|
||||||
key_filename=GERRIT_SSH_KEY, port=29418)
|
|
||||||
|
|
||||||
# Query all reviewed with no activity for 1 week
|
|
||||||
logger.info('Searching no activity on negative review for 1 week')
|
|
||||||
stdin, stdout, stderr = ssh.exec_command(
|
|
||||||
'gerrit query --current-patch-set --all-approvals'
|
|
||||||
' --format JSON status:reviewed age:1w')
|
|
||||||
|
|
||||||
for line in stdout:
|
|
||||||
row = json.loads(line)
|
|
||||||
if 'rowCount' not in row:
|
|
||||||
# Search for negative approvals
|
|
||||||
for approval in row['currentPatchSet']['approvals']:
|
|
||||||
if approval['value'] in ('-1', '-2'):
|
|
||||||
expire_patch_set(ssh,
|
|
||||||
row['currentPatchSet']['revision'],
|
|
||||||
row['subject'])
|
|
||||||
break
|
|
||||||
|
|
||||||
logger.info('End expire review')
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,82 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
# Copyright (C) 2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# Fetch remotes reads a project config file called projects.yaml
|
|
||||||
# It should look like:
|
|
||||||
|
|
||||||
# - homepage: http://openstack.org
|
|
||||||
# team-id: 153703
|
|
||||||
# has-wiki: False
|
|
||||||
# has-issues: False
|
|
||||||
# has-downloads: False
|
|
||||||
# ---
|
|
||||||
# - project: PROJECT_NAME
|
|
||||||
# options:
|
|
||||||
# - remote: https://gerrit.googlesource.com/gerrit
|
|
||||||
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import shlex
|
|
||||||
import subprocess
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
def run_command(cmd, status=False, env={}):
|
|
||||||
cmd_list = shlex.split(str(cmd))
|
|
||||||
newenv = os.environ
|
|
||||||
newenv.update(env)
|
|
||||||
p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.STDOUT, env=newenv)
|
|
||||||
(out, nothing) = p.communicate()
|
|
||||||
if status:
|
|
||||||
return (p.returncode, out.strip())
|
|
||||||
return out.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def run_command_status(cmd, env={}):
|
|
||||||
return run_command(cmd, True, env)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
logging.basicConfig(level=logging.ERROR)
|
|
||||||
|
|
||||||
REPO_ROOT = os.environ.get('REPO_ROOT',
|
|
||||||
'/home/gerrit2/review_site/git')
|
|
||||||
PROJECTS_YAML = os.environ.get('PROJECTS_YAML',
|
|
||||||
'/home/gerrit2/projects.yaml')
|
|
||||||
|
|
||||||
(defaults, config) = [config for config in
|
|
||||||
yaml.load_all(open(PROJECTS_YAML))]
|
|
||||||
|
|
||||||
for section in config:
|
|
||||||
project = section['project']
|
|
||||||
|
|
||||||
if 'remote' not in section:
|
|
||||||
continue
|
|
||||||
|
|
||||||
project_git = "%s.git" % project
|
|
||||||
os.chdir(os.path.join(REPO_ROOT, project_git))
|
|
||||||
|
|
||||||
# Make sure that the specified remote exists
|
|
||||||
remote_url = section['remote']
|
|
||||||
# We could check if it exists first, but we're ignoring output anyway
|
|
||||||
# So just try to make it, and it'll either make a new one or do nothing
|
|
||||||
run_command("git remote add -f upstream %s" % remote_url)
|
|
||||||
# Fetch new revs from it
|
|
||||||
run_command("git remote update upstream")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,412 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
# Copyright (C) 2011 OpenStack, LLC.
|
|
||||||
# Copyright (c) 2012 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# manage_projects.py reads a project config file called projects.yaml
|
|
||||||
# It should look like:
|
|
||||||
|
|
||||||
# - homepage: http://openstack.org
|
|
||||||
# gerrit-host: review.openstack.org
|
|
||||||
# local-git-dir: /var/lib/git
|
|
||||||
# gerrit-key: /home/gerrit2/review_site/etc/ssh_host_rsa_key
|
|
||||||
# gerrit-committer: Project Creator <openstack-infra@lists.openstack.org>
|
|
||||||
# has-github: True
|
|
||||||
# has-wiki: False
|
|
||||||
# has-issues: False
|
|
||||||
# has-downloads: False
|
|
||||||
# acl-dir: /home/gerrit2/acls
|
|
||||||
# acl-base: /home/gerrit2/acls/project.config
|
|
||||||
# ---
|
|
||||||
# - project: PROJECT_NAME
|
|
||||||
# options:
|
|
||||||
# - has-wiki
|
|
||||||
# - has-issues
|
|
||||||
# - has-downloads
|
|
||||||
# - has-pull-requests
|
|
||||||
# homepage: Some homepage that isn't http://openstack.org
|
|
||||||
# description: This is a great project
|
|
||||||
# remote: https://gerrit.googlesource.com/gerrit
|
|
||||||
# upstream: git://github.com/bushy/beards.git
|
|
||||||
# acl-config: /path/to/gerrit/project.config
|
|
||||||
# acl-append:
|
|
||||||
# - /path/to/gerrit/project.config
|
|
||||||
# acl-parameters:
|
|
||||||
# project: OTHER_PROJECT_NAME
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import ConfigParser
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import shlex
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
import gerritlib.gerrit
|
|
||||||
import github
|
|
||||||
|
|
||||||
import jeepyb.gerritdb
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.ERROR)
|
|
||||||
log = logging.getLogger("manage_projects")
|
|
||||||
|
|
||||||
|
|
||||||
def run_command(cmd, status=False, env={}):
|
|
||||||
cmd_list = shlex.split(str(cmd))
|
|
||||||
newenv = os.environ
|
|
||||||
newenv.update(env)
|
|
||||||
log.debug("Executing command: %s" % " ".join(cmd_list))
|
|
||||||
p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.STDOUT, env=newenv)
|
|
||||||
(out, nothing) = p.communicate()
|
|
||||||
log.debug("Return code: %s" % p.returncode)
|
|
||||||
log.debug("Command said: %s" % out.strip())
|
|
||||||
if status:
|
|
||||||
return (p.returncode, out.strip())
|
|
||||||
return out.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def run_command_status(cmd, env={}):
|
|
||||||
return run_command(cmd, True, env)
|
|
||||||
|
|
||||||
|
|
||||||
def git_command(repo_dir, sub_cmd, env={}):
|
|
||||||
git_dir = os.path.join(repo_dir, '.git')
|
|
||||||
cmd = "git --git-dir=%s --work-tree=%s %s" % (git_dir, repo_dir, sub_cmd)
|
|
||||||
status, _ = run_command(cmd, True, env)
|
|
||||||
return status
|
|
||||||
|
|
||||||
|
|
||||||
def git_command_output(repo_dir, sub_cmd, env={}):
|
|
||||||
git_dir = os.path.join(repo_dir, '.git')
|
|
||||||
cmd = "git --git-dir=%s --work-tree=%s %s" % (git_dir, repo_dir, sub_cmd)
|
|
||||||
status, out = run_command(cmd, True, env)
|
|
||||||
return (status, out)
|
|
||||||
|
|
||||||
|
|
||||||
def write_acl_config(project, acl_dir, acl_base, acl_append, parameters):
|
|
||||||
project_parts = os.path.split(project)
|
|
||||||
if len(project_parts) > 1:
|
|
||||||
repo_base = os.path.join(acl_dir, *project_parts[:-1])
|
|
||||||
if not os.path.exists(repo_base):
|
|
||||||
os.makedirs(repo_base)
|
|
||||||
if not os.path.isdir(repo_base):
|
|
||||||
return 1
|
|
||||||
project = project_parts[-1]
|
|
||||||
config_file = os.path.join(repo_base, "%s.config" % project)
|
|
||||||
else:
|
|
||||||
config_file = os.path.join(acl_dir, "%s.config" % project)
|
|
||||||
if 'project' not in parameters:
|
|
||||||
parameters['project'] = project
|
|
||||||
with open(config_file, 'w') as config:
|
|
||||||
if acl_base and os.path.exists(acl_base):
|
|
||||||
config.write(open(acl_base, 'r').read())
|
|
||||||
for acl_snippet in acl_append:
|
|
||||||
if not os.path.exists(acl_snippet):
|
|
||||||
acl_snippet = os.path.join(acl_dir, acl_snippet)
|
|
||||||
if not os.path.exists(acl_snippet):
|
|
||||||
continue
|
|
||||||
with open(acl_snippet, 'r') as append_content:
|
|
||||||
config.write(append_content.read() % parameters)
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_config(project, remote_url, repo_path, env={}):
|
|
||||||
status = git_command(repo_path, "fetch %s +refs/meta/config:"
|
|
||||||
"refs/remotes/gerrit-meta/config" % remote_url, env)
|
|
||||||
if status != 0:
|
|
||||||
print("Failed to fetch refs/meta/config for project: %s" % project)
|
|
||||||
return False
|
|
||||||
# Because the following fails if executed more than once you should only
|
|
||||||
# run fetch_config once in each repo.
|
|
||||||
status = git_command(repo_path, "checkout -b config "
|
|
||||||
"remotes/gerrit-meta/config")
|
|
||||||
if status != 0:
|
|
||||||
print("Failed to checkout config for project: %s" % project)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def copy_acl_config(project, repo_path, acl_config):
|
|
||||||
if not os.path.exists(acl_config):
|
|
||||||
return False
|
|
||||||
|
|
||||||
acl_dest = os.path.join(repo_path, "project.config")
|
|
||||||
status, _ = run_command("cp %s %s" %
|
|
||||||
(acl_config, acl_dest), status=True)
|
|
||||||
if status == 0:
|
|
||||||
status = git_command(repo_path, "diff --quiet")
|
|
||||||
if status != 0:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def push_acl_config(project, remote_url, repo_path, gitid, env={}):
|
|
||||||
cmd = "commit -a -m'Update project config.' --author='%s'" % gitid
|
|
||||||
status = git_command(repo_path, cmd)
|
|
||||||
if status != 0:
|
|
||||||
print("Failed to commit config for project: %s" % project)
|
|
||||||
return False
|
|
||||||
status, out = git_command_output(repo_path,
|
|
||||||
"push %s HEAD:refs/meta/config" %
|
|
||||||
remote_url, env)
|
|
||||||
if status != 0:
|
|
||||||
print("Failed to push config for project: %s" % project)
|
|
||||||
print(out)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _get_group_uuid(gerrit, group):
|
|
||||||
cursor = jeepyb.gerritdb.connect().cursor()
|
|
||||||
query = "SELECT group_uuid FROM account_groups WHERE name = %s"
|
|
||||||
cursor.execute(query, group)
|
|
||||||
data = cursor.fetchone()
|
|
||||||
if data:
|
|
||||||
return data[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_group_uuid(gerrit, group):
|
|
||||||
uuid = _get_group_uuid(gerrit, group)
|
|
||||||
if uuid:
|
|
||||||
return uuid
|
|
||||||
gerrit.createGroup(group)
|
|
||||||
uuid = _get_group_uuid(gerrit, group)
|
|
||||||
if uuid:
|
|
||||||
return uuid
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def create_groups_file(project, gerrit, repo_path):
|
|
||||||
acl_config = os.path.join(repo_path, "project.config")
|
|
||||||
group_file = os.path.join(repo_path, "groups")
|
|
||||||
uuids = {}
|
|
||||||
for line in open(acl_config, 'r'):
|
|
||||||
r = re.match(r'^\s+.*group\s+(.*)$', line)
|
|
||||||
if r:
|
|
||||||
group = r.group(1)
|
|
||||||
if group in uuids.keys():
|
|
||||||
continue
|
|
||||||
uuid = get_group_uuid(gerrit, group)
|
|
||||||
if uuid:
|
|
||||||
uuids[group] = uuid
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
if uuids:
|
|
||||||
with open(group_file, 'w') as fp:
|
|
||||||
for group, uuid in uuids.items():
|
|
||||||
fp.write("%s\t%s\n" % (uuid, group))
|
|
||||||
status = git_command(repo_path, "add groups")
|
|
||||||
if status != 0:
|
|
||||||
print("Failed to add groups file for project: %s" % project)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def make_ssh_wrapper(gerrit_user, gerrit_key):
|
|
||||||
(fd, name) = tempfile.mkstemp(text=True)
|
|
||||||
os.write(fd, '#!/bin/bash\n')
|
|
||||||
os.write(fd,
|
|
||||||
'ssh -i %s -l %s -o "StrictHostKeyChecking no" $@\n' %
|
|
||||||
(gerrit_key, gerrit_user))
|
|
||||||
os.close(fd)
|
|
||||||
os.chmod(name, 0o755)
|
|
||||||
return dict(GIT_SSH=name)
|
|
||||||
|
|
||||||
|
|
||||||
def create_github_project(defaults, options, project, description, homepage):
|
|
||||||
default_has_issues = defaults.get('has-issues', False)
|
|
||||||
default_has_downloads = defaults.get('has-downloads', False)
|
|
||||||
default_has_wiki = defaults.get('has-wiki', False)
|
|
||||||
has_issues = 'has-issues' in options or default_has_issues
|
|
||||||
has_downloads = 'has-downloads' in options or default_has_downloads
|
|
||||||
has_wiki = 'has-wiki' in options or default_has_wiki
|
|
||||||
|
|
||||||
GITHUB_SECURE_CONFIG = defaults.get(
|
|
||||||
'github-config',
|
|
||||||
'/etc/github/github-projects.secure.config')
|
|
||||||
|
|
||||||
secure_config = ConfigParser.ConfigParser()
|
|
||||||
secure_config.read(GITHUB_SECURE_CONFIG)
|
|
||||||
|
|
||||||
# Project creation doesn't work via oauth
|
|
||||||
ghub = github.Github(secure_config.get("github", "username"),
|
|
||||||
secure_config.get("github", "password"))
|
|
||||||
orgs = ghub.get_user().get_orgs()
|
|
||||||
orgs_dict = dict(zip([o.login.lower() for o in orgs], orgs))
|
|
||||||
|
|
||||||
# Find the project's repo
|
|
||||||
project_split = project.split('/', 1)
|
|
||||||
org_name = project_split[0]
|
|
||||||
if len(project_split) > 1:
|
|
||||||
repo_name = project_split[1]
|
|
||||||
else:
|
|
||||||
repo_name = project
|
|
||||||
|
|
||||||
try:
|
|
||||||
org = orgs_dict[org_name.lower()]
|
|
||||||
except KeyError:
|
|
||||||
# We do not have control of this github org ignore the project.
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
repo = org.get_repo(repo_name)
|
|
||||||
except github.GithubException:
|
|
||||||
repo = org.create_repo(repo_name,
|
|
||||||
homepage=homepage,
|
|
||||||
has_issues=has_issues,
|
|
||||||
has_downloads=has_downloads,
|
|
||||||
has_wiki=has_wiki)
|
|
||||||
if description:
|
|
||||||
repo.edit(repo_name, description=description)
|
|
||||||
if homepage:
|
|
||||||
repo.edit(repo_name, homepage=homepage)
|
|
||||||
|
|
||||||
repo.edit(repo_name, has_issues=has_issues,
|
|
||||||
has_downloads=has_downloads,
|
|
||||||
has_wiki=has_wiki)
|
|
||||||
|
|
||||||
if 'gerrit' not in [team.name for team in repo.get_teams()]:
|
|
||||||
teams = org.get_teams()
|
|
||||||
teams_dict = dict(zip([t.name.lower() for t in teams], teams))
|
|
||||||
teams_dict['gerrit'].add_to_repos(repo)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
PROJECTS_YAML = os.environ.get('PROJECTS_YAML',
|
|
||||||
'/home/gerrit2/projects.yaml')
|
|
||||||
configs = [config for config in yaml.load_all(open(PROJECTS_YAML))]
|
|
||||||
defaults = configs[0][0]
|
|
||||||
default_has_github = defaults.get('has-github', True)
|
|
||||||
|
|
||||||
LOCAL_GIT_DIR = defaults.get('local-git-dir', '/var/lib/git')
|
|
||||||
ACL_DIR = defaults.get('acl-dir')
|
|
||||||
GERRIT_HOST = defaults.get('gerrit-host')
|
|
||||||
GERRIT_PORT = int(defaults.get('gerrit-port', '29418'))
|
|
||||||
GERRIT_USER = defaults.get('gerrit-user')
|
|
||||||
GERRIT_KEY = defaults.get('gerrit-key')
|
|
||||||
GERRIT_GITID = defaults.get('gerrit-committer')
|
|
||||||
GERRIT_SYSTEM_USER = defaults.get('gerrit-system-user', 'gerrit2')
|
|
||||||
GERRIT_SYSTEM_GROUP = defaults.get('gerrit-system-group', 'gerrit2')
|
|
||||||
|
|
||||||
gerrit = gerritlib.gerrit.Gerrit('localhost',
|
|
||||||
GERRIT_USER,
|
|
||||||
GERRIT_PORT,
|
|
||||||
GERRIT_KEY)
|
|
||||||
project_list = gerrit.listProjects()
|
|
||||||
ssh_env = make_ssh_wrapper(GERRIT_USER, GERRIT_KEY)
|
|
||||||
try:
|
|
||||||
|
|
||||||
for section in configs[1]:
|
|
||||||
project = section['project']
|
|
||||||
options = section.get('options', dict())
|
|
||||||
description = section.get('description', None)
|
|
||||||
homepage = section.get('homepage', defaults.get('homepage', None))
|
|
||||||
upstream = section.get('upstream', None)
|
|
||||||
|
|
||||||
project_git = "%s.git" % project
|
|
||||||
project_dir = os.path.join(LOCAL_GIT_DIR, project_git)
|
|
||||||
|
|
||||||
if 'has-github' in options or default_has_github:
|
|
||||||
create_github_project(defaults, options, project,
|
|
||||||
description, homepage)
|
|
||||||
|
|
||||||
remote_url = "ssh://localhost:%s/%s" % (GERRIT_PORT, project)
|
|
||||||
if project not in project_list:
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
repo_path = os.path.join(tmpdir, 'repo')
|
|
||||||
if upstream:
|
|
||||||
run_command("git clone %(upstream)s %(repo_path)s" %
|
|
||||||
dict(upstream=upstream,
|
|
||||||
repo_path=repo_path))
|
|
||||||
git_command(repo_path,
|
|
||||||
"fetch origin "
|
|
||||||
"+refs/heads/*:refs/copy/heads/*",
|
|
||||||
env=ssh_env)
|
|
||||||
push_string = "push %s +refs/copy/heads/*:refs/heads/*"
|
|
||||||
else:
|
|
||||||
run_command("git init %s" % repo_path)
|
|
||||||
with open(os.path.join(repo_path,
|
|
||||||
".gitreview"),
|
|
||||||
'w') as gitreview:
|
|
||||||
gitreview.write("""[gerrit]
|
|
||||||
host=%s
|
|
||||||
port=%s
|
|
||||||
project=%s
|
|
||||||
""" % (GERRIT_HOST, GERRIT_PORT, project_git))
|
|
||||||
git_command(repo_path, "add .gitreview")
|
|
||||||
cmd = ("commit -a -m'Added .gitreview' --author='%s'"
|
|
||||||
% GERRIT_GITID)
|
|
||||||
git_command(repo_path, cmd)
|
|
||||||
push_string = "push --all %s"
|
|
||||||
gerrit.createProject(project)
|
|
||||||
|
|
||||||
if not os.path.exists(project_dir):
|
|
||||||
run_command("git --bare init %s" % project_dir)
|
|
||||||
run_command("chown -R %s:%s %s"
|
|
||||||
% (GERRIT_SYSTEM_USER, GERRIT_SYSTEM_GROUP,
|
|
||||||
project_dir))
|
|
||||||
|
|
||||||
git_command(repo_path,
|
|
||||||
push_string % remote_url,
|
|
||||||
env=ssh_env)
|
|
||||||
git_command(repo_path,
|
|
||||||
"push --tags %s" % remote_url, env=ssh_env)
|
|
||||||
finally:
|
|
||||||
run_command("rm -fr %s" % tmpdir)
|
|
||||||
|
|
||||||
try:
|
|
||||||
acl_config = section.get('acl-config',
|
|
||||||
'%s.config' % os.path.join(ACL_DIR,
|
|
||||||
project))
|
|
||||||
except AttributeError:
|
|
||||||
acl_config = None
|
|
||||||
|
|
||||||
if acl_config:
|
|
||||||
if not os.path.isfile(acl_config):
|
|
||||||
write_acl_config(project,
|
|
||||||
ACL_DIR,
|
|
||||||
section.get('acl-base', None),
|
|
||||||
section.get('acl-append', []),
|
|
||||||
section.get('acl-parameters', {}))
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
repo_path = os.path.join(tmpdir, 'repo')
|
|
||||||
ret, _ = run_command_status("git init %s" % repo_path)
|
|
||||||
if ret != 0:
|
|
||||||
continue
|
|
||||||
if (fetch_config(project,
|
|
||||||
remote_url,
|
|
||||||
repo_path,
|
|
||||||
ssh_env) and
|
|
||||||
copy_acl_config(project, repo_path,
|
|
||||||
acl_config) and
|
|
||||||
create_groups_file(project, gerrit, repo_path)):
|
|
||||||
push_acl_config(project,
|
|
||||||
remote_url,
|
|
||||||
repo_path,
|
|
||||||
GERRIT_GITID,
|
|
||||||
ssh_env)
|
|
||||||
finally:
|
|
||||||
run_command("rm -fr %s" % tmpdir)
|
|
||||||
finally:
|
|
||||||
os.unlink(ssh_env['GIT_SSH'])
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,146 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2012 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# This is designed to be called by a gerrit hook. It searched new
|
|
||||||
# patchsets for strings like "bug FOO" and updates corresponding Launchpad
|
|
||||||
# bugs status.
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import smtplib
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from email.mime import text
|
|
||||||
from launchpadlib import launchpad
|
|
||||||
from launchpadlib import uris
|
|
||||||
|
|
||||||
BASE_DIR = '/home/gerrit2/review_site'
|
|
||||||
EMAIL_TEMPLATE = """
|
|
||||||
Hi, I'd like you to take a look at this patch for potential
|
|
||||||
%s.
|
|
||||||
%s
|
|
||||||
|
|
||||||
Log:
|
|
||||||
%s
|
|
||||||
"""
|
|
||||||
|
|
||||||
GERRIT_CACHE_DIR = os.path.expanduser(
|
|
||||||
os.environ.get('GERRIT_CACHE_DIR',
|
|
||||||
'~/.launchpadlib/cache'))
|
|
||||||
GERRIT_CREDENTIALS = os.path.expanduser(
|
|
||||||
os.environ.get('GERRIT_CREDENTIALS',
|
|
||||||
'~/.launchpadlib/creds'))
|
|
||||||
|
|
||||||
|
|
||||||
def create_bug(git_log, args, lp_project):
|
|
||||||
"""Create a bug for a change.
|
|
||||||
|
|
||||||
Create a launchpad bug in lp_project, titled with the first line of
|
|
||||||
the git commit message, with the content of the git_log prepended
|
|
||||||
with the Gerrit review URL. Tag the bug with the name of the repository
|
|
||||||
it came from. Don't create a duplicate bug. Returns link to the bug.
|
|
||||||
"""
|
|
||||||
lpconn = launchpad.Launchpad.login_with(
|
|
||||||
'Gerrit User Sync',
|
|
||||||
uris.LPNET_SERVICE_ROOT,
|
|
||||||
GERRIT_CACHE_DIR,
|
|
||||||
credentials_file=GERRIT_CREDENTIALS,
|
|
||||||
version='devel')
|
|
||||||
|
|
||||||
lines_in_log = git_log.split("\n")
|
|
||||||
bug_title = lines_in_log[4]
|
|
||||||
bug_descr = args.change_url + '\n' + git_log
|
|
||||||
project = lpconn.projects[lp_project]
|
|
||||||
# check for existing bugs by searching for the title, to avoid
|
|
||||||
# creating multiple bugs per review
|
|
||||||
potential_dupes = project.searchTasks(search_text=bug_title)
|
|
||||||
if len(potential_dupes) == 0:
|
|
||||||
buginfo = lpconn.bugs.createBug(
|
|
||||||
target=project, title=bug_title,
|
|
||||||
description=bug_descr, tags=args.project.split('/')[1])
|
|
||||||
buglink = buginfo.web_link
|
|
||||||
|
|
||||||
return buglink
|
|
||||||
|
|
||||||
|
|
||||||
def process_impact(git_log, args):
|
|
||||||
"""Process DocImpact flag.
|
|
||||||
|
|
||||||
If the 'DocImpact' flag is present, create a new documentation bug in
|
|
||||||
the openstack-manuals launchpad project based on the git_log.
|
|
||||||
For non-documentation impacts notify the mailing list of impact.
|
|
||||||
"""
|
|
||||||
if args.impact.lower() == 'docimpact':
|
|
||||||
create_bug(git_log, args, 'openstack-manuals')
|
|
||||||
return
|
|
||||||
|
|
||||||
email_content = EMAIL_TEMPLATE % (args.impact,
|
|
||||||
args.change_url, git_log)
|
|
||||||
|
|
||||||
msg = text.MIMEText(email_content)
|
|
||||||
msg['Subject'] = '[%s] %s review request change %s' % \
|
|
||||||
(args.project, args.impact, args.change)
|
|
||||||
msg['From'] = 'gerrit2@review.openstack.org'
|
|
||||||
msg['To'] = args.dest_address
|
|
||||||
|
|
||||||
s = smtplib.SMTP('localhost')
|
|
||||||
s.sendmail('gerrit2@review.openstack.org',
|
|
||||||
args.dest_address, msg.as_string())
|
|
||||||
s.quit()
|
|
||||||
|
|
||||||
|
|
||||||
def impacted(git_log, impact_string):
|
|
||||||
"""Determine if a changes log indicates there is an impact."""
|
|
||||||
return re.search(impact_string, git_log, re.IGNORECASE)
|
|
||||||
|
|
||||||
|
|
||||||
def extract_git_log(args):
|
|
||||||
"""Extract git log of all merged commits."""
|
|
||||||
cmd = ['git',
|
|
||||||
'--git-dir=' + BASE_DIR + '/git/' + args.project + '.git',
|
|
||||||
'log', '--no-merges', args.commit + '^1..' + args.commit]
|
|
||||||
return subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('hook')
|
|
||||||
#common
|
|
||||||
parser.add_argument('--change', default=None)
|
|
||||||
parser.add_argument('--change-url', default=None)
|
|
||||||
parser.add_argument('--project', default=None)
|
|
||||||
parser.add_argument('--branch', default=None)
|
|
||||||
parser.add_argument('--commit', default=None)
|
|
||||||
#change-merged
|
|
||||||
parser.add_argument('--submitter', default=None)
|
|
||||||
#patchset-created
|
|
||||||
parser.add_argument('--uploader', default=None)
|
|
||||||
parser.add_argument('--patchset', default=None)
|
|
||||||
# Not passed by gerrit:
|
|
||||||
parser.add_argument('--impact', default=None)
|
|
||||||
parser.add_argument('--dest-address', default=None)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Get git log
|
|
||||||
git_log = extract_git_log(args)
|
|
||||||
|
|
||||||
# Process impacts found in git log
|
|
||||||
if impacted(git_log, args.impact):
|
|
||||||
process_impact(git_log, args)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,176 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2013 Chmouel Boudjnah, eNovance
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# This script is designed to generate rss feeds for subscription from updates
|
|
||||||
# to various gerrit tracked projects. It is intended to be run periodically,
|
|
||||||
# for example hourly via cron. It takes an optional argument to specify the
|
|
||||||
# path to a configuration file.
|
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
__author__ = "Chmouel Boudjnah <chmouel@chmouel.com>"
|
|
||||||
|
|
||||||
import ConfigParser
|
|
||||||
import cStringIO
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import urllib
|
|
||||||
|
|
||||||
import PyRSS2Gen
|
|
||||||
|
|
||||||
PROJECTS = ['openstack/nova', 'openstack/keystone', 'opensack/swift']
|
|
||||||
JSON_URL = 'https://review.openstack.org/query'
|
|
||||||
DEBUG = False
|
|
||||||
OUTPUT_MODE = 'multiple'
|
|
||||||
|
|
||||||
curdir = os.path.dirname(os.path.realpath(sys.argv[0]))
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_config(config, section, option, default=None):
|
|
||||||
if not config.has_section(section):
|
|
||||||
raise ConfigurationError("Invalid configuration, missing section: %s" %
|
|
||||||
section)
|
|
||||||
if config.has_option(section, option):
|
|
||||||
return config.get(section, option)
|
|
||||||
elif not default is None:
|
|
||||||
return default
|
|
||||||
else:
|
|
||||||
raise ConfigurationError("Invalid configuration, missing "
|
|
||||||
"section/option: %s/%s" % (section, option))
|
|
||||||
|
|
||||||
|
|
||||||
def parse_ini(inifile):
|
|
||||||
ret = {}
|
|
||||||
if not os.path.exists(inifile):
|
|
||||||
return
|
|
||||||
config = ConfigParser.RawConfigParser(allow_no_value=True)
|
|
||||||
config.read(inifile)
|
|
||||||
|
|
||||||
if config.has_section('swift'):
|
|
||||||
ret['swift'] = dict(config.items('swift'))
|
|
||||||
|
|
||||||
ret['projects'] = get_config(config, 'general', 'projects', PROJECTS)
|
|
||||||
if type(ret['projects']) is not list:
|
|
||||||
ret['projects'] = [x.strip() for x in ret['projects'].split(',')]
|
|
||||||
ret['json_url'] = get_config(config, 'general', 'json_url', JSON_URL)
|
|
||||||
ret['debug'] = get_config(config, 'general', 'debug', DEBUG)
|
|
||||||
ret['output_mode'] = get_config(config, 'general', 'output_mode',
|
|
||||||
OUTPUT_MODE)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
try:
|
|
||||||
conffile = sys.argv[1]
|
|
||||||
except IndexError:
|
|
||||||
conffile = os.path.join(curdir, '..', 'config', 'openstackwatch.ini')
|
|
||||||
CONFIG = parse_ini(conffile)
|
|
||||||
|
|
||||||
|
|
||||||
def debug(msg):
|
|
||||||
if DEBUG:
|
|
||||||
print(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def get_javascript(project=None):
|
|
||||||
url = CONFIG['json_url']
|
|
||||||
if project:
|
|
||||||
url += "+project:" + project
|
|
||||||
fp = urllib.urlretrieve(url)
|
|
||||||
ret = open(fp[0]).read()
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def parse_javascript(javascript):
|
|
||||||
for row in javascript.splitlines():
|
|
||||||
try:
|
|
||||||
json_row = json.loads(row)
|
|
||||||
except(ValueError):
|
|
||||||
continue
|
|
||||||
if not json_row or not 'project' in json_row or \
|
|
||||||
json_row['project'] not in CONFIG['projects']:
|
|
||||||
continue
|
|
||||||
yield json_row
|
|
||||||
|
|
||||||
|
|
||||||
def upload_rss(xml, output_object):
|
|
||||||
if 'swift' not in CONFIG:
|
|
||||||
print(xml)
|
|
||||||
return
|
|
||||||
|
|
||||||
import swiftclient
|
|
||||||
cfg = CONFIG['swift']
|
|
||||||
client = swiftclient.Connection(cfg['auth_url'],
|
|
||||||
cfg['username'],
|
|
||||||
cfg['password'],
|
|
||||||
auth_version=cfg.get('auth_version',
|
|
||||||
'2.0'))
|
|
||||||
try:
|
|
||||||
client.get_container(cfg['container'])
|
|
||||||
except(swiftclient.client.ClientException):
|
|
||||||
client.put_container(cfg['container'])
|
|
||||||
# eventual consistenties
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
client.put_object(cfg['container'], output_object,
|
|
||||||
cStringIO.StringIO(xml))
|
|
||||||
|
|
||||||
|
|
||||||
def generate_rss(javascript, project=""):
|
|
||||||
title = "OpenStack %s watch RSS feed" % (project)
|
|
||||||
rss = PyRSS2Gen.RSS2(
|
|
||||||
title=title,
|
|
||||||
link="http://github.com/chmouel/openstackwatch.rss",
|
|
||||||
description="The latest reviews about Openstack, straight "
|
|
||||||
"from Gerrit.",
|
|
||||||
lastBuildDate=datetime.datetime.now()
|
|
||||||
)
|
|
||||||
for row in parse_javascript(javascript):
|
|
||||||
author = row['owner']['name']
|
|
||||||
author += " <%s>" % ('email' in row['owner'] and
|
|
||||||
row['owner']['email']
|
|
||||||
or row['owner']['username'])
|
|
||||||
rss.items.append(
|
|
||||||
PyRSS2Gen.RSSItem(
|
|
||||||
title="%s [%s]: %s" % (os.path.basename(row['project']),
|
|
||||||
row['status'],
|
|
||||||
row['subject']),
|
|
||||||
author=author,
|
|
||||||
link=row['url'],
|
|
||||||
guid=PyRSS2Gen.Guid(row['id']),
|
|
||||||
description=row['subject'],
|
|
||||||
pubDate=datetime.datetime.fromtimestamp(row['lastUpdated']),
|
|
||||||
))
|
|
||||||
return rss.to_xml()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if CONFIG['output_mode'] == "combined":
|
|
||||||
upload_rss(generate_rss(get_javascript()),
|
|
||||||
CONFIG['swift']['combined_output_object'])
|
|
||||||
elif CONFIG['output_mode'] == "multiple":
|
|
||||||
for project in CONFIG['projects']:
|
|
||||||
upload_rss(
|
|
||||||
generate_rss(get_javascript(project), project=project),
|
|
||||||
"%s.xml" % (os.path.basename(project)))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,286 +0,0 @@
|
|||||||
# Copyright (c) 2010, Code Aurora Forum. All rights reserved.
|
|
||||||
# Copyright (c) 2012, Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions are
|
|
||||||
# met:
|
|
||||||
# # Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# # Redistributions in binary form must reproduce the above
|
|
||||||
# copyright notice, this list of conditions and the following
|
|
||||||
# disclaimer in the documentation and/or other materials provided
|
|
||||||
# with the distribution.
|
|
||||||
# # Neither the name of Code Aurora Forum, Inc. nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived
|
|
||||||
# from this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
||||||
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
|
||||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
|
||||||
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
||||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
||||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
||||||
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
||||||
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
# This script is designed to detect when a patchset uploaded to Gerrit is
|
|
||||||
# 'identical' (determined via git-patch-id) and reapply reviews onto the new
|
|
||||||
# patchset from the previous patchset.
|
|
||||||
|
|
||||||
# Get usage and help info by running: trivial-rebase --help
|
|
||||||
# Documentation is available here:
|
|
||||||
# https://www.codeaurora.org/xwiki/bin/QAEP/Gerrit
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import json
|
|
||||||
import optparse
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
class SilentOptionParser(optparse.OptionParser):
|
|
||||||
"""Make OptionParser silently swallow unrecognized options."""
|
|
||||||
def _process_args(self, largs, rargs, values):
|
|
||||||
while rargs:
|
|
||||||
try:
|
|
||||||
optparse.OptionParser._process_args(self, largs, rargs, values)
|
|
||||||
except (optparse.AmbiguousOptionError,
|
|
||||||
optparse.BadOptionError) as e:
|
|
||||||
largs.append(e.opt_str)
|
|
||||||
|
|
||||||
|
|
||||||
class CheckCallError(OSError):
|
|
||||||
"""CheckCall() returned non-0."""
|
|
||||||
def __init__(self, command, cwd, retcode, stdout, stderr=None):
|
|
||||||
OSError.__init__(self, command, cwd, retcode, stdout, stderr)
|
|
||||||
self.command = command
|
|
||||||
self.cwd = cwd
|
|
||||||
self.retcode = retcode
|
|
||||||
self.stdout = stdout
|
|
||||||
self.stderr = stderr
|
|
||||||
|
|
||||||
|
|
||||||
def CheckCall(command, cwd=None):
|
|
||||||
"""Like subprocess.check_call() but returns stdout.
|
|
||||||
|
|
||||||
Works on python 2.4
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
process = subprocess.Popen(command, cwd=cwd, stdout=subprocess.PIPE)
|
|
||||||
std_out, std_err = process.communicate()
|
|
||||||
except OSError as e:
|
|
||||||
raise CheckCallError(command, cwd, e.errno, None)
|
|
||||||
if process.returncode:
|
|
||||||
raise CheckCallError(command, cwd, process.returncode,
|
|
||||||
std_out, std_err)
|
|
||||||
return std_out, std_err
|
|
||||||
|
|
||||||
|
|
||||||
def Gssh(options, api_command):
|
|
||||||
"""Makes a Gerrit API call via SSH and returns the stdout results."""
|
|
||||||
ssh_cmd = ['ssh',
|
|
||||||
'-l', 'Gerrit Code Review',
|
|
||||||
'-p', options.port,
|
|
||||||
'-i', options.private_key_path,
|
|
||||||
options.server,
|
|
||||||
api_command]
|
|
||||||
try:
|
|
||||||
return CheckCall(ssh_cmd)[0]
|
|
||||||
except CheckCallError as e:
|
|
||||||
err_template = "call: %s\nreturn code: %s\nstdout: %s\nstderr: %s\n"
|
|
||||||
sys.stderr.write(err_template % (ssh_cmd,
|
|
||||||
e.retcode,
|
|
||||||
e.stdout,
|
|
||||||
e.stderr))
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def GsqlQuery(sql_query, options):
|
|
||||||
"""Runs a gerrit gsql query and returns the result."""
|
|
||||||
gsql_cmd = "gerrit gsql --format JSON -c %s" % sql_query
|
|
||||||
gsql_out = Gssh(options, gsql_cmd)
|
|
||||||
new_out = gsql_out.replace('}}\n', '}}\nsplit here\n')
|
|
||||||
return new_out.split('split here\n')
|
|
||||||
|
|
||||||
|
|
||||||
def FindPrevRev(options):
|
|
||||||
"""Finds the revision of the previous patch set on the change."""
|
|
||||||
sql_query = ("\"SELECT revision FROM patch_sets,changes WHERE "
|
|
||||||
"patch_sets.change_id = changes.change_id AND "
|
|
||||||
"patch_sets.patch_set_id = %s AND "
|
|
||||||
"changes.change_key = \'%s\'\"" % ((options.patchset - 1),
|
|
||||||
options.changeId))
|
|
||||||
revisions = GsqlQuery(sql_query, options)
|
|
||||||
|
|
||||||
json_dict = json.loads(revisions[0], strict=False)
|
|
||||||
return json_dict["columns"]["revision"]
|
|
||||||
|
|
||||||
|
|
||||||
def GetApprovals(options):
|
|
||||||
"""Get all the approvals on a specific patch set.
|
|
||||||
|
|
||||||
Returns a list of approval dicts
|
|
||||||
"""
|
|
||||||
sql_query = ("\"SELECT value,account_id,category_id"
|
|
||||||
" FROM patch_set_approvals"
|
|
||||||
" WHERE patch_set_id = %s"
|
|
||||||
" AND change_id = (SELECT change_id FROM"
|
|
||||||
" changes WHERE change_key = \'%s\') AND value <> 0\""
|
|
||||||
% ((options.patchset - 1), options.changeId))
|
|
||||||
gsql_out = GsqlQuery(sql_query, options)
|
|
||||||
approvals = []
|
|
||||||
for json_str in gsql_out:
|
|
||||||
dict = json.loads(json_str, strict=False)
|
|
||||||
if dict["type"] == "row":
|
|
||||||
approvals.append(dict["columns"])
|
|
||||||
return approvals
|
|
||||||
|
|
||||||
|
|
||||||
def GetPatchId(revision, consider_whitespace=False):
|
|
||||||
git_show_cmd = ['git', 'show', revision]
|
|
||||||
patch_id_cmd = ['git', 'patch-id']
|
|
||||||
patch_id_process = subprocess.Popen(patch_id_cmd, stdout=subprocess.PIPE,
|
|
||||||
stdin=subprocess.PIPE)
|
|
||||||
git_show_process = subprocess.Popen(git_show_cmd, stdout=subprocess.PIPE)
|
|
||||||
if consider_whitespace:
|
|
||||||
# This matches on change lines in the patch (those starting with "+"
|
|
||||||
# or "-" but not followed by another of the same), then replaces any
|
|
||||||
# space or tab characters with "%" before calculating a patch-id.
|
|
||||||
replace_ws_cmd = ['sed', r'/^\(+[^+]\|-[^-]\)/y/ \t/%%/']
|
|
||||||
replace_ws_process = subprocess.Popen(replace_ws_cmd,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stdin=subprocess.PIPE)
|
|
||||||
git_show_output = git_show_process.communicate()[0]
|
|
||||||
replace_ws_output = replace_ws_process.communicate(git_show_output)[0]
|
|
||||||
return patch_id_process.communicate(replace_ws_output)[0]
|
|
||||||
else:
|
|
||||||
return patch_id_process.communicate(
|
|
||||||
git_show_process.communicate()[0])[0]
|
|
||||||
|
|
||||||
|
|
||||||
def SuExec(options, as_user, cmd):
|
|
||||||
suexec_cmd = "suexec --as %s -- %s" % (as_user, cmd)
|
|
||||||
Gssh(options, suexec_cmd)
|
|
||||||
|
|
||||||
|
|
||||||
def DiffCommitMessages(commit1, commit2):
|
|
||||||
log_cmd1 = ['git', 'log', '--pretty=format:"%an %ae%n%s%n%b"',
|
|
||||||
commit1 + '^!']
|
|
||||||
commit1_log = CheckCall(log_cmd1)
|
|
||||||
log_cmd2 = ['git', 'log', '--pretty=format:"%an %ae%n%s%n%b"',
|
|
||||||
commit2 + '^!']
|
|
||||||
commit2_log = CheckCall(log_cmd2)
|
|
||||||
if commit1_log != commit2_log:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
usage = "usage: %prog <required options> [optional options]"
|
|
||||||
parser = SilentOptionParser(usage=usage)
|
|
||||||
parser.add_option("--change", dest="changeId", help="Change identifier")
|
|
||||||
parser.add_option("--project", help="Project path in Gerrit")
|
|
||||||
parser.add_option("--commit", help="Git commit-ish for this patchset")
|
|
||||||
parser.add_option("--patchset", type="int", help="The patchset number")
|
|
||||||
parser.add_option("--role-user", dest="role_user",
|
|
||||||
help="E-mail/ID of user commenting on commit messages")
|
|
||||||
parser.add_option("--private-key-path", dest="private_key_path",
|
|
||||||
help="Full path to Gerrit SSH daemon's private host key")
|
|
||||||
parser.add_option("--server-port", dest="port", default='29418',
|
|
||||||
help="Port to connect to Gerrit's SSH daemon "
|
|
||||||
"[default: %default]")
|
|
||||||
parser.add_option("--server", dest="server", default="localhost",
|
|
||||||
help="Server name/address for Gerrit's SSH daemon "
|
|
||||||
"[default: %default]")
|
|
||||||
parser.add_option("--whitespace", action="store_true",
|
|
||||||
help="Treat whitespace as significant")
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
|
||||||
|
|
||||||
if not options.changeId:
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
if options.patchset == 1:
|
|
||||||
# Nothing to detect on first patchset
|
|
||||||
sys.exit(0)
|
|
||||||
prev_revision = None
|
|
||||||
prev_revision = FindPrevRev(options)
|
|
||||||
if not prev_revision:
|
|
||||||
# Couldn't find a previous revision
|
|
||||||
sys.exit(0)
|
|
||||||
prev_patch_id = GetPatchId(prev_revision)
|
|
||||||
cur_patch_id = GetPatchId(options.commit)
|
|
||||||
if cur_patch_id.split()[0] != prev_patch_id.split()[0]:
|
|
||||||
# patch-ids don't match
|
|
||||||
sys.exit(0)
|
|
||||||
# Patch ids match. This is a trivial rebase.
|
|
||||||
# In addition to patch-id we should check if whitespace content changed.
|
|
||||||
# Some languages are more sensitive to whitespace than others, and some
|
|
||||||
# changes may either introduce or be intended to fix style problems
|
|
||||||
# specifically involving whitespace as well.
|
|
||||||
if options.whitespace:
|
|
||||||
prev_patch_ws = GetPatchId(prev_revision, consider_whitespace=True)
|
|
||||||
cur_patch_ws = GetPatchId(options.commit, consider_whitespace=True)
|
|
||||||
if cur_patch_ws.split()[0] != prev_patch_ws.split()[0]:
|
|
||||||
# Insert a comment into the change letting the approvers know
|
|
||||||
# only the whitespace changed
|
|
||||||
comment_msg = ("\"New patchset patch-id matches previous patchset,"
|
|
||||||
" but whitespace content has changed.\"")
|
|
||||||
comment_cmd = ['gerrit', 'approve', '--project', options.project,
|
|
||||||
'--message', comment_msg, options.commit]
|
|
||||||
SuExec(options, options.role_user, ' '.join(comment_cmd))
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# We should also check if the commit message changed. Most approvers would
|
|
||||||
# want to re-review changes when the commit message changes.
|
|
||||||
changed = DiffCommitMessages(prev_revision, options.commit)
|
|
||||||
if changed:
|
|
||||||
# Insert a comment into the change letting the approvers know only the
|
|
||||||
# commit message changed
|
|
||||||
comment_msg = ("\"New patchset patch-id matches previous patchset,"
|
|
||||||
" but commit message has changed.\"")
|
|
||||||
comment_cmd = ['gerrit', 'approve', '--project', options.project,
|
|
||||||
'--message', comment_msg, options.commit]
|
|
||||||
SuExec(options, options.role_user, ' '.join(comment_cmd))
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# Need to get all approvals on prior patch set, then suexec them onto
|
|
||||||
# this patchset.
|
|
||||||
approvals = GetApprovals(options)
|
|
||||||
gerrit_approve_msg = ("\'Automatically re-added by Gerrit trivial rebase "
|
|
||||||
"detection script.\'")
|
|
||||||
for approval in approvals:
|
|
||||||
# Note: Sites with different 'copy_min_score' values in the
|
|
||||||
# approval_categories DB table might want different behavior here.
|
|
||||||
# Additional categories should also be added if desired.
|
|
||||||
if approval["category_id"] == "CRVW":
|
|
||||||
approve_category = '--code-review'
|
|
||||||
elif approval["category_id"] == "VRIF":
|
|
||||||
# Don't re-add verifies
|
|
||||||
#approve_category = '--verified'
|
|
||||||
continue
|
|
||||||
elif approval["category_id"] == "SUBM":
|
|
||||||
# We don't care about previous submit attempts
|
|
||||||
continue
|
|
||||||
elif approval["category_id"] == "APRV":
|
|
||||||
# Similarly squash old approvals
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
print("Unsupported category: %s" % approval)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
score = approval["value"]
|
|
||||||
gerrit_approve_cmd = ['gerrit', 'approve',
|
|
||||||
'--project', options.project,
|
|
||||||
'--message', gerrit_approve_msg,
|
|
||||||
approve_category, score, options.commit]
|
|
||||||
SuExec(options, approval["account_id"], ' '.join(gerrit_approve_cmd))
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,158 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# This is designed to be called by a gerrit hook. It searched new
|
|
||||||
# patchsets for strings like "blueprint FOO" or "bp FOO" and updates
|
|
||||||
# corresponding Launchpad blueprints with links back to the change.
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import ConfigParser
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import StringIO
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from launchpadlib import launchpad
|
|
||||||
from launchpadlib import uris
|
|
||||||
import MySQLdb
|
|
||||||
|
|
||||||
BASE_DIR = '/home/gerrit2/review_site'
|
|
||||||
GERRIT_CACHE_DIR = os.path.expanduser(
|
|
||||||
os.environ.get('GERRIT_CACHE_DIR',
|
|
||||||
'~/.launchpadlib/cache'))
|
|
||||||
GERRIT_CREDENTIALS = os.path.expanduser(
|
|
||||||
os.environ.get('GERRIT_CREDENTIALS',
|
|
||||||
'~/.launchpadlib/creds'))
|
|
||||||
GERRIT_CONFIG = os.environ.get('GERRIT_CONFIG',
|
|
||||||
'/home/gerrit2/review_site/etc/gerrit.config')
|
|
||||||
GERRIT_SECURE_CONFIG_DEFAULT = '/home/gerrit2/review_site/etc/secure.config'
|
|
||||||
GERRIT_SECURE_CONFIG = os.environ.get('GERRIT_SECURE_CONFIG',
|
|
||||||
GERRIT_SECURE_CONFIG_DEFAULT)
|
|
||||||
SPEC_RE = re.compile(r'\b(blueprint|bp)\b[ \t]*[#:]?[ \t]*(\S+)', re.I)
|
|
||||||
BODY_RE = re.compile(r'^\s+.*$')
|
|
||||||
|
|
||||||
|
|
||||||
def get_broken_config(filename):
|
|
||||||
"""gerrit config ini files are broken and have leading tabs."""
|
|
||||||
text = ""
|
|
||||||
with open(filename, "r") as conf:
|
|
||||||
for line in conf.readlines():
|
|
||||||
text = "%s%s" % (text, line.lstrip())
|
|
||||||
|
|
||||||
fp = StringIO.StringIO(text)
|
|
||||||
c = ConfigParser.ConfigParser()
|
|
||||||
c.readfp(fp)
|
|
||||||
return c
|
|
||||||
|
|
||||||
GERRIT_CONFIG = get_broken_config(GERRIT_CONFIG)
|
|
||||||
SECURE_CONFIG = get_broken_config(GERRIT_SECURE_CONFIG)
|
|
||||||
DB_USER = GERRIT_CONFIG.get("database", "username")
|
|
||||||
DB_PASS = SECURE_CONFIG.get("database", "password")
|
|
||||||
DB_DB = GERRIT_CONFIG.get("database", "database")
|
|
||||||
|
|
||||||
|
|
||||||
def short_project(full_project_name):
|
|
||||||
"""Return the project part of the git repository name."""
|
|
||||||
return full_project_name.split('/')[-1]
|
|
||||||
|
|
||||||
|
|
||||||
def git2lp(full_project_name):
|
|
||||||
"""Convert Git repo name to Launchpad project."""
|
|
||||||
project_map = {
|
|
||||||
'stackforge/puppet-openstack_dev_env': 'puppet-openstack',
|
|
||||||
'stackforge/puppet-quantum': 'puppet-neutron',
|
|
||||||
}
|
|
||||||
return project_map.get(full_project_name, short_project(full_project_name))
|
|
||||||
|
|
||||||
|
|
||||||
def update_spec(launchpad, project, name, subject, link, topic=None):
|
|
||||||
project = git2lp(project)
|
|
||||||
spec = launchpad.projects[project].getSpecification(name=name)
|
|
||||||
if not spec:
|
|
||||||
return
|
|
||||||
|
|
||||||
if spec.whiteboard:
|
|
||||||
wb = spec.whiteboard.strip()
|
|
||||||
else:
|
|
||||||
wb = ''
|
|
||||||
changed = False
|
|
||||||
if topic:
|
|
||||||
topiclink = '%s/#q,topic:%s,n,z' % (link[:link.find('/', 8)],
|
|
||||||
topic)
|
|
||||||
if topiclink not in wb:
|
|
||||||
wb += "\n\n\nGerrit topic: %(link)s" % dict(link=topiclink)
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if link not in wb:
|
|
||||||
wb += ("\n\n\nAddressed by: {link}\n"
|
|
||||||
" {subject}\n").format(subject=subject,
|
|
||||||
link=link)
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
spec.whiteboard = wb
|
|
||||||
spec.lp_save()
|
|
||||||
|
|
||||||
|
|
||||||
def find_specs(launchpad, dbconn, args):
|
|
||||||
git_dir_arg = '--git-dir={base_dir}/git/{project}.git'.format(
|
|
||||||
base_dir=BASE_DIR,
|
|
||||||
project=args.project)
|
|
||||||
git_log = subprocess.Popen(['git', git_dir_arg, 'log', '--no-merges',
|
|
||||||
args.commit + '^1..' + args.commit],
|
|
||||||
stdout=subprocess.PIPE).communicate()[0]
|
|
||||||
|
|
||||||
cur = dbconn.cursor()
|
|
||||||
cur.execute("select subject, topic from changes where change_key=%s",
|
|
||||||
args.change)
|
|
||||||
subject, topic = cur.fetchone()
|
|
||||||
specs = set([m.group(2) for m in SPEC_RE.finditer(git_log)])
|
|
||||||
|
|
||||||
if topic:
|
|
||||||
topicspec = topic.split('/')[-1]
|
|
||||||
specs |= set([topicspec])
|
|
||||||
|
|
||||||
for spec in specs:
|
|
||||||
update_spec(launchpad, args.project, spec, subject,
|
|
||||||
args.change_url, topic)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('hook')
|
|
||||||
#common
|
|
||||||
parser.add_argument('--change', default=None)
|
|
||||||
parser.add_argument('--change-url', default=None)
|
|
||||||
parser.add_argument('--project', default=None)
|
|
||||||
parser.add_argument('--branch', default=None)
|
|
||||||
parser.add_argument('--commit', default=None)
|
|
||||||
#change-merged
|
|
||||||
parser.add_argument('--submitter', default=None)
|
|
||||||
# patchset-created
|
|
||||||
parser.add_argument('--uploader', default=None)
|
|
||||||
parser.add_argument('--patchset', default=None)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
lpconn = launchpad.Launchpad.login_with(
|
|
||||||
'Gerrit User Sync', uris.LPNET_SERVICE_ROOT, GERRIT_CACHE_DIR,
|
|
||||||
credentials_file=GERRIT_CREDENTIALS, version='devel')
|
|
||||||
|
|
||||||
conn = MySQLdb.connect(user=DB_USER, passwd=DB_PASS, db=DB_DB)
|
|
||||||
|
|
||||||
find_specs(lpconn, conn, args)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,405 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2011 OpenStack, LLC.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# This is designed to be called by a gerrit hook. It searched new
|
|
||||||
# patchsets for strings like "bug FOO" and updates corresponding Launchpad
|
|
||||||
# bugs status.
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from launchpadlib import launchpad
|
|
||||||
from launchpadlib import uris
|
|
||||||
|
|
||||||
import jeepyb.gerritdb
|
|
||||||
|
|
||||||
BASE_DIR = '/home/gerrit2/review_site'
|
|
||||||
GERRIT_CACHE_DIR = os.path.expanduser(
|
|
||||||
os.environ.get('GERRIT_CACHE_DIR',
|
|
||||||
'~/.launchpadlib/cache'))
|
|
||||||
GERRIT_CREDENTIALS = os.path.expanduser(
|
|
||||||
os.environ.get('GERRIT_CREDENTIALS',
|
|
||||||
'~/.launchpadlib/creds'))
|
|
||||||
|
|
||||||
|
|
||||||
def add_change_proposed_message(bugtask, change_url, project, branch):
|
|
||||||
subject = 'Fix proposed to %s (%s)' % (short_project(project), branch)
|
|
||||||
body = 'Fix proposed to branch: %s\nReview: %s' % (branch, change_url)
|
|
||||||
bugtask.bug.newMessage(subject=subject, content=body)
|
|
||||||
|
|
||||||
|
|
||||||
def add_change_merged_message(bugtask, change_url, project, commit,
|
|
||||||
submitter, branch, git_log):
|
|
||||||
subject = 'Fix merged to %s (%s)' % (short_project(project), branch)
|
|
||||||
git_url = 'http://github.com/%s/commit/%s' % (project, commit)
|
|
||||||
body = '''Reviewed: %s
|
|
||||||
Committed: %s
|
|
||||||
Submitter: %s
|
|
||||||
Branch: %s\n''' % (change_url, git_url, submitter, branch)
|
|
||||||
body = body + '\n' + git_log
|
|
||||||
bugtask.bug.newMessage(subject=subject, content=body)
|
|
||||||
|
|
||||||
|
|
||||||
def set_in_progress(bugtask, launchpad, uploader, change_url):
|
|
||||||
"""Set bug In progress with assignee being the uploader"""
|
|
||||||
|
|
||||||
# Retrieve uploader from Launchpad by correlating Gerrit E-mail
|
|
||||||
# address to OpenID, and only set if there is a clear match.
|
|
||||||
try:
|
|
||||||
searchkey = uploader[uploader.rindex("(") + 1:-1]
|
|
||||||
except ValueError:
|
|
||||||
searchkey = uploader
|
|
||||||
|
|
||||||
# The counterintuitive query is due to odd database schema choices
|
|
||||||
# in Gerrit. For example, an account with a secondary E-mail
|
|
||||||
# address added looks like...
|
|
||||||
# select email_address,external_id from account_external_ids
|
|
||||||
# where account_id=1234;
|
|
||||||
# +-----------------+-----------------------------------------+
|
|
||||||
# | email_address | external_id |
|
|
||||||
# +-----------------+-----------------------------------------+
|
|
||||||
# | plugh@xyzzy.com | https://login.launchpad.net/+id/fR0bnU1 |
|
|
||||||
# | bar@foo.org | mailto:bar@foo.org |
|
|
||||||
# | NULL | username:quux |
|
|
||||||
# +-----------------+-----------------------------------------+
|
|
||||||
# ...thus we need a join on a secondary query to search against
|
|
||||||
# all the user's configured E-mail addresses.
|
|
||||||
#
|
|
||||||
query = """SELECT t.external_id FROM account_external_ids t
|
|
||||||
INNER JOIN (
|
|
||||||
SELECT t.account_id FROM account_external_ids t
|
|
||||||
WHERE t.email_address = %s )
|
|
||||||
original ON t.account_id = original.account_id
|
|
||||||
AND t.external_id LIKE 'https://login.launchpad.net%%'"""
|
|
||||||
|
|
||||||
cursor = jeepyb.gerritdb.connect().cursor()
|
|
||||||
cursor.execute(query, searchkey)
|
|
||||||
data = cursor.fetchone()
|
|
||||||
if data:
|
|
||||||
assignee = launchpad.people.getByOpenIDIdentifier(identifier=data[0])
|
|
||||||
if assignee:
|
|
||||||
bugtask.assignee = assignee
|
|
||||||
|
|
||||||
bugtask.status = "In Progress"
|
|
||||||
bugtask.lp_save()
|
|
||||||
|
|
||||||
|
|
||||||
def set_fix_committed(bugtask):
|
|
||||||
"""Set bug fix committed."""
|
|
||||||
|
|
||||||
bugtask.status = "Fix Committed"
|
|
||||||
bugtask.lp_save()
|
|
||||||
|
|
||||||
|
|
||||||
def set_fix_released(bugtask):
|
|
||||||
"""Set bug fix released."""
|
|
||||||
|
|
||||||
bugtask.status = "Fix Released"
|
|
||||||
bugtask.lp_save()
|
|
||||||
|
|
||||||
|
|
||||||
def release_fixcommitted(bugtask):
|
|
||||||
"""Set bug FixReleased if it was FixCommitted."""
|
|
||||||
|
|
||||||
if bugtask.status == u'Fix Committed':
|
|
||||||
set_fix_released(bugtask)
|
|
||||||
|
|
||||||
|
|
||||||
def tag_in_branchname(bugtask, branch):
|
|
||||||
"""Tag bug with in-branch-name tag (if name is appropriate)."""
|
|
||||||
|
|
||||||
lp_bug = bugtask.bug
|
|
||||||
branch_name = branch.replace('/', '-')
|
|
||||||
if branch_name.replace('-', '').isalnum():
|
|
||||||
lp_bug.tags = lp_bug.tags + ["in-%s" % branch_name]
|
|
||||||
lp_bug.tags.append("in-%s" % branch_name)
|
|
||||||
lp_bug.lp_save()
|
|
||||||
|
|
||||||
|
|
||||||
def short_project(full_project_name):
|
|
||||||
"""Return the project part of the git repository name."""
|
|
||||||
return full_project_name.split('/')[-1]
|
|
||||||
|
|
||||||
|
|
||||||
def git2lp(full_project_name):
|
|
||||||
"""Convert Git repo name to Launchpad project."""
|
|
||||||
project_map = {
|
|
||||||
'openstack/api-site': 'openstack-api-site',
|
|
||||||
'openstack/quantum': 'neutron',
|
|
||||||
'openstack/python-quantumclient': 'python-neutronclient',
|
|
||||||
'openstack/oslo-incubator': 'oslo',
|
|
||||||
'openstack/tripleo-incubator': 'tripleo',
|
|
||||||
'openstack-infra/askbot-theme': 'openstack-ci',
|
|
||||||
'openstack-infra/config': 'openstack-ci',
|
|
||||||
'openstack-infra/devstack-gate': 'openstack-ci',
|
|
||||||
'openstack-infra/gear': 'openstack-ci',
|
|
||||||
'openstack-infra/gerrit': 'openstack-ci',
|
|
||||||
'openstack-infra/gerritbot': 'openstack-ci',
|
|
||||||
'openstack-infra/gerritlib': 'openstack-ci',
|
|
||||||
'openstack-infra/gitdm': 'openstack-ci',
|
|
||||||
'openstack-infra/jeepyb': 'openstack-ci',
|
|
||||||
'openstack-infra/jenkins-job-builder': 'openstack-ci',
|
|
||||||
'openstack-infra/lodgeit': 'openstack-ci',
|
|
||||||
'openstack-infra/meetbot': 'openstack-ci',
|
|
||||||
'openstack-infra/nose-html-output': 'openstack-ci',
|
|
||||||
'openstack-infra/publications': 'openstack-ci',
|
|
||||||
'openstack-infra/puppet-apparmor': 'openstack-ci',
|
|
||||||
'openstack-infra/puppet-dashboard': 'openstack-ci',
|
|
||||||
'openstack-infra/puppet-vcsrepo': 'openstack-ci',
|
|
||||||
'openstack-infra/reviewday': 'openstack-ci',
|
|
||||||
'openstack-infra/statusbot': 'openstack-ci',
|
|
||||||
'openstack-infra/zmq-event-publisher': 'openstack-ci',
|
|
||||||
'stackforge/cookbook-openstack-block-storage': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-common': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-compute': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-dashboard': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-identity': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-image': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-metering': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-network': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-object-storage': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-ops-database': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-ops-messaging': 'openstack-chef',
|
|
||||||
'stackforge/cookbook-openstack-orchestration': 'openstack-chef',
|
|
||||||
'stackforge/openstack-chef-repo': 'openstack-chef',
|
|
||||||
'stackforge/puppet-openstack_dev_env': 'puppet-openstack',
|
|
||||||
'stackforge/puppet-quantum': 'puppet-neutron',
|
|
||||||
'stackforge/tripleo-heat-templates': 'tripleo',
|
|
||||||
'stackforge/tripleo-image-elements': 'tripleo',
|
|
||||||
}
|
|
||||||
return project_map.get(full_project_name, short_project(full_project_name))
|
|
||||||
|
|
||||||
|
|
||||||
def is_direct_release(full_project_name):
|
|
||||||
"""Test against a list of projects who directly release changes."""
|
|
||||||
return full_project_name in [
|
|
||||||
'openstack/openstack-manuals',
|
|
||||||
'openstack/api-site',
|
|
||||||
'openstack/tripleo-incubator',
|
|
||||||
'openstack-dev/devstack',
|
|
||||||
'openstack-infra/askbot-theme',
|
|
||||||
'openstack-infra/config',
|
|
||||||
'openstack-infra/devstack-gate',
|
|
||||||
'openstack-infra/gerrit',
|
|
||||||
'openstack-infra/gerritbot',
|
|
||||||
'openstack-infra/gerritlib',
|
|
||||||
'openstack-infra/gitdm',
|
|
||||||
'openstack-infra/lodgeit',
|
|
||||||
'openstack-infra/meetbot',
|
|
||||||
'openstack-infra/nose-html-output',
|
|
||||||
'openstack-infra/publications',
|
|
||||||
'openstack-infra/reviewday',
|
|
||||||
'openstack-infra/statusbot',
|
|
||||||
'stackforge/cookbook-openstack-block-storage',
|
|
||||||
'stackforge/cookbook-openstack-common',
|
|
||||||
'stackforge/cookbook-openstack-compute',
|
|
||||||
'stackforge/cookbook-openstack-dashboard',
|
|
||||||
'stackforge/cookbook-openstack-identity',
|
|
||||||
'stackforge/cookbook-openstack-image',
|
|
||||||
'stackforge/cookbook-openstack-metering',
|
|
||||||
'stackforge/cookbook-openstack-network',
|
|
||||||
'stackforge/cookbook-openstack-object-storage',
|
|
||||||
'stackforge/cookbook-openstack-ops-database',
|
|
||||||
'stackforge/cookbook-openstack-ops-messaging',
|
|
||||||
'stackforge/cookbook-openstack-orchestration',
|
|
||||||
'stackforge/openstack-chef-repo',
|
|
||||||
'stackforge/tripleo-heat-templates',
|
|
||||||
'stackforge/tripleo-image-elements',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class Task:
|
|
||||||
def __init__(self, lp_task, prefix):
|
|
||||||
'''Prefixes associated with bug references will allow for certain
|
|
||||||
changes to be made to the bug's launchpad (lp) page. The following
|
|
||||||
tokens represent the automation currently taking place.
|
|
||||||
|
|
||||||
::
|
|
||||||
add_comment -> Adds a comment to the bug's lp page.
|
|
||||||
set_in_progress -> Sets the bug's lp status to 'In Progress'.
|
|
||||||
set_fix_released -> Sets the bug's lp status to 'Fix Released'.
|
|
||||||
set_fix_committed -> Sets the bug's lp status to 'Fix Committed'.
|
|
||||||
::
|
|
||||||
|
|
||||||
changes_needed, when populated, simply indicates the actions that are
|
|
||||||
available to be taken based on the value of 'prefix'.
|
|
||||||
'''
|
|
||||||
self.lp_task = lp_task
|
|
||||||
self.changes_needed = []
|
|
||||||
|
|
||||||
# If no prefix was matched, default to 'closes'.
|
|
||||||
prefix = prefix.split('-')[0].lower() if prefix else 'closes'
|
|
||||||
|
|
||||||
if prefix in ('closes', 'fixes', 'resolves'):
|
|
||||||
self.changes_needed.extend(('add_comment',
|
|
||||||
'set_in_progress',
|
|
||||||
'set_fix_committed',
|
|
||||||
'set_fix_released'))
|
|
||||||
elif prefix in ('partial',):
|
|
||||||
self.changes_needed.extend(('add_comment', 'set_in_progress'))
|
|
||||||
elif prefix in ('related', 'impacts', 'affects'):
|
|
||||||
self.changes_needed.extend(('add_comment',))
|
|
||||||
else:
|
|
||||||
# prefix is not recognized.
|
|
||||||
self.changes_needed.extend(('add_comment',))
|
|
||||||
|
|
||||||
def needs_change(self, change):
|
|
||||||
'''Return a boolean indicating if given 'change' needs to be made.'''
|
|
||||||
if change in self.changes_needed:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def process_bugtask(launchpad, task, git_log, args):
|
|
||||||
"""Apply changes to lp bug tasks, based on hook / branch."""
|
|
||||||
|
|
||||||
bugtask = task.lp_task
|
|
||||||
|
|
||||||
if args.hook == "change-merged":
|
|
||||||
if args.branch == 'master':
|
|
||||||
if (is_direct_release(args.project) and
|
|
||||||
task.needs_change('set_fix_released')):
|
|
||||||
set_fix_released(bugtask)
|
|
||||||
else:
|
|
||||||
if (bugtask.status != u'Fix Released' and
|
|
||||||
task.needs_change('set_fix_committed')):
|
|
||||||
set_fix_committed(bugtask)
|
|
||||||
elif args.branch == 'milestone-proposed':
|
|
||||||
release_fixcommitted(bugtask)
|
|
||||||
elif args.branch.startswith('stable/'):
|
|
||||||
series = args.branch[7:]
|
|
||||||
# Look for a related task matching the series.
|
|
||||||
for reltask in bugtask.related_tasks:
|
|
||||||
if (reltask.bug_target_name.endswith("/" + series) and
|
|
||||||
reltask.status != u'Fix Released' and
|
|
||||||
task.needs_change('set_fix_committed')):
|
|
||||||
set_fix_committed(reltask)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Use tag_in_branchname if there isn't any.
|
|
||||||
tag_in_branchname(bugtask, args.branch)
|
|
||||||
|
|
||||||
add_change_merged_message(bugtask, args.change_url, args.project,
|
|
||||||
args.commit, args.submitter, args.branch,
|
|
||||||
git_log)
|
|
||||||
|
|
||||||
if args.hook == "patchset-created":
|
|
||||||
if args.branch == 'master':
|
|
||||||
if (bugtask.status not in [u'Fix Committed', u'Fix Released'] and
|
|
||||||
task.needs_change('set_in_progress')):
|
|
||||||
set_in_progress(bugtask, launchpad,
|
|
||||||
args.uploader, args.change_url)
|
|
||||||
elif args.branch.startswith('stable/'):
|
|
||||||
series = args.branch[7:]
|
|
||||||
for reltask in bugtask.related_tasks:
|
|
||||||
if (reltask.bug_target_name.endswith("/" + series) and
|
|
||||||
task.needs_change('set_in_progress') and
|
|
||||||
reltask.status not in [u'Fix Committed',
|
|
||||||
u'Fix Released']):
|
|
||||||
set_in_progress(reltask, launchpad,
|
|
||||||
args.uploader, args.change_url)
|
|
||||||
break
|
|
||||||
|
|
||||||
if args.patchset == '1':
|
|
||||||
add_change_proposed_message(bugtask, args.change_url,
|
|
||||||
args.project, args.branch)
|
|
||||||
|
|
||||||
|
|
||||||
def find_bugs(launchpad, git_log, args):
|
|
||||||
'''Find bugs referenced in the git log and return related tasks.
|
|
||||||
|
|
||||||
Our regular expression is composed of three major parts:
|
|
||||||
part1: Matches only at start-of-line (required). Optionally matches any
|
|
||||||
word or hyphen separated words.
|
|
||||||
part2: Matches the words 'bug' or 'lp' on a word boundry (required).
|
|
||||||
part3: Matches a whole number (required).
|
|
||||||
|
|
||||||
The following patterns will be matched properly:
|
|
||||||
bug # 555555
|
|
||||||
Closes-Bug: 555555
|
|
||||||
Fixes: bug # 555555
|
|
||||||
Resolves: bug 555555
|
|
||||||
Partial-Bug: lp bug # 555555
|
|
||||||
|
|
||||||
:returns: an iterable containing Task objects.
|
|
||||||
'''
|
|
||||||
|
|
||||||
part1 = r'^[\t ]*(?P<prefix>[-\w]+)?[\s:]*'
|
|
||||||
part2 = r'(?:\b(?:bug|lp)\b[\s#:]*)+'
|
|
||||||
part3 = r'(?P<bug_number>\d+)\s*?$'
|
|
||||||
regexp = part1 + part2 + part3
|
|
||||||
matches = re.finditer(regexp, git_log, flags=re.I | re.M)
|
|
||||||
|
|
||||||
# Extract unique bug tasks and associated prefixes.
|
|
||||||
bugtasks = {}
|
|
||||||
for match in matches:
|
|
||||||
prefix = match.group('prefix')
|
|
||||||
bug_num = match.group('bug_number')
|
|
||||||
if bug_num not in bugtasks:
|
|
||||||
try:
|
|
||||||
lp_bug = launchpad.bugs[bug_num]
|
|
||||||
for lp_task in lp_bug.bug_tasks:
|
|
||||||
if lp_task.bug_target_name == git2lp(args.project):
|
|
||||||
bugtasks[bug_num] = Task(lp_task, prefix)
|
|
||||||
break
|
|
||||||
except KeyError:
|
|
||||||
# Unknown bug.
|
|
||||||
pass
|
|
||||||
|
|
||||||
return bugtasks.values()
|
|
||||||
|
|
||||||
|
|
||||||
def extract_git_log(args):
|
|
||||||
"""Extract git log of all merged commits."""
|
|
||||||
cmd = ['git',
|
|
||||||
'--git-dir=' + BASE_DIR + '/git/' + args.project + '.git',
|
|
||||||
'log', '--no-merges', args.commit + '^1..' + args.commit]
|
|
||||||
return subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('hook')
|
|
||||||
# common
|
|
||||||
parser.add_argument('--change', default=None)
|
|
||||||
parser.add_argument('--change-url', default=None)
|
|
||||||
parser.add_argument('--project', default=None)
|
|
||||||
parser.add_argument('--branch', default=None)
|
|
||||||
parser.add_argument('--commit', default=None)
|
|
||||||
# change-merged
|
|
||||||
parser.add_argument('--submitter', default=None)
|
|
||||||
# patchset-created
|
|
||||||
parser.add_argument('--uploader', default=None)
|
|
||||||
parser.add_argument('--patchset', default=None)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Connect to Launchpad.
|
|
||||||
lpconn = launchpad.Launchpad.login_with(
|
|
||||||
'Gerrit User Sync', uris.LPNET_SERVICE_ROOT, GERRIT_CACHE_DIR,
|
|
||||||
credentials_file=GERRIT_CREDENTIALS, version='devel')
|
|
||||||
|
|
||||||
# Get git log.
|
|
||||||
git_log = extract_git_log(args)
|
|
||||||
|
|
||||||
# Process tasks found in git log.
|
|
||||||
for task in find_bugs(lpconn, git_log, args):
|
|
||||||
process_bugtask(lpconn, task, git_log, args)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,39 +0,0 @@
|
|||||||
# -*- Mode: conf -*-
|
|
||||||
|
|
||||||
[general]
|
|
||||||
# only show certain projects (don't forget the openstack/ as start)
|
|
||||||
projects = openstack/swift, openstack/cinder
|
|
||||||
|
|
||||||
# The Json URL where is the gerrit system.
|
|
||||||
json_url = https://review.openstack.org/query?q=status:open
|
|
||||||
|
|
||||||
# Allow different mode to output to swift, by default 'combined' will
|
|
||||||
# combined all rss in one and 'multiple' will upload all the projects
|
|
||||||
# in each rss file.
|
|
||||||
output_mode = multiple
|
|
||||||
|
|
||||||
# username to your swift cluster
|
|
||||||
# [swift]
|
|
||||||
# username/tenant for swift with 2.0 or just username with 1.0 (i.e:
|
|
||||||
# RAX).
|
|
||||||
# username =
|
|
||||||
|
|
||||||
# passowrd or api key
|
|
||||||
# password =
|
|
||||||
|
|
||||||
# container to upload (probably want to be public)
|
|
||||||
# container =
|
|
||||||
|
|
||||||
# auth_url of the cluster, for Rackspace this is :
|
|
||||||
# https://auth.api.rackspacecloud.com/v1.0
|
|
||||||
# or Rackspace UK :
|
|
||||||
# https://lon.auth.api.rackspacecloud.com/v1.0
|
|
||||||
# auth_url = https://lon.auth.api.rackspacecloud.com/v1.0
|
|
||||||
|
|
||||||
# auth version (1.0 for Rackspace clouds, 2.0 for keystone backend clusters)
|
|
||||||
# auth_version = 1.0
|
|
||||||
|
|
||||||
# the object name where to store the combined rss
|
|
||||||
# combined_output_object = openstackwatch.xml
|
|
||||||
|
|
||||||
# vim: ft=dosini
|
|
@ -1,55 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
# Copyright (C) 2011 OpenStack, LLC.
|
|
||||||
# Copyright (c) 2012 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import ConfigParser
|
|
||||||
import MySQLdb
|
|
||||||
import os
|
|
||||||
import StringIO
|
|
||||||
|
|
||||||
|
|
||||||
GERRIT_CONFIG = os.environ.get(
|
|
||||||
'GERRIT_CONFIG',
|
|
||||||
'/home/gerrit2/review_site/etc/gerrit.config')
|
|
||||||
GERRIT_SECURE_CONFIG = os.environ.get(
|
|
||||||
'GERRIT_SECURE_CONFIG',
|
|
||||||
'/home/gerrit2/review_site/etc/secure.config')
|
|
||||||
db_connection = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_broken_config(filename):
|
|
||||||
"""gerrit config ini files are broken and have leading tabs."""
|
|
||||||
text = ""
|
|
||||||
for line in open(filename, "r"):
|
|
||||||
text += line.lstrip()
|
|
||||||
|
|
||||||
fp = StringIO.StringIO(text)
|
|
||||||
c = ConfigParser.ConfigParser()
|
|
||||||
c.readfp(fp)
|
|
||||||
return c
|
|
||||||
|
|
||||||
|
|
||||||
def connect():
|
|
||||||
global db_connection
|
|
||||||
if not db_connection:
|
|
||||||
gerrit_config = get_broken_config(GERRIT_CONFIG)
|
|
||||||
secure_config = get_broken_config(GERRIT_SECURE_CONFIG)
|
|
||||||
|
|
||||||
DB_USER = gerrit_config.get("database", "username")
|
|
||||||
DB_PASS = secure_config.get("database", "password")
|
|
||||||
DB_DB = gerrit_config.get("database", "database")
|
|
||||||
|
|
||||||
db_connection = MySQLdb.connect(user=DB_USER, passwd=DB_PASS, db=DB_DB)
|
|
||||||
return db_connection
|
|
@ -1,9 +1,3 @@
|
|||||||
gerritlib
|
PyYAML>=3.1.0
|
||||||
MySQL-python
|
|
||||||
paramiko
|
|
||||||
PyGithub
|
|
||||||
pyyaml
|
|
||||||
pkginfo
|
pkginfo
|
||||||
PyRSS2Gen
|
|
||||||
python-swiftclient
|
|
||||||
pip>=1.4
|
pip>=1.4
|
||||||
|
21
setup.cfg
21
setup.cfg
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = jeepyb
|
name = pypi-mirror
|
||||||
summary = Tools for managing gerrit projects and external sources.
|
summary = Utility to build a partial PyPI mirror
|
||||||
description-file =
|
description-file =
|
||||||
README.rst
|
README.rst
|
||||||
author = OpenStack Infrastructure Team
|
author = OpenStack Infrastructure Team
|
||||||
@ -16,21 +16,6 @@ classifier =
|
|||||||
Programming Language :: Python :: 2.7
|
Programming Language :: Python :: 2.7
|
||||||
Programming Language :: Python :: 2.6
|
Programming Language :: Python :: 2.6
|
||||||
|
|
||||||
[global]
|
|
||||||
setup-hooks =
|
|
||||||
pbr.hooks.setup_hook
|
|
||||||
|
|
||||||
[entry_points]
|
[entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
close-pull-requests = jeepyb.cmd.close_pull_requests:main
|
run-mirror = pypi_mirror.cmd.run_mirror:main
|
||||||
create-cgitrepos = jeepyb.cmd.create_cgitrepos:main
|
|
||||||
expire-old-reviews = jeepyb.cmd.expire_old_reviews:main
|
|
||||||
fetch-remotes = jeepyb.cmd.fetch_remotes:main
|
|
||||||
manage-projects = jeepyb.cmd.manage_projects:main
|
|
||||||
notify-impact = jeepyb.cmd.notify_impact:main
|
|
||||||
openstackwatch = jeepyb.cmd.openstackwatch:main
|
|
||||||
process-cache = jeepyb.cmd.process_cache:main
|
|
||||||
run-mirror = jeepyb.cmd.run_mirror:main
|
|
||||||
trivial-rebase = jeepyb.cmd.trivial_rebase:main
|
|
||||||
update-blueprint = jeepyb.cmd.update_blueprint:main
|
|
||||||
update-bug = jeepyb.cmd.update_bug:main
|
|
||||||
|
5
setup.py
5
setup.py
@ -14,8 +14,9 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||||
import setuptools
|
import setuptools
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
setup_requires=['d2to1', 'pbr'],
|
setup_requires=['pbr'],
|
||||||
d2to1=True)
|
pbr=True)
|
||||||
|
@ -1 +1 @@
|
|||||||
hacking>=0.5.6,<0.7
|
hacking>=0.5.6,<0.8
|
||||||
|
Loading…
x
Reference in New Issue
Block a user