zuul-jobs/roles/fetch-zuul-cloner/templates/zuul-cloner-shim.py.j2
Ian Wienand e46b67a1d9 zuul-cloner-shim: Use st_dev to check for filesystem
Rather than check mounts, check if the source and destination reside
on the same st_dev; if so use hardlinks, otherwise copy.

Change-Id: Ic5fdc899d70c67ddcfc19994c254a8efcd0fd3d6
2017-10-11 10:59:40 +11:00

194 lines
6.6 KiB
Django/Jinja

#!{{ destination | dirname }}/python
# Copyright 2017 Red Hat
#
# 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 argparse
import os
import re
import sys
import textwrap
import yaml
from collections import defaultdict
from collections import OrderedDict
REPO_SRC_DIR = "{{ repo_src_dir }}"
# Class copied from zuul/lib/conemapper.py with minor logging changes
class CloneMapper(object):
def __init__(self, clonemap, projects):
self.clonemap = clonemap
self.projects = projects
def expand(self, workspace):
print("Workspace path set to: %s" % workspace)
is_valid = True
ret = OrderedDict()
errors = []
for project in self.projects:
dests = []
for mapping in self.clonemap:
if re.match(r'^%s$' % mapping['name'], project):
# Might be matched more than one time
dests.append(
re.sub(mapping['name'], mapping['dest'], project))
if len(dests) > 1:
errors.append(
"Duplicate destinations for %s: %s." % (project, dests))
is_valid = False
elif len(dests) == 0:
print("Using %s as destination (unmatched)" % project)
ret[project] = [project]
else:
ret[project] = dests
if not is_valid:
raise Exception("Expansion errors: %s" % errors)
print("Mapping projects to workspace...")
for project, dest in ret.items():
dest = os.path.normpath(os.path.join(workspace, dest[0]))
ret[project] = dest
print(" %s -> %s" % (project, dest))
print("Checking overlap in destination directories...")
check = defaultdict(list)
for project, dest in ret.items():
check[dest].append(project)
dupes = dict((d, p) for (d, p) in check.items() if len(p) > 1)
if dupes:
raise Exception("Some projects share the same destination: %s",
dupes)
print("Expansion completed.")
return ret
def parseArgs():
ZUUL_ENV_SUFFIXES = ('branch', 'ref', 'url', 'project', 'newrev')
parser = argparse.ArgumentParser()
# Ignored arguments
parser.add_argument('-v', '--verbose', dest='verbose',
action='store_true', help='IGNORED')
parser.add_argument('--color', dest='color', action='store_true',
help='IGNORED')
parser.add_argument('--cache-dir', dest='cache_dir', help='IGNORED')
parser.add_argument('git_base_url', help='IGNORED')
parser.add_argument('--branch', help='IGNORED')
parser.add_argument('--project-branch', nargs=1, action='append',
metavar='PROJECT=BRANCH', help='IGNORED')
for zuul_suffix in ZUUL_ENV_SUFFIXES:
env_name = 'ZUUL_%s' % zuul_suffix.upper()
parser.add_argument(
'--zuul-%s' % zuul_suffix, metavar='$' + env_name,
help='IGNORED'
)
# Active arguments
parser.add_argument('-m', '--map', dest='clone_map_file',
help='specify clone map file')
parser.add_argument('--workspace', dest='workspace',
default=os.getcwd(),
help='where to clone repositories too')
parser.add_argument('projects', nargs='+',
help='list of Gerrit projects to clone')
return parser.parse_args()
def readCloneMap(clone_map):
clone_map_file = os.path.expanduser(clone_map)
if not os.path.exists(clone_map_file):
raise Exception("Unable to read clone map file at %s." %
clone_map_file)
clone_map_file = open(clone_map_file)
clone_map = yaml.safe_load(clone_map_file).get('clonemap')
return clone_map
def main():
args = parseArgs()
clone_map = []
if args.clone_map_file:
clone_map = readCloneMap(args.clone_map_file)
mapper = CloneMapper(clone_map, args.projects)
dests = mapper.expand(workspace=args.workspace)
for project in args.projects:
src = os.path.join(os.path.expanduser(REPO_SRC_DIR), project)
if not os.path.exists(src):
print(textwrap.dedent(
"""
**********************************
ERROR! {src} not found
In Zuul v3 all repositories used need to be declared
in the 'required-projects' parameter on the job.
To fix this issue, add:
{project}
to 'required-projects'.
While you're at it, it's worth noting that zuul-cloner itself
is deprecated and this shim is only present for transition
purposes. Start thinking about how to rework job content to
just use the git repos that zuul will place into
{repo_src_dir} directly.
**********************************
""".format(
src=src, project=project, repo_src_dir=REPO_SRC_DIR)))
sys.exit(1)
dst = dests[project]
# Remove the tail end of the path (since the copy operation will
# automatically create that)
d = dst.rstrip('/')
if not os.path.exists(d):
print("Creating %s" % d)
os.makedirs(d)
#
# Create (possible hard link) copy of the source directory
#
# Check if src & dst are on the same filesystem; if so we will
# hardlink, otherwise we copy
src_dev = os.stat(src).st_dev
dst_dev = os.stat(dst).st_dev
link = "l" if src_dev == dst_dev else ""
# note: don't use "-a" here as that implies "--preserve=all"
# which overwrites the permissions of dst from src ... this is
# fatal to ssh if dst is a home directory and we make it
# world-accessable. This should leave dst alone
cmd = "cp -dR%s %s/. %s" % (link, src, dst)
print("%s" % cmd)
if os.system(cmd):
print("Error executing: %s" % cmd)
sys.exit(1)
if __name__ == "__main__":
main()