Merge "Inspect all configs in manage-projects"
diff --git a/.gitreview b/.gitreview
index 60f203a..b11a679 100644
--- a/.gitreview
+++ b/.gitreview
@@ -1,4 +1,4 @@
 [gerrit]
-host=review.openstack.org
+host=review.opendev.org
 port=29418
-project=openstack-infra/jeepyb.git
+project=opendev/jeepyb.git
diff --git a/.zuul.yaml b/.zuul.yaml
new file mode 100644
index 0000000..6107d2f
--- /dev/null
+++ b/.zuul.yaml
@@ -0,0 +1,9 @@
+- project:
+    check:
+      jobs:
+        - gerritlib-jeepyb-integration
+        - tox-pep8
+    gate:
+      jobs:
+        - gerritlib-jeepyb-integration
+        - tox-pep8
diff --git a/jeepyb/cmd/close_pull_requests.py b/jeepyb/cmd/close_pull_requests.py
index ad41f41..4ead4ad 100644
--- a/jeepyb/cmd/close_pull_requests.py
+++ b/jeepyb/cmd/close_pull_requests.py
@@ -39,7 +39,7 @@
 # oauth_token = GITHUB_OAUTH_TOKEN
 
 import argparse
-import ConfigParser
+from six.moves import configparser
 import github
 import logging
 import os
@@ -87,7 +87,7 @@
     GITHUB_SECURE_CONFIG = os.environ.get('GITHUB_SECURE_CONFIG',
                                           '/etc/github/github.secure.config')
 
-    secure_config = ConfigParser.ConfigParser()
+    secure_config = configparser.ConfigParser()
     secure_config.read(GITHUB_SECURE_CONFIG)
     registry = u.ProjectsRegistry()
 
diff --git a/jeepyb/cmd/create_hound_config.py b/jeepyb/cmd/create_hound_config.py
index 0cb529f..601ba74 100644
--- a/jeepyb/cmd/create_hound_config.py
+++ b/jeepyb/cmd/create_hound_config.py
@@ -19,38 +19,44 @@
 import json
 import os
 
+# Python2 has unicode as a builtin
+# Python3 does not
+import sys
+if sys.version_info[0] >= 3:
+    unicode = str
+
 import jeepyb.utils as u
 
 
 PROJECTS_YAML = os.environ.get('PROJECTS_YAML', '/home/hound/projects.yaml')
-GIT_SERVER = os.environ.get('GIT_BASE', 'git.openstack.org')
+GIT_SERVER = os.environ.get('GIT_BASE', 'opendev.org')
 DATA_PATH = os.environ.get('DATA_PATH', 'data')
-GIT_PROTOCOL = os.environ.get('GIT_PROTOCOL', 'git://')
+GIT_PROTOCOL = os.environ.get('GIT_PROTOCOL', 'https://')
 
 
 def main():
     registry = u.ProjectsRegistry(PROJECTS_YAML)
-    projects = [entry['project'] for entry in registry.configs_list]
     repos = {}
-    for project in projects:
+    for entry in registry.configs_list:
+        project = entry['project']
+        # Don't bother indexing RETIRED projects.
+        if entry.get('description', '').startswith('RETIRED'):
+            continue
+        if 'retired.config' in entry.get('acl-config', ''):
+            continue
         # Ignore attic and stackforge, those are repos that are not
         # active anymore.
         if project.startswith(('openstack-attic', 'stackforge')):
             continue
-        basename = os.path.basename(project)
-        # ignore deb- projects that are forks of other projects intended for
-        # internal debian packaging needs only and are generally not of
-        # interest to upstream developers
-        if basename.startswith('deb-'):
-            continue
-        repos[basename] = {
+        repos[project] = {
             'url': "%(proto)s%(gitbase)s/%(project)s" % dict(
                 proto=GIT_PROTOCOL, gitbase=GIT_SERVER, project=project),
             'url-pattern': {
-                'base-url': "http://%(gitbase)s/cgit/%(project)s"
-                            "/tree/{path}{anchor}" % dict(gitbase=GIT_SERVER,
-                                                          project=project),
-                'anchor': '#n{line}',
+                'base-url': "https://%(gitbase)s/%(project)s"
+                            "/src/branch/master/{path}{anchor}" % dict(
+                                gitbase=GIT_SERVER,
+                                project=project),
+                'anchor': '#L{line}',
             }
         }
 
diff --git a/jeepyb/cmd/manage_projects.py b/jeepyb/cmd/manage_projects.py
index 27dde5e..caa9388 100644
--- a/jeepyb/cmd/manage_projects.py
+++ b/jeepyb/cmd/manage_projects.py
@@ -51,7 +51,7 @@
 #     project: OTHER_PROJECT_NAME
 
 import argparse
-import ConfigParser
+from six.moves import configparser
 import glob
 import hashlib
 import json
@@ -65,7 +65,6 @@
 import gerritlib.gerrit
 import github
 
-import jeepyb.gerritdb
 import jeepyb.log as l
 import jeepyb.utils as u
 
@@ -173,19 +172,21 @@
 def push_acl_config(project, remote_url, repo_path, gitid, env=None):
     env = env or {}
     cmd = "commit -a -m'Update project config.' --author='%s'" % gitid
-    status = u.git_command(repo_path, cmd)
+    status, out = u.git_command_output(repo_path, cmd)
     if status != 0:
         log.error("Failed to commit config for project: %s" % project)
+        log.error(out)
         return False
     status, out = u.git_command_output(
         repo_path, "push %s HEAD:refs/meta/config" % remote_url, env)
     if status != 0:
         log.error("Failed to push config for project: %s" % project)
+        log.error(out)
         return False
     return True
 
 
-def _get_group_uuid(group, retries=10):
+def _get_group_uuid(gerrit, group, retries=10):
     """
     Gerrit keeps internal user groups in the DB while it keeps systems
     groups in All-Projects groups file (in refs/meta/config).  This
@@ -196,34 +197,33 @@
 
     Wait for up to 10 seconds for the group to be created in the DB.
     """
-    query = "SELECT group_uuid FROM account_groups WHERE name = %s"
-    con = jeepyb.gerritdb.connect()
     for x in range(retries):
-        cursor = con.cursor()
-        cursor.execute(query, (group,))
-        data = cursor.fetchone()
-        cursor.close()
-        con.commit()
-        if data:
-            return data[0]
+        # Work around gerritlib raising a generic "Exception" exception
+        # when listGroup() finds no group
+        try:
+            group_list = list(gerrit.listGroup(group, verbose=True))
+        except Exception:
+            group_list = None
+        if group_list:
+            return group_list[0].split('\t')[1]
         if retries > 1:
             time.sleep(1)
     return None
 
 
 def get_group_uuid(gerrit, group):
-    uuid = _get_group_uuid(group, retries=1)
+    uuid = _get_group_uuid(gerrit, group, retries=1)
     if uuid:
         return uuid
     if group in GERRIT_SYSTEM_GROUPS:
         return GERRIT_SYSTEM_GROUPS[group]
     gerrit.createGroup(group)
-    for user in gerrit.listMembers(group):
-        if gerrit.username == user['username']:
+    for user in list(gerrit.listMembers(group)):
+        if gerrit.connection.username == user['username']:
             # Gerrit now adds creating user to groups. We don't want that.
-            gerrit.removeMember(group, gerrit.username)
+            gerrit.removeMember(group, gerrit.connection.username)
             break
-    uuid = _get_group_uuid(group)
+    uuid = _get_group_uuid(gerrit, group)
     if uuid:
         return uuid
     return None
@@ -278,7 +278,7 @@
     if not needs_update:
         return False
 
-    secure_config = ConfigParser.ConfigParser()
+    secure_config = configparser.ConfigParser()
     secure_config.read(github_secure_config)
 
     global orgs
@@ -462,14 +462,14 @@
     acl_cache = {}
     for acl_file in glob.glob(os.path.join(ACL_DIR, '*/*.config')):
         sha256 = hashlib.sha256()
-        sha256.update(open(acl_file, 'r').read())
+        sha256.update(open(acl_file, 'r').read().encode('utf-8'))
         acl_cache[acl_file] = sha256.hexdigest()
 
     gerrit = gerritlib.gerrit.Gerrit(GERRIT_HOST,
                                      GERRIT_USER,
                                      GERRIT_PORT,
                                      GERRIT_KEY)
-    project_list = gerrit.listProjects()
+    project_list = list(gerrit.listProjects())
     ssh_env = u.make_ssh_wrapper(GERRIT_USER, GERRIT_KEY)
     try:
         # Collect processed errors,if any
diff --git a/jeepyb/cmd/notify_impact.py b/jeepyb/cmd/notify_impact.py
index f30f042..5b402f5 100644
--- a/jeepyb/cmd/notify_impact.py
+++ b/jeepyb/cmd/notify_impact.py
@@ -48,7 +48,8 @@
 logger = logging.getLogger('notify_impact')
 
 DOC_TAG = "doc"
-BASE_DIR = '/home/gerrit2/review_site'
+GERRIT_GIT_DIR = os.environ.get(
+    'GERRIT_GIT_DIR', '/home/gerrit2/review_site/git')
 EMAIL_TEMPLATE = """
 Hi, I'd like you to take a look at this patch for potential
 %s.
@@ -241,9 +242,10 @@
 def extract_git_log(args):
     """Extract git log of all merged commits."""
     cmd = ['git',
-           '--git-dir=' + BASE_DIR + '/git/' + args.project + '.git',
+           '--git-dir=' + GERRIT_GIT_DIR + '/' + args.project + '.git',
            'log', '--no-merges', args.commit + '^1..' + args.commit]
-    return subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
+    return subprocess.Popen(
+        cmd, stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
 
 
 def main():
diff --git a/jeepyb/cmd/openstackwatch.py b/jeepyb/cmd/openstackwatch.py
index 47add37..c9d6a54 100644
--- a/jeepyb/cmd/openstackwatch.py
+++ b/jeepyb/cmd/openstackwatch.py
@@ -23,8 +23,8 @@
 
 __author__ = "Chmouel Boudjnah <chmouel@chmouel.com>"
 
-import ConfigParser
-import cStringIO
+import six
+from six.moves import configparser
 import datetime
 import json
 import os
@@ -63,7 +63,7 @@
     ret = {}
     if not os.path.exists(inifile):
         return
-    config = ConfigParser.RawConfigParser(allow_no_value=True)
+    config = configparser.RawConfigParser(allow_no_value=True)
     config.read(inifile)
 
     if config.has_section('swift'):
@@ -127,7 +127,7 @@
         time.sleep(1)
 
     client.put_object(cfg['container'], objectname,
-                      cStringIO.StringIO(content))
+                      six.StringIO(content))
 
 
 def generate_rss(content, project=""):
diff --git a/jeepyb/cmd/update_blueprint.py b/jeepyb/cmd/update_blueprint.py
index 093e73b..22efa84 100644
--- a/jeepyb/cmd/update_blueprint.py
+++ b/jeepyb/cmd/update_blueprint.py
@@ -18,10 +18,10 @@
 # corresponding Launchpad blueprints with links back to the change.
 
 import argparse
-import ConfigParser
+import six
+from six.moves import configparser
 import os
 import re
-import StringIO
 import subprocess
 
 from launchpadlib import launchpad
@@ -31,7 +31,8 @@
 from jeepyb import projects as p
 
 
-BASE_DIR = '/home/gerrit2/review_site'
+GERRIT_GIT_DIR = os.environ.get(
+    'GERRIT_GIT_DIR', '/home/gerrit2/review_site/git')
 GERRIT_CACHE_DIR = os.path.expanduser(
     os.environ.get('GERRIT_CACHE_DIR',
                    '~/.launchpadlib/cache'))
@@ -54,8 +55,8 @@
         for line in conf.readlines():
             text = "%s%s" % (text, line.lstrip())
 
-    fp = StringIO.StringIO(text)
-    c = ConfigParser.ConfigParser()
+    fp = six.StringIO(text)
+    c = configparser.ConfigParser(strict=False)
     c.readfp(fp)
     return c
 
@@ -89,8 +90,8 @@
         wb = ''
     changed = False
     if topic:
-        topiclink = '%s/#q,topic:%s,n,z' % (link[:link.find('/', 8)],
-                                            topic)
+        topiclink = '%s/#/q/topic:%s' % (link[:link.find('/', 8)],
+                                         topic)
         if topiclink not in wb:
             wb += "\n\n\nGerrit topic: %(link)s" % dict(link=topiclink)
             changed = True
@@ -107,12 +108,15 @@
 
 
 def find_specs(launchpad, dbconn, args):
-    git_dir_arg = '--git-dir={base_dir}/git/{project}.git'.format(
-        base_dir=BASE_DIR,
+    git_dir_arg = '--git-dir={base_dir}/{project}.git'.format(
+        base_dir=GERRIT_GIT_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]
+    git_log = subprocess.Popen(
+        [
+            'git', git_dir_arg, 'log', '--no-merges',
+            args.commit + '^1..' + args.commit
+        ],
+        stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
 
     change = args.change
     if '~' in change:
diff --git a/jeepyb/cmd/update_bug.py b/jeepyb/cmd/update_bug.py
index 212ee7a..64fa536 100644
--- a/jeepyb/cmd/update_bug.py
+++ b/jeepyb/cmd/update_bug.py
@@ -30,7 +30,8 @@
 from jeepyb import utils as u
 
 
-BASE_DIR = '/home/gerrit2/review_site'
+GERRIT_GIT_DIR = os.environ.get(
+    'GERRIT_GIT_DIR', '/home/gerrit2/review_site/git')
 GERRIT_CACHE_DIR = os.path.expanduser(
     os.environ.get('GERRIT_CACHE_DIR',
                    '~/.launchpadlib/cache'))
@@ -332,9 +333,10 @@
 def extract_git_log(args):
     """Extract git log of all merged commits."""
     cmd = ['git',
-           '--git-dir=' + BASE_DIR + '/git/' + args.project + '.git',
+           '--git-dir=' + GERRIT_GIT_DIR + '/' + args.project + '.git',
            'log', '--no-merges', args.commit + '^1..' + args.commit]
-    return subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
+    return subprocess.Popen(
+        cmd, stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
 
 
 def main():
diff --git a/jeepyb/cmd/welcome_message.py b/jeepyb/cmd/welcome_message.py
index 3694c89..b28f5e1 100644
--- a/jeepyb/cmd/welcome_message.py
+++ b/jeepyb/cmd/welcome_message.py
@@ -33,8 +33,6 @@
 import jeepyb.gerritdb
 import jeepyb.log as l
 
-BASE_DIR = '/home/gerrit2/review_site'
-
 logger = logging.getLogger('welcome_reviews')
 
 
@@ -69,30 +67,28 @@
 def post_message(commit, gerrit_user, gerrit_ssh_key, message_file):
     """Post a welcome message on the patch set specified by the commit."""
 
-    default_text = """Thank you for your first contribution to OpenStack.
+    default_text = """\
+Congratulations, you've proposed your first change in OpenDev.
 
-Your patch will now be tested automatically by OpenStack testing frameworks
-and once the automatic tests pass, it will be reviewed by other friendly
-developers. They will give you feedback and may require you to refine it.
+Your submission will now be tested automatically by Zuul, our gatekeeper,
+and reviewed by other friendly developers. They will give you feedback and
+may require you to refine it.
 
 People seldom get their patch approved on the first try, so don't be
 concerned if requested to make corrections. Feel free to modify your patch
 and resubmit a new change-set.
 
-Patches usually take 3 to 7 days to be reviewed so be patient and be
-available on IRC to ask and answer questions about your work. Also it
-takes generally at least a couple of weeks for cores to get around to
-reviewing code. The more you participate in the community the more
-rewarding it is for you. You may also notice that the more you get to know
-people and get to be known, the faster your patches will be reviewed and
-eventually approved. Get to know others and become known by doing code
-reviews: anybody can do it, and it's a great way to learn the code base.
+Patches often take days (and sometimes weeks) to get reviewed, so be
+patient. Don't hesitate to ask for help, and answer questions about your
+work promptly if you can. The more you get to know reviewers and get to be
+known by them, the smoother the review and approval process will become. The
+fastest way to accomplish this is by reviewing other proposed changes
+yourself: anybody can do it, and it's a great way to learn the code base.
 
-Thanks again for supporting OpenStack, we look forward to working with you.
+Thanks again for participating in OpenDev, we look forward to seeing you
+around.
 
-IRC: https://wiki.openstack.org/wiki/IRC
-Workflow: https://docs.openstack.org/infra/manual/developers.html
-Commit Messages: https://wiki.openstack.org/wiki/GitCommitMessages
+Workflow Guide: https://docs.openstack.org/infra/manual/developers.html
 """
 
     if message_file:
diff --git a/jeepyb/gerritdb.py b/jeepyb/gerritdb.py
index 79f311a..8c06d2c 100644
--- a/jeepyb/gerritdb.py
+++ b/jeepyb/gerritdb.py
@@ -14,9 +14,9 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-import ConfigParser
+import six
+from six.moves import configparser
 import os
-import StringIO
 
 
 GERRIT_CONFIG = os.environ.get(
@@ -34,8 +34,8 @@
     for line in open(filename, "r"):
         text += line.lstrip()
 
-    fp = StringIO.StringIO(text)
-    c = ConfigParser.ConfigParser()
+    fp = six.StringIO(text)
+    c = configparser.ConfigParser(strict=False)
     c.readfp(fp)
     return c
 
diff --git a/jeepyb/projects.py b/jeepyb/projects.py
index 043f7cf..bcf29b2 100644
--- a/jeepyb/projects.py
+++ b/jeepyb/projects.py
@@ -26,7 +26,7 @@
     - no-launchpad-blueprints
 """
 
-import ConfigParser
+from six.moves import configparser
 
 import jeepyb.utils as u
 
@@ -69,7 +69,7 @@
                 # ...and if it's not set, then still don't use it.
                 return False
     # It's okay if the global option or even the section for this don't exist.
-    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+    except (configparser.NoSectionError, configparser.NoOptionError):
         pass
     # If we got this far, we either explicitly or implicitly default to use it.
     return True
diff --git a/jeepyb/utils.py b/jeepyb/utils.py
index f51d13c..8e6ab34 100644
--- a/jeepyb/utils.py
+++ b/jeepyb/utils.py
@@ -12,7 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-import ConfigParser
+from six.moves import configparser
 import logging
 import os
 import shlex
@@ -52,6 +52,7 @@
     p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
                          stderr=subprocess.STDOUT, env=newenv)
     (out, nothing) = p.communicate()
+    out = out.decode('utf-8')
     log.debug("Return code: %s" % p.returncode)
     log.debug("Command said: %s" % out.strip())
     if status:
@@ -82,10 +83,10 @@
 
 def make_ssh_wrapper(gerrit_user, gerrit_key):
     (fd, name) = tempfile.mkstemp(text=True)
-    os.write(fd, '#!/bin/bash\n')
+    os.write(fd, b'#!/bin/bash\n')
     os.write(fd,
-             'ssh -i %s -l %s -o "StrictHostKeyChecking no" $@\n' %
-             (gerrit_key, gerrit_user))
+             b'ssh -i %s -l %s -o "StrictHostKeyChecking no" $@\n' %
+             (gerrit_key.encode('utf-8'), gerrit_user.encode('utf-8')))
     os.close(fd)
     os.chmod(name, 0o755)
     return dict(GIT_SSH=name)
@@ -196,7 +197,7 @@
             self._configs_list = self.yaml_doc[1]
 
         if os.path.exists(PROJECTS_INI):
-            self.defaults = ConfigParser.ConfigParser()
+            self.defaults = configparser.ConfigParser()
             self.defaults.read(PROJECTS_INI)
         else:
             try:
diff --git a/tox.ini b/tox.ini
index 925577c..835b4ac 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,6 +5,7 @@
 setenv = VIRTUAL_ENV={envdir}
 deps = -r{toxinidir}/requirements.txt
        -r{toxinidir}/test-requirements.txt
+basepython = python3
 
 [testenv:pep8]
 commands = flake8