Merge "Wait for groups to be created before getting uuids"
diff --git a/jeepyb/cmd/update_blueprint.py b/jeepyb/cmd/update_blueprint.py
index 738ba13..b0865aa 100644
--- a/jeepyb/cmd/update_blueprint.py
+++ b/jeepyb/cmd/update_blueprint.py
@@ -28,6 +28,9 @@
 from launchpadlib import uris
 import MySQLdb
 
+from jeepyb import projects as p
+
+
 BASE_DIR = '/home/gerrit2/review_site'
 GERRIT_CACHE_DIR = os.path.expanduser(
     os.environ.get('GERRIT_CACHE_DIR',
@@ -63,22 +66,11 @@
 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)
+    if p.is_no_launchpad_blueprints(project):
+        return
+
+    project = p.git2lp(project)
     spec = launchpad.projects[project].getSpecification(name=name)
     if not spec:
         return
diff --git a/jeepyb/cmd/update_bug.py b/jeepyb/cmd/update_bug.py
index 3a72546..fdf2b5a 100644
--- a/jeepyb/cmd/update_bug.py
+++ b/jeepyb/cmd/update_bug.py
@@ -26,6 +26,9 @@
 from launchpadlib import uris
 
 import jeepyb.gerritdb
+from jeepyb import projects as p
+from jeepyb import utils as u
+
 
 BASE_DIR = '/home/gerrit2/review_site'
 GERRIT_CACHE_DIR = os.path.expanduser(
@@ -46,7 +49,8 @@
 def add_change_proposed_message(bugtask, change_url, project, branch,
                                 related=False):
     fix = fix_or_related_fix(related)
-    subject = '%s proposed to %s (%s)' % (fix, short_project(project), branch)
+    subject = ('%s proposed to %s (%s)'
+               % (fix, u.short_project_name(project), branch))
     body = '%s proposed to branch: %s\nReview: %s' % (fix, branch, change_url)
     bugtask.bug.newMessage(subject=subject, content=body)
 
@@ -54,7 +58,7 @@
 def add_change_merged_message(bugtask, change_url, project, commit,
                               submitter, branch, git_log, related=False):
     subject = '%s merged to %s (%s)' % (fix_or_related_fix(related),
-                                        short_project(project), branch)
+                                        u.short_project_name(project), branch)
     git_url = 'http://github.com/%s/commit/%s' % (project, commit)
     body = '''Reviewed:  %s
 Committed: %s
@@ -140,106 +144,6 @@
         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/identity-api': 'openstack-api-site',
-        'openstack/object-api': 'openstack-api-site',
-        'openstack/volume-api': 'openstack-api-site',
-        'openstack/netconn-api': 'openstack-api-site',
-        'openstack/compute-api': 'openstack-api-site',
-        'openstack/image-api': 'openstack-api-site',
-        'openstack/database-api': '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/tempest',
-        '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
@@ -291,7 +195,7 @@
 
     if args.hook == "change-merged":
         if args.branch == 'master':
-            if (is_direct_release(args.project) and
+            if (p.is_direct_release(args.project) and
                     task.needs_change('set_fix_released')):
                 set_fix_released(bugtask)
             else:
@@ -362,6 +266,13 @@
     :returns: an iterable containing Task objects.
     '''
 
+    project = args.project
+
+    if p.is_no_launchpad_bugs(project):
+        return []
+
+    project = p.git2lp(project)
+
     part1 = r'^[\t ]*(?P<prefix>[-\w]+)?[\s:]*'
     part2 = r'(?:\b(?:bug|lp)\b[\s#:]*)+'
     part3 = r'(?P<bug_number>\d+)\s*?$'
@@ -377,7 +288,7 @@
             try:
                 lp_bug = launchpad.bugs[bug_num]
                 for lp_task in lp_bug.bug_tasks:
-                    if lp_task.bug_target_name == git2lp(args.project):
+                    if lp_task.bug_target_name == project:
                         bugtasks[bug_num] = Task(lp_task, prefix)
                         break
             except KeyError:
@@ -424,5 +335,6 @@
     for task in find_bugs(lpconn, git_log, args):
         process_bugtask(lpconn, task, git_log, args)
 
+
 if __name__ == "__main__":
     main()
diff --git a/jeepyb/projects.py b/jeepyb/projects.py
new file mode 100644
index 0000000..4e74b78
--- /dev/null
+++ b/jeepyb/projects.py
@@ -0,0 +1,192 @@
+# Copyright (c) 2013 Mirantis.
+#
+# 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.
+
+"""
+Expected review.projects.yaml format:
+
+- project: some/project
+  launchpad: awesomeproject
+  description: Best project ever.
+  options:
+    - direct-release
+    - no-launchpad-bugs
+    - no-launchpad-blueprints
+"""
+
+import jeepyb.utils as u
+
+
+registry = u.ProjectsYamlRegistry('/home/gerrit2/projects.yaml',
+                                  'PROJECTS_YAML')
+
+
+def git2lp(project_full_name):
+    try:
+        return registry[project_full_name]['launchpad']
+    except KeyError:
+        return _hardcoded_git2lp(project_full_name)
+        # return u.short_project_name(project_full_name)
+
+
+def _is_no_launchpad(project_full_name, obj_type):
+    try:
+        return ('no-launchpad-' + obj_type
+                in registry[project_full_name]['options'])
+    except KeyError:
+        return False
+
+
+def is_no_launchpad_bugs(project_full_name):
+    return _is_no_launchpad(project_full_name, 'bugs')
+
+
+def is_no_launchpad_blueprints(project_full_name):
+    return _is_no_launchpad(project_full_name, 'blueprints')
+
+
+def is_direct_release(project_full_name):
+    try:
+        direct = 'direct-release' in registry[project_full_name]['options']
+        # return ...
+    except KeyError:
+        direct = False
+        # return False
+
+    return direct or _hardcoded_is_direct_release(project_full_name)
+
+
+# The following functions should be deleted when projects.yaml will be updated
+
+def _hardcoded_is_direct_release(project_full_name):
+    """Test against a list of projects who directly release changes.
+
+    This function should be removed when projects.yaml will be updated.
+    To specify direct_release you just need add option 'direct_relese' to your
+    project declaration in projects.yaml
+
+    Example:
+        - project: some/project
+          options:
+            - direct-release
+          description: Best project ever.
+    """
+    return project_full_name in [
+        'openstack/openstack-manuals',
+        'openstack/api-site',
+        'openstack/tripleo-incubator',
+        'openstack/tempest',
+        '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',
+    ]
+
+
+def _hardcoded_git2lp(project_full_name):
+    """Convert Git repo name to Launchpad project.
+
+    This function should be removed when projects.yaml will be updated.
+    To specify launchpad project name you just need add parameter 'lp' to your
+    project declaration in projects.yaml
+
+    Example:
+        - project: some/project
+          launchpad: awesomeproject
+          description: Best project ever.
+    """
+
+    project_map = {
+        'openstack/api-site': 'openstack-api-site',
+        'openstack/identity-api': 'openstack-api-site',
+        'openstack/object-api': 'openstack-api-site',
+        'openstack/volume-api': 'openstack-api-site',
+        'openstack/netconn-api': 'openstack-api-site',
+        'openstack/compute-api': 'openstack-api-site',
+        'openstack/image-api': 'openstack-api-site',
+        'openstack/database-api': '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',
+        'stackforge/savanna': 'savanna',
+        'stackforge/savanna-dashboard': 'savanna',
+        'stackforge/savanna-extra': 'savanna',
+        'stackforge/savanna-image-elements': 'savanna',
+        'stackforge/python-savannaclient': 'savanna',
+        'stackforge/puppet-savanna': 'savanna'
+    }
+    return project_map.get(project_full_name,
+                           u.short_project_name(project_full_name))
diff --git a/jeepyb/utils.py b/jeepyb/utils.py
new file mode 100644
index 0000000..5ff3830
--- /dev/null
+++ b/jeepyb/utils.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2013 Mirantis.
+#
+# 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 os
+import yaml
+
+
+def short_project_name(full_project_name):
+    """Return the project part of the git repository name."""
+    return full_project_name.split('/')[-1]
+
+
+class ProjectsYamlRegistry(object):
+    """review.projects.yaml style config file parser.
+
+    It could be used as dict 'project name' -> 'project properties'.
+    """
+
+    def __init__(self, file_path, env_name=None):
+        self.file_path = file_path
+        self.env_name = env_name
+
+        self._parse_file()
+
+    def _parse_file(self):
+        file_path = os.environ.get(self.env_name, self.file_path)
+        configs_list = [config for config in yaml.load_all(open(file_path))][1]
+
+        configs = {}
+        for section in configs_list:
+            configs[section['project']] = section
+
+        self.configs = configs
+
+    def __getitem__(self, item):
+        return self.configs[item]