Skip to content

Commit 2d128fc

Browse files
author
Doug Greiman
committed
Revert "Improve tool to run cloudbuild.yaml steps locally."
This reverts commit 6dc37bd.
1 parent 6dc37bd commit 2d128fc

8 files changed

Lines changed: 164 additions & 689 deletions

.gitignore

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
/cloudbuild.yaml
2-
/cloudbuild.yaml_local.sh
3-
/ext_run.sh
4-
__pycache__
1+
cloudbuild.yaml
2+
ext_run.sh

local-cloudbuild.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright 2016 Google Inc. All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
"""Emulate the Google Container Builder locally.
18+
19+
The input is a cloudbuild.yaml file locally, which is processed using
20+
a locally installed Docker daemon. The output images are not pushed
21+
to the Google Container Registry. Not all functionality is supported.
22+
23+
See https://cloud.google.com/container-builder/docs/api/build-steps
24+
for more information.
25+
"""
26+
27+
import argparse
28+
import getpass
29+
import os
30+
import shutil
31+
import subprocess
32+
import sys
33+
import tempfile
34+
35+
import yaml
36+
37+
38+
class CloudBuildError(Exception):
39+
"""Syntax error in cloudbuild.yaml or other user error"""
40+
pass
41+
42+
43+
def run_steps(cloudbuild, host_workspace):
44+
"""Run the steps listed in a cloudbuild.yaml file.
45+
46+
Args:
47+
cloudbuild (dict): The decoded contents of a cloudbuild.yaml
48+
host_workspace (str): Scratch directory
49+
50+
Raises:
51+
CloudBuildError if the yaml contents are invalid
52+
"""
53+
steps = cloudbuild.get_field_value('steps', {})
54+
if not steps:
55+
raise CloudBuildError('No steps defined in cloudbuild.yaml')
56+
57+
for step in steps:
58+
run_one_step(step, host_workspace)
59+
60+
61+
def run_one_step(step, host_workspace):
62+
"""Run a single step listed in a cloudbuild.yaml file.
63+
64+
Args:
65+
step (dict): A single step to perform
66+
host_workspace (str): Scratch directory
67+
"""
68+
name = get_field_value(step, 'name', str)
69+
dir_ = get_field_value(step, 'dir', list)
70+
env = get_field_value(step, 'env', list)
71+
args = get_field_value(step, 'args', list)
72+
run_docker(name, dir_, env, args, host_workspace)
73+
74+
75+
def get_field_value(container, field_name, field_type):
76+
"""Fetch a field from a container with typechecking and default values.
77+
78+
If the field is not present, a instance of `field_type` is
79+
constructed with no arguments and used as the default value.
80+
81+
Args:
82+
container (dict): Object decoded from yaml
83+
field_name (str): Field that should be present in `container`
84+
field_type (type): Expected type for field value
85+
86+
Returns:
87+
(any) fetched or default value of field
88+
89+
Raises:
90+
CloudBuildError if field value is present but is the wrong type.
91+
"""
92+
value = container.get(field_name)
93+
if value is None:
94+
return field_type()
95+
if not isinstance(value, field_type):
96+
raise CloudBuildError(
97+
'Expected "%d" to be of type "%d", but found "%d"',
98+
field_name, field_type, type(value))
99+
return value
100+
101+
102+
def run_docker(name, dir_, env_args, args, host_workspace):
103+
"""Construct and execute a single 'docker run' command"""
104+
workdir = '/workspace'
105+
if dir_:
106+
workdir = os.path.join(workdir, dir_)
107+
108+
env_pairs = []
109+
for env_arg in env_args:
110+
env_pairs.append('--env', env_arg)
111+
112+
process_args = [
113+
'docker',
114+
'run',
115+
'--volume',
116+
'/var/run/docker.sock:/var/run/docker.sock',
117+
'--volume',
118+
'/root/.docker:/root/.docker',
119+
'--volume',
120+
'%s:/workspace' % host_workspace,
121+
'--workdir',
122+
workdir,
123+
] + env_args + [name] + args
124+
125+
print('Executing ' + ' '.join(process_args))
126+
subprocess.check_call(process_args)
127+
128+
129+
def main(argv):
130+
"""Main entrypoint for cli"""
131+
parser = argparse.ArgumentParser(
132+
description='Process cloudbuild.yaml locally to build Docker images')
133+
parser.add_argument(
134+
'cloudbuild', type=str, help='Path to cloudbuild.yaml input file')
135+
parser.add_argument(
136+
'--keep_workspace',
137+
type=bool,
138+
default=False,
139+
help='Retain workspace directory after building')
140+
args = parser.parse_args(argv[1:])
141+
142+
# Load and parse cloudbuild.yaml
143+
with open(args.cloudbuild, 'rb') as infile:
144+
cloudbuild = yaml.safe_load(infile)
145+
146+
host_workspace_parent = tempfile.mkdtemp(prefix='local-cloudbuild_')
147+
host_workspace = os.path.join(host_workspace_parent, 'workspace')
148+
try:
149+
# Prepare workspace
150+
print('Running cloudbuild locally. Host workspace directory is %s' %
151+
host_workspace)
152+
shutil.copytree('.', host_workspace, symlinks=True)
153+
154+
# Execute a series of 'docker run' commands locally
155+
run_steps(cloudbuild, host_workspace)
156+
finally:
157+
if not args.keep_workspace:
158+
shutil.rmtree(host_workspace_parent, ignore_errors=True)
159+
160+
161+
if __name__ == '__main__':
162+
sys.exit(main(sys.argv))

0 commit comments

Comments
 (0)