Skip to content

Instantly share code, notes, and snippets.

@rgov
Created April 25, 2019 02:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rgov/9c9c75931958b207f4a09d2e99827f20 to your computer and use it in GitHub Desktop.
Save rgov/9c9c75931958b207f4a09d2e99827f20 to your computer and use it in GitHub Desktop.
#!/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