Created
April 25, 2019 02:24
-
-
Save rgov/9c9c75931958b207f4a09d2e99827f20 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# Copyright (C) 2019 Woods Hole Oceanographic Institution | |
# | |
# This file is part of the CGSN Mooring Project ("cgsn-mooring"). | |
# | |
# cgsn-mooring is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 2 of the License, or | |
# (at your option) any later version. | |
# | |
# cgsn-mooring is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with cgsn-mooring in the COPYING.md file at the project root. | |
# If not, see <http://www.gnu.org/licenses/>. | |
''' | |
This tool can be used to execute a command or start an interactive session in an | |
environment similar to that used by continuous integration builds. | |
Without a command, an interactive shell is started: | |
scripts/dockerize.py --job amd64-bionic-build | |
With a command, the command is executed in the environment: | |
scripts/dockerize.py --job amd64-bionic-build uname -a | |
To perform a full build in a local CircleCI-like environment, instead use: | |
circleci local execute --job amd64-bionic-build | |
''' | |
from __future__ import print_function | |
import argparse | |
import os | |
import pipes | |
import subprocess | |
import sys | |
try: | |
import yaml | |
except ImportError: | |
print('Error: Please install PyYAML', file=sys.stderr) | |
print(' pip install pyyaml', file=sys.stderr) | |
print(' apt-get install python-yaml', file=sys.stderr) | |
raise | |
# Parse command-line arguments | |
parser = argparse.ArgumentParser(description=__doc__, | |
formatter_class=argparse.RawDescriptionHelpFormatter) | |
parser.add_argument('--show-jobs', action='store_true', | |
help='list out jobs defined in the CircleCI config') | |
parser.add_argument('--job', default='amd64-bionic-build', | |
help='the job environment to use from the CircleCI config') | |
parser.add_argument('--dry-run', action='store_true', | |
help='display commands to be run without executing them') | |
parser.add_argument('command', nargs=argparse.REMAINDER, | |
help='the command to execute in the container (default: /bin/bash)') | |
args = parser.parse_args() | |
if not args.command: # parser.add_argument(default=...) didn't work | |
args.command = [ '/bin/bash' ] | |
if len(args.command) > 0 and args.command[0] == '--': | |
del args.command[0] # drop the -- separator if it was captured | |
# Path calculations | |
repo_path = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], | |
cwd=os.path.dirname(__file__)).rstrip() | |
mapped_path = os.path.join('/root', os.path.basename(repo_path)) | |
config_path = os.path.join(repo_path, '.circleci', 'config.yml') | |
# Make sure prerequisites are installed | |
try: | |
subprocess.check_call(['circleci', 'version'], stdout=open(os.devnull, 'wb')) | |
except: | |
print('Error: Please install the CircleCI command line tool', file=sys.stderr) | |
print(' https://circleci.com/docs/2.0/local-cli', file=sys.stderr) | |
sys.exit(1) | |
try: | |
subprocess.check_call(['docker', 'version'], stdout=open(os.devnull, 'wb')) | |
except: | |
print('Error: Please install Docker', file=sys.stderr) | |
print(' https://docs.docker.com/install/', file=sys.stderr) | |
sys.exit(1) | |
# Canonicalize and read the configuration file | |
config_yaml = subprocess.check_output(['circleci', 'config', 'process', | |
'--skip-update-check', config_path]) | |
config = yaml.load(config_yaml) | |
# Display a list of jobs (--show-jobs) | |
if args.show_jobs: | |
if sys.stdout.isatty(): | |
print('The following jobs are configured in the CircleCI config:') | |
for job in config['jobs'].keys(): | |
print(' *', job) | |
else: | |
for job in config['jobs'].keys(): | |
print(job) | |
sys.exit(0) | |
# Awkwardly parse out settings we care about | |
try: | |
job = config['jobs'][args.job] | |
except: | |
print('Error: job', args.job, 'not found in CircleCI config', file=sys.stderr) | |
sys.exit(1) | |
image = next(x['image'] for x in job['docker'] | |
if isinstance(x, dict) and 'image' in x) | |
env_vars = { k: v for d in job.get('environment', []) for k, v in d.items() } | |
if 'working_directory' in job: | |
mapped_path = job['working_directory'] | |
# Parse (poorly) any environment variables out of the command as well | |
for i, arg in enumerate(args.command): | |
k, eq, v = arg.partition('=') | |
if eq == '': # first non-variable, does not contain = | |
del args.command[:i] | |
break | |
env_vars[k] = v | |
# Build the command | |
command = [ | |
'docker', 'run', | |
'--cap-add', 'SYS_PTRACE', # allow using GDB in the container | |
'--volume', repo_path + ':' + mapped_path, # map this repo into the container | |
'--workdir', mapped_path, # set current working directory | |
'--interactive', | |
'--tty', | |
] | |
for k, v in env_vars.items(): | |
command += ['--env', k + '=' + v] | |
command.append('--') | |
command.append(image) | |
command += args.command | |
# Run (or print) the command | |
if args.dry_run: | |
print(' '.join(pipes.quote(x) for x in command)) | |
else: | |
os.execvp(command[0], command) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment