Skip to content

Commit 9c25112

Browse files
authored
Merge pull request #579 from GladysNalvarte/expose_cpu
Exposes CPU limit
2 parents 9766c95 + 7609e7f commit 9c25112

File tree

6 files changed

+108
-36
lines changed

6 files changed

+108
-36
lines changed

repo2docker/app.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,28 @@ def _default_log_level(self):
9797
"""
9898
)
9999

100+
extra_build_kwargs = Dict(
101+
{},
102+
help="""
103+
extra kwargs to limit CPU quota when building a docker image.
104+
Dictionary that allows the user to set the desired runtime flag
105+
to configure the amount of access to CPU resources your container has.
106+
Reference https://docs.docker.com/config/containers/resource_constraints/#cpu
107+
""",
108+
config=True
109+
)
110+
111+
extra_run_kwargs = Dict(
112+
{},
113+
help="""
114+
extra kwargs to limit CPU quota when running a docker image.
115+
Dictionary that allows the user to set the desired runtime flag
116+
to configure the amount of access to CPU resources your container has.
117+
Reference https://docs.docker.com/config/containers/resource_constraints/#cpu
118+
""",
119+
config=True
120+
)
121+
100122
default_buildpack = Any(
101123
PythonBuildPack,
102124
config=True,
@@ -499,15 +521,19 @@ def start_container(self):
499521
'mode': 'rw'
500522
}
501523

502-
container = client.containers.run(
503-
self.output_image_spec,
524+
run_kwargs = dict(
504525
publish_all_ports=self.all_ports,
505526
ports=ports,
506527
detach=True,
507528
command=run_cmd,
508529
volumes=container_volumes,
509530
environment=self.environment
510531
)
532+
533+
run_kwargs.update(self.extra_run_kwargs)
534+
535+
container = client.containers.run(self.output_image_spec, **run_kwargs)
536+
511537
while container.status == 'created':
512538
time.sleep(0.5)
513539
container.reload()
@@ -636,15 +662,16 @@ def build(self):
636662
self.output_image_spec,
637663
self.build_memory_limit,
638664
build_args,
639-
self.cache_from):
665+
self.cache_from,
666+
self.extra_build_kwargs):
640667
if 'stream' in l:
641668
self.log.info(l['stream'],
642669
extra=dict(phase='building'))
643670
elif 'error' in l:
644671
self.log.info(l['error'], extra=dict(phase='failure'))
645672
raise docker.errors.BuildError(l['error'], build_log='')
646673
elif 'status' in l:
647-
self.log.info('Fetching base image...\r',
674+
self.log.info('Fetching base image...\r',
648675
extra=dict(phase='building'))
649676
else:
650677
self.log.info(json.dumps(l),

repo2docker/buildpacks/base.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import sys
1010
import xml.etree.ElementTree as ET
1111

12+
from traitlets import Dict
13+
1214
TEMPLATE = r"""
1315
FROM buildpack-deps:bionic
1416
@@ -463,7 +465,7 @@ def render(self):
463465
appendix=self.appendix,
464466
)
465467

466-
def build(self, client, image_spec, memory_limit, build_args, cache_from):
468+
def build(self, client, image_spec, memory_limit, build_args, cache_from, extra_build_kwargs):
467469
tarf = io.BytesIO()
468470
tar = tarfile.open(fileobj=tarf, mode='w')
469471
dockerfile_tarinfo = tarfile.TarInfo("Dockerfile")
@@ -503,17 +505,22 @@ def _filter_tar(tar):
503505
}
504506
if memory_limit:
505507
limits['memory'] = memory_limit
506-
for line in client.build(
507-
fileobj=tarf,
508-
tag=image_spec,
509-
custom_context=True,
510-
buildargs=build_args,
511-
decode=True,
512-
forcerm=True,
513-
rm=True,
514-
container_limits=limits,
515-
cache_from=cache_from
516-
):
508+
509+
build_kwargs = dict(
510+
fileobj=tarf,
511+
tag=image_spec,
512+
custom_context=True,
513+
buildargs=build_args,
514+
decode=True,
515+
forcerm=True,
516+
rm=True,
517+
container_limits=limits,
518+
cache_from=cache_from,
519+
)
520+
521+
build_kwargs.update(extra_build_kwargs)
522+
523+
for line in client.build(**build_kwargs):
517524
yield line
518525

519526

repo2docker/buildpacks/docker.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def render(self):
1919
with open(Dockerfile) as f:
2020
return f.read()
2121

22-
def build(self, client, image_spec, memory_limit, build_args, cache_from):
22+
def build(self, client, image_spec, memory_limit, build_args, cache_from, extra_build_kwargs):
2323
"""Build a Docker image based on the Dockerfile in the source repo."""
2424
limits = {
2525
# Always disable memory swap for building, since mostly
@@ -28,15 +28,20 @@ def build(self, client, image_spec, memory_limit, build_args, cache_from):
2828
}
2929
if memory_limit:
3030
limits['memory'] = memory_limit
31-
for line in client.build(
32-
path=os.getcwd(),
33-
dockerfile=self.binder_path(self.dockerfile),
34-
tag=image_spec,
35-
buildargs=build_args,
36-
decode=True,
37-
forcerm=True,
38-
rm=True,
39-
container_limits=limits,
40-
cache_from=cache_from
41-
):
31+
32+
build_kwargs = dict(
33+
path=os.getcwd(),
34+
dockerfile=self.binder_path(self.dockerfile),
35+
tag=image_spec,
36+
buildargs=build_args,
37+
decode=True,
38+
forcerm=True,
39+
rm=True,
40+
container_limits=limits,
41+
cache_from=cache_from
42+
)
43+
44+
build_kwargs.update(extra_build_kwargs)
45+
46+
for line in client.build(**build_kwargs):
4247
yield line

repo2docker/buildpacks/legacy/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,14 @@ def get_build_script_files(self):
7676
7777
This currently adds a frozen set of Python requirements to the dict
7878
of files.
79-
79+
8080
"""
8181
return {
8282
'legacy/root.frozen.yml': '/tmp/root.frozen.yml',
8383
'legacy/python3.frozen.yml': '/tmp/python3.frozen.yml',
8484
}
85-
86-
def build(self, client, image_spec, memory_limit, build_args, cache_from):
85+
86+
def build(self, client, image_spec, memory_limit, build_args, cache_from, extra_build_kwargs):
8787
"""Build a legacy Docker image."""
8888
with open(self.dockerfile, 'w') as f:
8989
f.write(self.render())
@@ -94,7 +94,7 @@ def build(self, client, image_spec, memory_limit, build_args, cache_from):
9494
env_file,
9595
)
9696
shutil.copy(src_path, env_file)
97-
return super().build(client, image_spec, memory_limit, build_args, cache_from)
97+
return super().build(client, image_spec, memory_limit, build_args, cache_from, extra_build_kwargs)
9898

9999
def detect(self):
100100
"""Check if current repo should be built with the Legacy BuildPack.

tests/unit/test_app.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from tempfile import TemporaryDirectory
22
from unittest.mock import patch
33

4+
import docker
45
import escapism
56

67
from repo2docker.app import Repo2Docker
@@ -72,3 +73,32 @@ def test_local_dir_image_name(repo_with_content):
7273
assert app.output_image_spec.startswith(
7374
'r2d' + escapism.escape(upstream, escape_char='-').lower()
7475
)
76+
77+
78+
def test_build_kwargs(repo_with_content):
79+
upstream, sha1 = repo_with_content
80+
argv = [upstream]
81+
app = make_r2d(argv)
82+
app.extra_build_kwargs = {'somekey': "somevalue"}
83+
84+
with patch.object(docker.APIClient, 'build') as builds:
85+
builds.return_value = []
86+
app.build()
87+
builds.assert_called_once()
88+
args, kwargs = builds.call_args
89+
assert 'somekey' in kwargs
90+
assert kwargs['somekey'] == "somevalue"
91+
92+
93+
def test_run_kwargs(repo_with_content):
94+
upstream, sha1 = repo_with_content
95+
argv = [upstream]
96+
app = make_r2d(argv)
97+
app.extra_run_kwargs = {'somekey': "somevalue"}
98+
99+
with patch.object(docker.DockerClient, 'containers') as containers:
100+
app.start_container()
101+
containers.run.assert_called_once()
102+
args, kwargs = containers.run.call_args
103+
assert 'somekey' in kwargs
104+
assert kwargs['somekey'] == "somevalue"

tests/unit/test_cache_from.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ def test_cache_from_base(tmpdir):
1717
fake_log_value = {'stream': 'fake'}
1818
fake_client = MagicMock(spec=docker.APIClient)
1919
fake_client.build.return_value = iter([fake_log_value])
20+
extra_build_kwargs = {'somekey': 'somevalue'}
2021

2122
# Test base image build pack
2223
tmpdir.chdir()
23-
for line in BaseImage().build(fake_client, 'image-2', '1Gi', {}, cache_from):
24+
for line in BaseImage().build(fake_client, 'image-2', '1Gi', {}, cache_from, extra_build_kwargs):
2425
assert line == fake_log_value
2526
called_args, called_kwargs = fake_client.build.call_args
2627
assert 'cache_from' in called_kwargs
@@ -36,13 +37,14 @@ def test_cache_from_docker(tmpdir):
3637
fake_log_value = {'stream': 'fake'}
3738
fake_client = MagicMock(spec=docker.APIClient)
3839
fake_client.build.return_value = iter([fake_log_value])
39-
40+
extra_build_kwargs = {'somekey': 'somevalue'}
4041
tmpdir.chdir()
42+
4143
# test dockerfile
4244
with tmpdir.join("Dockerfile").open('w') as f:
4345
f.write('FROM scratch\n')
4446

45-
for line in DockerBuildPack().build(fake_client, 'image-2', '1Gi', {}, cache_from):
47+
for line in DockerBuildPack().build(fake_client, 'image-2', '1Gi', {}, cache_from, extra_build_kwargs):
4648
assert line == fake_log_value
4749
called_args, called_kwargs = fake_client.build.call_args
4850
assert 'cache_from' in called_kwargs
@@ -57,12 +59,13 @@ def test_cache_from_legacy(tmpdir):
5759
fake_log_value = {'stream': 'fake'}
5860
fake_client = MagicMock(spec=docker.APIClient)
5961
fake_client.build.return_value = iter([fake_log_value])
62+
extra_build_kwargs = {'somekey': 'somevalue'}
6063

6164
# Test legacy docker image
6265
with tmpdir.join("Dockerfile").open('w') as f:
6366
f.write('FROM andrewosh/binder-base\n')
6467

65-
for line in LegacyBinderDockerBuildPack().build(fake_client, 'image-2', '1Gi', {}, cache_from):
68+
for line in LegacyBinderDockerBuildPack().build(fake_client, 'image-2', '1Gi', {}, cache_from, extra_build_kwargs):
6669
assert line == fake_log_value
6770
called_args, called_kwargs = fake_client.build.call_args
6871
assert 'cache_from' in called_kwargs

0 commit comments

Comments
 (0)