Skip to content

Commit eb0bf36

Browse files
committed
Add runbook docker driver
Docker is now the default driver, subprocess is used to communicate with the docker itself. runbook_runner image is used as a base image for all the runs and each run spawns a new image, that is later removed.
1 parent ed362ec commit eb0bf36

4 files changed

Lines changed: 138 additions & 12 deletions

File tree

runbook/drivers/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Driver(object):
1818

1919
interpreters = {
2020
"bash": "/bin/bash",
21-
"python": "/bin/python",
21+
"python": "/usr/bin/python",
2222
}
2323

2424
@classmethod

runbook/drivers/docker.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Copyright 2016: Mirantis Inc.
2+
# All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
# not use this file except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
16+
import logging
17+
import os
18+
import shutil
19+
import subprocess
20+
import tempfile
21+
import uuid
22+
23+
from runbook.drivers import base
24+
25+
LOG = logging.getLogger("runner.docker")
26+
27+
28+
RUNNER_DOCKERFILE = """
29+
FROM ubuntu:14.04
30+
RUN apt-get update
31+
RUN apt-get install python -y
32+
"""
33+
34+
RUN_DOCKERFILE = """
35+
FROM runbook_runner
36+
COPY . /runbook
37+
WORKDIR /runbook
38+
ENTRYPOINT ["{interpreter}"]
39+
CMD ["runbook"]
40+
"""
41+
42+
43+
class Driver(base.Driver):
44+
45+
@classmethod
46+
def initialise(cls):
47+
dirname = tempfile.mkdtemp()
48+
with open(os.path.join(dirname, 'Dockerfile'), "w") as f:
49+
f.write(RUNNER_DOCKERFILE)
50+
output = subprocess.check_output(
51+
["docker", "build", "-t", "runbook_runner", dirname],
52+
stderr=subprocess.STDOUT,)
53+
LOG.info("Built 'runbook_runner' image, {}".format(output))
54+
55+
shutil.rmtree(dirname)
56+
57+
@classmethod
58+
def run(cls, runbook, parameters):
59+
LOG.info("Running runbook '{}' with parameters '{}'".format(
60+
runbook, parameters))
61+
62+
interpreter = cls.interpreters.get(runbook.get("type"))
63+
if not interpreter:
64+
return {
65+
"return_code": -1,
66+
"output": "Don't know how to run '{}' type runbook".format(
67+
runbook.get("type")),
68+
}
69+
70+
dirname = tempfile.mkdtemp()
71+
72+
with open(os.path.join(dirname, 'runbook'), "w") as f:
73+
f.write(runbook["runbook"])
74+
75+
with open(os.path.join(dirname, 'Dockerfile'), "w") as f:
76+
f.write(RUN_DOCKERFILE.format(
77+
interpreter=interpreter))
78+
79+
run_name = "run-{}".format(uuid.uuid4().hex)
80+
output = subprocess.check_output(
81+
["docker", "build", "-t", run_name, dirname],
82+
stderr=subprocess.STDOUT,)
83+
LOG.info("Built 'runbook_runner' image, {}".format(output))
84+
85+
returncode = 0
86+
try:
87+
output = subprocess.check_output(
88+
["docker", "run", "--name", run_name, run_name],
89+
stderr=subprocess.STDOUT,
90+
)
91+
except subprocess.CalledProcessError as e:
92+
output = e.output
93+
returncode = e.returncode
94+
95+
# cleanup
96+
try:
97+
subprocess.check_output(
98+
["docker", "rm", run_name],
99+
stderr=subprocess.STDOUT,
100+
)
101+
LOG.info("Removed container {}".format(run_name))
102+
except subprocess.CalledProcessError as e:
103+
LOG.warning("Could not remove container {}".format(
104+
run_name, e.output))
105+
106+
try:
107+
subprocess.check_output(
108+
["docker", "rmi", run_name],
109+
stderr=subprocess.STDOUT,
110+
)
111+
LOG.info("Removed image {}".format(run_name))
112+
except subprocess.CalledProcessError as e:
113+
LOG.warning("Could not remove image {}: {}".format(
114+
run_name, e.output))
115+
116+
shutil.rmtree(dirname, ignore_errors=True)
117+
return {
118+
"return_code": returncode,
119+
"output": output,
120+
}

runbook/drivers/shell.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
from runbook.drivers import base
2323

24+
LOG = logging.getLogger("runner.shell")
25+
2426

2527
class Driver(base.Driver):
2628

@@ -30,7 +32,7 @@ def initialise(cls):
3032

3133
@classmethod
3234
def run(cls, runbook, parameters):
33-
logging.info("Running runbook '{}' with parameters '{}'".format(
35+
LOG.info("Running runbook '{}' with parameters '{}'".format(
3436
runbook, parameters))
3537
f = tempfile.NamedTemporaryFile()
3638
f.write(runbook["runbook"])

runbook/runner.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
API_TYPE = "runner"
3333
CONF = config.get_config(API_TYPE)
3434
POOL = eventlet.GreenPool()
35+
DRIVER = None
3536

3637
LOG = logging.getLogger("runner")
3738
LOG.setLevel(logging.INFO)
@@ -97,7 +98,6 @@ def handle_hit(hit, index_name, driver):
9798
LOG.info("Finished run '{}': '{}'".format(hit['_id'], run_result))
9899

99100
end_status = "finished" if run_result.get("return_code") == 0 else "failed"
100-
# end_status = "scheduled" # FIXME
101101

102102
now = datetime.datetime.now()
103103
try:
@@ -122,15 +122,7 @@ def handle_hit(hit, index_name, driver):
122122

123123

124124
def job():
125-
driver_name = CONF.get("driver", "shell")
126-
try:
127-
driver_module = importlib.import_module(
128-
"runbook.drivers." + driver_name)
129-
except ImportError:
130-
LOG.critical("No driver named '{}'".format(driver_name))
131-
return
132-
133-
driver = driver_module.Driver
125+
driver = DRIVER
134126

135127
es = storage.get_elasticsearch(API_TYPE)
136128
for region in CONF["regions"]:
@@ -146,6 +138,18 @@ def job():
146138

147139

148140
def main():
141+
driver_name = CONF.get("driver", "docker")
142+
try:
143+
driver_module = importlib.import_module(
144+
"runbook.drivers." + driver_name)
145+
except ImportError:
146+
LOG.critical("No driver named '{}'".format(driver_name))
147+
return
148+
149+
driver = driver_module.Driver
150+
driver.initialise()
151+
global DRIVER
152+
DRIVER = driver
149153

150154
run_every_seconds = CONF.get("run_every_seconds", 30)
151155
schedule.every(run_every_seconds).seconds.do(job)

0 commit comments

Comments
 (0)