Last active
June 28, 2018 02:36
-
-
Save ssummer3/c366c88de389aad1faaae318a3ba31bd to your computer and use it in GitHub Desktop.
Local CodeBuild
This file contains hidden or 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 | |
""" local codebuild implementation | |
Parses buildspec.yml files, and executes them locally, as per: | |
http://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec-ref-example | |
""" | |
from __future__ import print_function | |
import functools | |
import glob | |
import os | |
import subprocess | |
import sys | |
import tempfile | |
import yaml | |
# optimizations and setup | |
log = print | |
popen = functools.partial( | |
subprocess.Popen, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.STDOUT, | |
shell=True, | |
universal_newlines=True) | |
yaml_load = functools.partial( | |
yaml.load, Loader=getattr(yaml, 'CLoader', yaml.Loader)) | |
def parse_buildspec(buildspec_filepath): | |
""" parse buildspec.yml v0.2 | |
- buildspec_filepath: <buildspec.yml> with path (default "./buildspec.yml") | |
- returns: (env, phases, artifacts) | |
""" | |
with open(buildspec_filepath) as f: | |
buildspec = yaml_load(f) | |
assert float( | |
buildspec.pop('version')) == 0.2, "Unsupported buildspec version" | |
env = buildspec.pop('env', {}).pop('variables', {}) | |
phases = buildspec.pop('phases', {}) | |
artifacts = buildspec.pop('artifacts', {}) | |
if buildspec: | |
log("Unsupported buildspec components: {}".format(buildspec)) | |
return env, phases, artifacts | |
def do_phases(phases_commands, env=None, phase_list=None): | |
""" execute phases_commands for each phase in phase_list (in order) | |
The environment and current working directory carry over between phases. | |
- phases_commands: {phase: [commands]} | |
- env: {key:value} for environment (default os.environ) | |
- phase_list: [phases] in run order (default ['install', 'pre_build', 'build', 'post_build']) | |
- returns: None | |
""" | |
env = env or os.environ.copy() | |
phase_list = phase_list or ('install', 'pre_build', 'build', 'post_build') | |
cwd = os.getcwd() | |
os.chdir(os.getenv('CODEBUILD_SRC_DIR', os.curdir)) | |
for current_phase in phase_list: | |
log("phase: {}".format(current_phase)) | |
if current_phase in phases_commands: | |
commands = phases_commands.pop(current_phase).pop('commands') | |
assert isinstance(commands, list), "Phase commands must be a list" | |
for command in commands: | |
log("{}: {}".format(current_phase, command)) | |
p = popen(command, env=env) | |
stdout, _ = p.communicate() | |
log('\n'.join((stdout, ''))) | |
if p.return_code: | |
sys.exit(return_code) | |
os.chdir(cwd) | |
def do_artifacts(artifacts, target_directory=None): | |
""" parse artifacts and move them to a designated target_directory | |
target_directory: "<path>" (default "$CODEBUILD_SRC_DIR}/build" | |
TODO: the glob parsing is non-trivial and currently incorrect | |
""" | |
discard_paths = artifacts.pop('discard-paths', False) | |
base_directory = artifacts.pop('base-directory', '.') | |
files = artifacts.pop('files') | |
assert isinstance(files, list), "Artifact files must be a list" | |
if artifacts: | |
log("Unsupported artifact components: {}".format(artifacts)) | |
cwd = os.getcwd() | |
os.chdir(os.getenv('CODEBUILD_SRC_DIR', os.curdir)) | |
target_directory = target_directory or tempfile.mkdtemp( | |
prefix='build', dir=os.getcwd()) | |
for file_glob in files: | |
for file_name in glob.glob(file_glob): | |
log("artifact: {}".format(file_name)) | |
shutil.copy2(file_name, target_directory) | |
log("Artifact output: {}".format(target_directory)) | |
os.chdir(cwd) | |
def main(buildspec_file='buildspec.yml'): | |
codebuild_src_dir = os.getenv('CODEBUILD_SRC_DIR', os.getcwd()) | |
if os.path.sep not in os.path.normpath(buildspec_file): | |
buildspec_file = os.path.sep.join((codebuild_src_dir, buildspec_file)) | |
assert os.path.isfile(buildspec_file), "{} not found".format( | |
buildspec_file) | |
env, phases, artifacts = parse_buildspec(buildspec_file) | |
# override default environment | |
codebuild_env = os.environ.copy() | |
codebuild_env.update(env) | |
do_phases(phases, codebuild_env) | |
do_artifacts(artifacts) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment