Skip to content

Commit 2507037

Browse files
patch major bugs with overriding cmd
1 parent 020ac41 commit 2507037

File tree

9 files changed

+135
-50
lines changed

9 files changed

+135
-50
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# pycharm
44
.idea
55

6+
Makefile
7+
68
# Byte-compiled / optimized / DLL files
79
__pycache__/
810
*.py[cod]

Makefile

Lines changed: 0 additions & 20 deletions
This file was deleted.

docker_patch/__init__.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@
88
registered_patchers: typing.List[typing.Callable] = []
99

1010

11-
__version__ = '0.1.1'
11+
__version__ = '0.1.2'
1212

1313

14-
def default_on_error(e: Exception):
15-
raise e
14+
def default_on_error(error: Exception):
15+
raise error
16+
17+
18+
def _get(d: dict, key: str, default):
19+
res = d.get(key, default)
20+
return default if res is None else res
1621

1722

1823
def register_patcher(
@@ -39,44 +44,44 @@ def patch_image(
3944
on_patcher_error = default_on_error
4045

4146
orig_config = client.api.inspect_image(image.id)['Config']
42-
orig_entrypoint = orig_config['Entrypoint']
43-
if not orig_entrypoint:
44-
orig_entrypoint = ''
4547

4648
# create a container
47-
logger.info(f'Creating container for "{image}"')
48-
c: Container = client.containers.run(
49+
logger.info('Creating container for "%s"', image)
50+
container: Container = client.containers.run(
4951
image, detach=True, entrypoint='/bin/sh',
5052
remove=True, tty=True, network_mode='host'
5153
)
52-
logger.info(f'Container: "{c.name}" (id: {c.id})')
54+
logger.info('Container: "%s" (id: "%s")', container.name, container.id)
5355

5456
try:
5557
for patch_func in registered_patchers:
5658
try:
57-
logger.info(f'calling patcher func "{patch_func.__name__}"')
58-
patch_func(c)
59-
except Exception as e:
60-
on_patcher_error(e)
59+
logger.info('calling patcher func "%s"', patch_func.__name__)
60+
patch_func(container)
61+
except Exception as error:
62+
on_patcher_error(error)
6163

6264
# commit
6365
logger.info('Commit container')
64-
result_image = c.commit(conf={'Entrypoint': orig_entrypoint})
66+
result_image = container.commit(conf={
67+
'Entrypoint': _get(orig_config, 'Entrypoint', []),
68+
'Cmd': _get(orig_config, 'Cmd', [])
69+
})
6570

66-
logger.info(f'New image id: {result_image.id}')
71+
logger.info('New image id: "%s"', result_image.id)
6772

6873
# re-tag
69-
logger.info(f'tag new image with {len(image.tags)} tags')
74+
logger.info('tag new image with %d tags', len(image.tags))
7075
for tag in image.tags:
71-
logger.info(f'-tag "{tag}"')
76+
logger.info('-tag "%s"', tag)
7277
result_image.tag(tag)
7378

7479
# fetch image again with tags
7580
return client.images.get(result_image.id)
7681
finally:
7782
# fetch the updated container object
78-
c = client.containers.get(c.id)
79-
if c.status == 'running':
83+
container = client.containers.get(container.id)
84+
if container.status == 'running':
8085
# stop container (should be removed)
81-
logger.info(f'Stopping container "{c.name}"')
82-
c.stop()
86+
logger.info('Stopping container "%s"', container.name)
87+
container.stop()

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
def test(session):
66
session.install('flake8', 'tox')
77
session.run('flake8', '--show-source', '--statistics', 'docker_patch')
8-
session.run('tox')
8+
session.run('tox', '-s')
99

1010

1111
@nox.session(name='build')

tests/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +0,0 @@
1-
import docker
2-
3-
4-
def test_cli():
5-
client = docker.DockerClient()

tests/data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
images = ['alpine:3.12']
1+
images = ['alpine:3.12', 'ubuntu:20.04', 'debian:buster']
22
modules = ['module_1', 'module_2']
33
add_paths = ['example_patchers']

tests/test_cli.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import logging
2+
13
import docker
24
import click.testing
35

@@ -6,24 +8,44 @@
68

79

810
def test_cli():
11+
logger = logging.getLogger('test_cli')
12+
913
client = docker.DockerClient()
1014

1115
args = []
1216

17+
logger.info('Add %d images...', len(data.images))
1318
for image in data.images:
1419
args.append(image)
15-
client.images.pull(image)
20+
try:
21+
logger.info('Trying remove image first if exists')
22+
client.images.remove(image, force=True)
23+
logger.info('Image "%s" removed successfully', image)
24+
except docker.errors.ImageNotFound:
25+
logger.info('Image "%s" does not exists', image)
26+
27+
logger.info('Pulling "%s"', image)
28+
image = client.images.pull(image)
29+
logger.info('Pulled "%s" (id: "%s")', image, image.id)
1630

1731
for module in data.modules:
32+
logger.info('Add module "%s"', module)
1833
args += ['-m', module]
1934
for path in data.add_paths:
35+
logger.info('Add path "%s"', path)
2036
args += ['--add-path', path]
2137

38+
logger.info('Cli args: "%s"', args)
39+
2240
runner = click.testing.CliRunner()
2341
result = runner.invoke(docker_patch, args)
42+
logger.info('exit code: %d', result.exit_code)
2443
assert result.exit_code == 0
2544

2645
# test new image
46+
logger.info('Test %d images...', len(data.images))
2747
for image in data.images:
48+
logger.info('Test image "%s"', image)
2849
res = client.containers.run(image, 'cat patched.txt', remove=True)
50+
logger.info('Result: "%s"', res)
2951
assert res

tests/test_package.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import logging
2+
import uuid
3+
4+
import docker
5+
import docker.errors
6+
7+
import pytest
8+
9+
from docker_patch import patch_image, register_patcher
10+
from . import data
11+
12+
client = docker.DockerClient()
13+
test_solution = str(uuid.uuid4())
14+
15+
TEST_SOLUTION_FILEPATH = '/test_solution.txt'
16+
17+
logger = logging.getLogger(__name__)
18+
19+
logger.info('test solution: "%s"', test_solution)
20+
21+
22+
# register new patcher
23+
@register_patcher
24+
def patcher(container):
25+
logger.info('Injecting solution "%s" into container "%s"', test_solution, container)
26+
r = container.exec_run(f'/bin/sh -c \'echo "{test_solution}" >> {TEST_SOLUTION_FILEPATH}\'')
27+
if r.exit_code != 0:
28+
assert False
29+
30+
31+
def on_error(error):
32+
logger.error('patcher raised error: "%s"', str(error))
33+
assert False
34+
35+
36+
@pytest.mark.parametrize('image_name', data.images)
37+
def test_package(image_name: str):
38+
logger.info('Test image: "%s"', image_name)
39+
40+
try:
41+
logger.info('Trying remove image first if exists')
42+
client.images.remove(image_name, force=True)
43+
logger.info('Image removed successfully')
44+
except docker.errors.ImageNotFound:
45+
logger.info('Image does not exists')
46+
47+
logger.info('Pulling image "%s"', image_name)
48+
image = client.images.pull(image_name)
49+
logger.info('Pulling result: "%s"', image)
50+
assert image
51+
logger.info('Image ID: "%s"', image.id)
52+
53+
# store all images original configs
54+
original_configs = client.api.inspect_image(image.id)['Config']
55+
logger.info('Original image configs: "%s"', original_configs)
56+
57+
# patch image
58+
logger.info('Patching image...')
59+
new_image = patch_image(image, on_patcher_error=on_error)
60+
logger.info('New image id: "%s"', new_image.id)
61+
62+
# test new image
63+
# check original config equals to patched config
64+
current_configs = client.api.inspect_image(new_image.id)['Config']
65+
logger.info('New image configs: "%s"', current_configs)
66+
67+
def compare_config(key):
68+
orig_val = original_configs.get(key)
69+
curr_val = current_configs.get(key)
70+
71+
return orig_val == curr_val or (orig_val is None and curr_val in ['', []])
72+
73+
for key in ['Cmd', 'Entrypoint']:
74+
logger.info('Compare Config.%s', key)
75+
assert compare_config(key)
76+
77+
# check solution exists in image by spawn a new container
78+
logger.info('Check solution exists in new image')
79+
solution = client.containers.run(new_image, f'cat {TEST_SOLUTION_FILEPATH}',
80+
remove=True).decode('utf-8').strip()
81+
assert test_solution == solution

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ deps =
77
docker
88
-r requirements.txt
99
commands =
10-
pytest
10+
pytest -o log_cli_level=INFO

0 commit comments

Comments
 (0)