617 lines
23 KiB
Python
617 lines
23 KiB
Python
#! /usr/bin/env python
|
|
# Copyright (C) 2011 OpenStack Foundation
|
|
# 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 config file called projects.ini
|
|
# It should look like:
|
|
#
|
|
# [projects]
|
|
# homepage=https://opendev.org
|
|
# acl-dir=/home/gerrit2/acls
|
|
# local-git-dir=/opt/lib/git
|
|
# jeepyb-cache-dir=/opt/lib/jeepyb
|
|
# gerrit-host=review.opendev.org
|
|
# gerrit-user=project-creator
|
|
# gerrit-committer=Project Creator <project-creator@opendev.org>
|
|
# gerrit-key=/home/gerrit2/review_site/etc/ssh_project_rsa_key
|
|
# has-github=false
|
|
#
|
|
# manage_projects.py reads a project listing file called projects.yaml
|
|
# It should look like:
|
|
# - project: PROJECT_NAME
|
|
# options:
|
|
# - has-wiki
|
|
# - has-issues
|
|
# - has-downloads
|
|
# - has-pull-requests
|
|
# - track-upstream
|
|
# homepage: Some homepage that isn't http://opendev.org
|
|
# description: This is a great project
|
|
# upstream: https://gerrit.googlesource.com/gerrit
|
|
# upstream-prefix: upstream
|
|
# acl-config: /path/to/gerrit/project.config
|
|
# acl-append:
|
|
# - /path/to/gerrit/project.config
|
|
# acl-parameters:
|
|
# project: OTHER_PROJECT_NAME
|
|
|
|
import argparse
|
|
from six.moves import configparser
|
|
import glob
|
|
import hashlib
|
|
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import time
|
|
|
|
import gerritlib.gerrit
|
|
import github
|
|
|
|
import jeepyb.log
|
|
import jeepyb.utils as u
|
|
|
|
registry = u.ProjectsRegistry()
|
|
|
|
log = logging.getLogger("manage_projects")
|
|
orgs = None
|
|
|
|
# Gerrit system groups as defined:
|
|
# https://review.opendev.org/Documentation/access-control.html#system_groups
|
|
# Need to set Gerrit system group's uuid to the format it expects.
|
|
GERRIT_SYSTEM_GROUPS = {
|
|
'Anonymous Users': 'global:Anonymous-Users',
|
|
'Project Owners': 'global:Project-Owners',
|
|
'Registered Users': 'global:Registered-Users',
|
|
'Change Owner': 'global:Change-Owner',
|
|
}
|
|
|
|
|
|
class FetchConfigException(Exception):
|
|
pass
|
|
|
|
|
|
class CopyACLException(Exception):
|
|
pass
|
|
|
|
|
|
class ProcessACLException(Exception):
|
|
pass
|
|
|
|
|
|
class PushToGerritException(Exception):
|
|
pass
|
|
|
|
|
|
class CreateGroupException(Exception):
|
|
pass
|
|
|
|
|
|
def fetch_config(project, remote_url, repo_path, env=None):
|
|
env = env or {}
|
|
# Poll for refs/meta/config as gerrit may not have written it out for
|
|
# us yet.
|
|
for x in range(10):
|
|
status = u.git_command(
|
|
repo_path,
|
|
"fetch %s +refs/meta/config:refs/remotes/gerrit-meta/config"
|
|
% remote_url, env)
|
|
if status == 0:
|
|
break
|
|
else:
|
|
log.debug("Failed to fetch refs/meta/config for project: %s" %
|
|
project)
|
|
time.sleep(2)
|
|
if status != 0:
|
|
log.error("Failed to fetch refs/meta/config for project: %s" % project)
|
|
raise FetchConfigException()
|
|
|
|
# Poll for project.config as gerrit may not have committed an empty
|
|
# one yet.
|
|
output = ""
|
|
for x in range(10):
|
|
status = u.git_command(repo_path, "remote update --prune", env)
|
|
if status != 0:
|
|
log.error("Failed to update remote: %s" % remote_url)
|
|
time.sleep(2)
|
|
continue
|
|
else:
|
|
status, output = u.git_command_output(
|
|
repo_path, "ls-files --with-tree=remotes/gerrit-meta/config "
|
|
"project.config", env)
|
|
if output.strip() != "project.config" or status != 0:
|
|
log.debug("Failed to find project.config for project: %s" %
|
|
project)
|
|
time.sleep(2)
|
|
else:
|
|
break
|
|
if output.strip() != "project.config" or status != 0:
|
|
log.error("Failed to find project.config for project: %s" % project)
|
|
raise FetchConfigException()
|
|
|
|
# Because the following fails if executed more than once you should only
|
|
# run fetch_config once in each repo.
|
|
status = u.git_command(
|
|
repo_path, "checkout -B config remotes/gerrit-meta/config")
|
|
if status != 0:
|
|
log.error("Failed to checkout config for project: %s" % project)
|
|
raise FetchConfigException()
|
|
|
|
|
|
def copy_acl_config(project, repo_path, acl_config):
|
|
if not os.path.exists(acl_config):
|
|
raise CopyACLException()
|
|
|
|
acl_dest = os.path.join(repo_path, "project.config")
|
|
status, _ = u.run_command(
|
|
"cp %s %s" % (acl_config, acl_dest), status=True)
|
|
if status != 0:
|
|
raise CopyACLException()
|
|
|
|
status = u.git_command(repo_path, "diff --quiet")
|
|
return status != 0
|
|
|
|
|
|
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, 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)
|
|
if 'project state does not permit write' in out:
|
|
# We tried to push an acl update to a read only project.
|
|
# This is an error to Gerrit, but we treat it as success
|
|
# to allow other acls to update.
|
|
return True
|
|
else:
|
|
return False
|
|
return True
|
|
|
|
|
|
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
|
|
will only get the UUIDs for internal user groups.
|
|
|
|
Note: 'Administrators', 'Non-Interactive Users' and all other custom
|
|
groups in Gerrit are defined as internal user groups.
|
|
|
|
Wait for up to 10 seconds for the group to be created in the DB.
|
|
"""
|
|
for x in range(retries):
|
|
# 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(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 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.connection.username)
|
|
break
|
|
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'^.*\sgroup\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:
|
|
log.error("Unable to get UUID for group %s." % group)
|
|
raise CreateGroupException()
|
|
if uuids:
|
|
with open(group_file, 'w') as fp:
|
|
for group, uuid in uuids.items():
|
|
fp.write("%s\t%s\n" % (uuid, group))
|
|
status = u.git_command(repo_path, "add groups")
|
|
if status != 0:
|
|
log.error("Failed to add groups file for project: %s" % project)
|
|
raise CreateGroupException()
|
|
|
|
|
|
def create_update_github_project(
|
|
default_has_issues, default_has_downloads, default_has_wiki,
|
|
github_secure_config, options, project, description, homepage,
|
|
cache):
|
|
created = 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
|
|
|
|
needs_update = False
|
|
if not cache.get('created-in-github', False):
|
|
needs_update = True
|
|
if not cache.get('gerrit-in-team', False):
|
|
needs_update = True
|
|
if cache.get('has_issues', default_has_issues) != has_issues:
|
|
needs_update = True
|
|
if cache.get('has_downloads', default_has_downloads) != has_downloads:
|
|
needs_update = True
|
|
if cache.get('has_wiki', default_has_wiki) != has_wiki:
|
|
needs_update = True
|
|
if not needs_update:
|
|
return False
|
|
|
|
secure_config = configparser.ConfigParser()
|
|
secure_config.read(github_secure_config)
|
|
|
|
global orgs
|
|
if orgs is None:
|
|
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"))
|
|
|
|
log.info('Fetching github org list')
|
|
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 False
|
|
|
|
try:
|
|
log.info("Fetching github info about %s", repo_name)
|
|
repo = org.get_repo(repo_name)
|
|
|
|
except github.GithubException:
|
|
log.info("Creating %s in github", repo_name)
|
|
repo = org.create_repo(repo_name,
|
|
homepage=homepage,
|
|
has_issues=has_issues,
|
|
has_downloads=has_downloads,
|
|
has_wiki=has_wiki)
|
|
created = True
|
|
|
|
cache['has_wiki'] = has_wiki
|
|
cache['has_downloads'] = has_downloads
|
|
cache['has_issues'] = has_issues
|
|
|
|
kwargs = {}
|
|
# If necessary, update project on Github
|
|
if description and description != repo.description:
|
|
kwargs['description'] = description
|
|
if homepage and homepage != repo.homepage:
|
|
kwargs['homepage'] = homepage
|
|
if has_issues != repo.has_issues:
|
|
kwargs['has_issues'] = has_issues
|
|
if has_downloads != repo.has_downloads:
|
|
kwargs['has_downloads'] = has_downloads
|
|
if has_wiki != repo.has_wiki:
|
|
kwargs['has_wiki'] = has_wiki
|
|
|
|
if kwargs:
|
|
log.info("Updating github repo info about %s", repo_name)
|
|
repo.edit(repo_name, **kwargs)
|
|
cache.update(kwargs)
|
|
|
|
if not cache.get('gerrit-in-team', False):
|
|
if 'gerrit' not in [team.name for team in repo.get_teams()]:
|
|
log.info("Adding gerrit to github team for %s", repo_name)
|
|
teams = org.get_teams()
|
|
teams_dict = dict(zip([t.name.lower() for t in teams], teams))
|
|
teams_dict['gerrit'].add_to_repos(repo)
|
|
cache['gerrit-in-team'] = True
|
|
created = True
|
|
|
|
return created
|
|
|
|
|
|
# TODO(mordred): Inspect repo_dir:default-branch for a description
|
|
# override
|
|
def find_description_override(repo_path):
|
|
return None
|
|
|
|
|
|
def push_to_gerrit(repo_path, project, push_string, remote_url, ssh_env):
|
|
try:
|
|
u.git_command(repo_path, push_string % remote_url, env=ssh_env)
|
|
u.git_command(repo_path, "push --tags %s" % remote_url, env=ssh_env)
|
|
except Exception:
|
|
log.exception(
|
|
"Error pushing %s to Gerrit." % project)
|
|
raise PushToGerritException()
|
|
|
|
|
|
def process_acls(acl_config, project, ACL_DIR, section,
|
|
remote_url, repo_path, ssh_env, gerrit, GERRIT_GITID):
|
|
if not os.path.isfile(acl_config):
|
|
return
|
|
try:
|
|
fetch_config(project, remote_url, repo_path, ssh_env)
|
|
if not copy_acl_config(project, repo_path, acl_config):
|
|
# nothing was copied, so we're done
|
|
return
|
|
create_groups_file(project, gerrit, repo_path)
|
|
rc = push_acl_config(project, remote_url, repo_path,
|
|
GERRIT_GITID, ssh_env)
|
|
if not rc:
|
|
raise Exception("Non zero acl push return value.")
|
|
except Exception:
|
|
log.exception(
|
|
"Exception processing ACLS for %s." % project)
|
|
raise ProcessACLException()
|
|
finally:
|
|
u.git_command(repo_path, 'reset --hard')
|
|
default_branch = section.get('default-branch', 'master')
|
|
u.git_command(repo_path, 'checkout %s' % default_branch)
|
|
u.git_command(repo_path, 'branch -D config')
|
|
|
|
|
|
def create_gerrit_project(project, default_branch, project_list, gerrit):
|
|
if project not in project_list:
|
|
try:
|
|
gerrit.createProject(project, branches=[default_branch])
|
|
return True
|
|
except Exception:
|
|
log.exception(
|
|
"Exception creating %s in Gerrit." % project)
|
|
raise
|
|
return False
|
|
|
|
|
|
def create_local_mirror(local_git_dir, project_git,
|
|
gerrit_system_user, gerrit_system_group):
|
|
git_mirror_path = os.path.join(local_git_dir, project_git)
|
|
if not os.path.exists(git_mirror_path):
|
|
(ret, output) = u.run_command_status(
|
|
"git --bare init %s" % git_mirror_path)
|
|
if ret:
|
|
u.run_command("rm -rf git_mirror_path")
|
|
raise Exception(output)
|
|
u.run_command(
|
|
"chown -R %s:%s %s" % (
|
|
gerrit_system_user, gerrit_system_group, git_mirror_path))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Manage projects')
|
|
jeepyb.log.setup_logging_arguments(parser)
|
|
parser.add_argument('--nocleanup', action='store_true',
|
|
help='do not remove temp directories')
|
|
parser.add_argument('projects', metavar='project', nargs='*',
|
|
help='name of project(s) to process')
|
|
args = parser.parse_args()
|
|
jeepyb.log.configure_logging(args)
|
|
|
|
default_has_github = registry.get_defaults('has-github', True)
|
|
|
|
LOCAL_GIT_DIR = registry.get_defaults('local-git-dir', None)
|
|
JEEPYB_CACHE_DIR = registry.get_defaults('jeepyb-cache-dir',
|
|
'/var/lib/jeepyb')
|
|
ACL_DIR = registry.get_defaults('acl-dir')
|
|
GERRIT_HOST = registry.get_defaults('gerrit-host')
|
|
GITREVIEW_GERRIT_HOST = registry.get_defaults(
|
|
'gitreview-gerrit-host', GERRIT_HOST)
|
|
GERRIT_PORT = int(registry.get_defaults('gerrit-port', '29418'))
|
|
GITREVIEW_GERRIT_PORT = int(registry.get_defaults(
|
|
'gitreview-gerrit-port', GERRIT_PORT))
|
|
GERRIT_USER = registry.get_defaults('gerrit-user')
|
|
GERRIT_KEY = registry.get_defaults('gerrit-key')
|
|
GERRIT_GITID = registry.get_defaults('gerrit-committer')
|
|
GERRIT_REPLICATE = registry.get_defaults('gerrit-replicate', True)
|
|
GERRIT_OS_SYSTEM_USER = registry.get_defaults('gerrit-system-user',
|
|
'gerrit2')
|
|
GERRIT_OS_SYSTEM_GROUP = registry.get_defaults('gerrit-system-group',
|
|
'gerrit2')
|
|
DEFAULT_HOMEPAGE = registry.get_defaults('homepage')
|
|
DEFAULT_HAS_ISSUES = registry.get_defaults('has-issues', False)
|
|
DEFAULT_HAS_DOWNLOADS = registry.get_defaults('has-downloads', False)
|
|
DEFAULT_HAS_WIKI = registry.get_defaults('has-wiki', False)
|
|
GITHUB_SECURE_CONFIG = registry.get_defaults(
|
|
'github-config',
|
|
'/etc/github/github-projects.secure.config')
|
|
PROJECT_CACHE_FILE = os.path.join(JEEPYB_CACHE_DIR, 'project.cache')
|
|
project_cache = {}
|
|
if os.path.exists(PROJECT_CACHE_FILE):
|
|
project_cache = json.loads(open(PROJECT_CACHE_FILE, 'r').read())
|
|
acl_cache = {}
|
|
for acl_file in glob.glob(os.path.join(ACL_DIR, '*/*.config')):
|
|
sha256 = hashlib.sha256()
|
|
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 = list(gerrit.listProjects())
|
|
ssh_env = u.make_ssh_wrapper(GERRIT_USER, GERRIT_KEY)
|
|
try:
|
|
# Collect processed errors,if any
|
|
process_errors = []
|
|
for section in registry.all_configs_list:
|
|
project = section['project']
|
|
if args.projects and project not in args.projects:
|
|
continue
|
|
|
|
try:
|
|
log.info("Processing project: %s" % project)
|
|
|
|
# Figure out all of the options
|
|
options = section.get('options', dict())
|
|
description = section.get('description', None)
|
|
homepage = section.get('homepage', DEFAULT_HOMEPAGE)
|
|
upstream = section.get('upstream', None)
|
|
default_branch = section.get('default-branch', 'master')
|
|
repo_path = os.path.join(JEEPYB_CACHE_DIR, project)
|
|
|
|
# If this project doesn't want to use gerrit, exit cleanly.
|
|
if 'no-gerrit' in options:
|
|
continue
|
|
|
|
project_git = "%s.git" % project
|
|
remote_url = "ssh://%s:%s/%s" % (
|
|
GERRIT_HOST,
|
|
GERRIT_PORT,
|
|
project)
|
|
git_opts = dict(upstream=upstream,
|
|
repo_path=repo_path,
|
|
remote_url=remote_url)
|
|
acl_config = section.get(
|
|
'acl-config',
|
|
'%s.config' % os.path.join(ACL_DIR, project))
|
|
project_cache.setdefault(project, {})
|
|
|
|
# Create the project in Gerrit first, since it will fail
|
|
# spectacularly if its project directory or local replica
|
|
# already exist on disk
|
|
project_created = project_cache[project].get(
|
|
'project-created', False)
|
|
if not project_created:
|
|
try:
|
|
project_created = create_gerrit_project(
|
|
project, default_branch, project_list, gerrit)
|
|
project_cache[project]['project-created'] = True
|
|
except Exception:
|
|
project_cache[project]['project-created'] = False
|
|
continue
|
|
|
|
pushed_to_gerrit = project_cache[project].get(
|
|
'pushed-to-gerrit', False)
|
|
if not pushed_to_gerrit:
|
|
# We haven't pushed to gerrit, so grab the repo again
|
|
if os.path.exists(repo_path):
|
|
shutil.rmtree(repo_path)
|
|
|
|
# Make Local repo
|
|
push_string = u.make_local_copy(
|
|
repo_path, project, default_branch, project_list,
|
|
git_opts, ssh_env, upstream, GITREVIEW_GERRIT_HOST,
|
|
GITREVIEW_GERRIT_PORT, project_git, GERRIT_GITID)
|
|
|
|
description = (
|
|
find_description_override(repo_path)
|
|
or description)
|
|
|
|
u.fsck_repo(repo_path)
|
|
|
|
if push_string:
|
|
push_to_gerrit(
|
|
repo_path, project, push_string,
|
|
remote_url, ssh_env)
|
|
project_cache[project]['pushed-to-gerrit'] = True
|
|
if GERRIT_REPLICATE:
|
|
gerrit.replicate(project)
|
|
|
|
# Create the repo for the local git mirror
|
|
if LOCAL_GIT_DIR:
|
|
# This is conditional because new gerrit url pathing
|
|
# has made local git mirrors less straightfoward.
|
|
create_local_mirror(
|
|
LOCAL_GIT_DIR, project_git,
|
|
GERRIT_OS_SYSTEM_USER, GERRIT_OS_SYSTEM_GROUP)
|
|
|
|
if acl_config:
|
|
acl_sha = acl_cache.get(acl_config)
|
|
if project_cache[project].get('acl-sha') != acl_sha:
|
|
|
|
if not os.path.exists(repo_path):
|
|
u.make_local_copy(
|
|
repo_path, project, default_branch,
|
|
project_list, git_opts, ssh_env, upstream,
|
|
GERRIT_HOST, GERRIT_PORT, project_git,
|
|
GERRIT_GITID)
|
|
process_acls(
|
|
acl_config, project, ACL_DIR, section,
|
|
remote_url, repo_path, ssh_env, gerrit,
|
|
GERRIT_GITID)
|
|
project_cache[project]['acl-sha'] = acl_sha
|
|
else:
|
|
log.info("%s has matching sha, skipping ACLs",
|
|
project)
|
|
|
|
if 'has-github' in options or default_has_github:
|
|
created = create_update_github_project(
|
|
DEFAULT_HAS_ISSUES, DEFAULT_HAS_DOWNLOADS,
|
|
DEFAULT_HAS_WIKI, GITHUB_SECURE_CONFIG,
|
|
options, project, description, homepage,
|
|
project_cache[project])
|
|
if created and GERRIT_REPLICATE:
|
|
gerrit.replicate(project)
|
|
project_cache[project]['created-in-github'] = created
|
|
|
|
except Exception:
|
|
msg = "Problems creating %s, moving on." % project
|
|
log.exception(msg)
|
|
process_errors.append(msg)
|
|
continue
|
|
finally:
|
|
# Clean up after ourselves - this repo has no use
|
|
if os.path.exists(repo_path):
|
|
shutil.rmtree(repo_path)
|
|
finally:
|
|
with open(PROJECT_CACHE_FILE, 'w') as cache_out:
|
|
log.info("Writing cache file %s", PROJECT_CACHE_FILE)
|
|
cache_out.write(json.dumps(
|
|
project_cache, sort_keys=True, indent=2))
|
|
os.unlink(ssh_env['GIT_SSH'])
|
|
if len(process_errors) > 0:
|
|
log.error("%d problems has been caught during run:\n %s" % (
|
|
len(process_errors), process_errors))
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|