Skip to content

Commit 46960a9

Browse files
committed
Using Debian instead of Alpine, not running tasks as root
1 parent 4ddc540 commit 46960a9

File tree

6 files changed

+74
-53
lines changed

6 files changed

+74
-53
lines changed

arca/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77

88
__all__ = ["Arca", "BaseBackend", "VenvBackend", "DockerBackend", "Result", "Task", "CurrentEnvironmentBackend",
99
"RequirementsStrategy", "VagrantBackend"]
10-
__version__ = "0.1.1"
10+
__version__ = "0.2.0a0"

arca/backend/docker.py

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class DockerBackend(BaseBackend):
3333
3434
* **python_version** - set a specific version, current env. python version by default
3535
* **keep_container_running** - stop the container right away (default) or keep it running
36-
* **apk_dependencies** - a list of dependencies to install via alpine
36+
* **apt_dependencies** - a list of dependencies to install via ``apt-get``
3737
* **disable_pull** - build all locally
3838
* **inherit_image** - instead of using the default base Arca image, use this one
3939
* **use_registry_name** - use this registry to store images with requirements and dependencies
@@ -42,7 +42,7 @@ class DockerBackend(BaseBackend):
4242

4343
python_version = LazySettingProperty(default=None)
4444
keep_container_running = LazySettingProperty(default=False)
45-
apk_dependencies = LazySettingProperty(default=None)
45+
apt_dependencies = LazySettingProperty(default=None)
4646
disable_pull = LazySettingProperty(default=False) # so the build can be tested
4747
inherit_image = LazySettingProperty(default=None)
4848
use_registry_name = LazySettingProperty(default=None)
@@ -60,8 +60,11 @@ class DockerBackend(BaseBackend):
6060

6161
INSTALL_DEPENDENCIES = """
6262
FROM {name}:{tag}
63-
RUN apk update
64-
RUN apk add --no-cache {dependencies}
63+
USER root
64+
RUN apt-get update && \
65+
apt-get install --no-install-recommends -y --autoremove {dependencies} && \
66+
apt-get clean
67+
USER arca
6568
CMD bash -i
6669
"""
6770

@@ -80,7 +83,7 @@ def validate_settings(self):
8083
8184
* Checks ``inherit_image`` format.
8285
* Checks ``use_registry_name`` format.
83-
* Checks that ``apk_dependencies`` is not set when ``inherit_image`` is set.
86+
* Checks that ``apt_dependencies`` is not set when ``inherit_image`` is set.
8487
8588
:raise ArcaMisconfigured: If some of the settings aren't valid.
8689
"""
@@ -118,17 +121,17 @@ def check_docker_access(self):
118121
raise BuildError("Docker is not running or the current user doesn't have permissions to access docker.")
119122

120123
def get_dependencies(self) -> Optional[List[str]]:
121-
""" Returns the ``apk_dependencies`` setting to a standardized format.
124+
""" Returns the ``apt_dependencies`` setting to a standardized format.
122125
123126
:raise ArcaMisconfigured: if the dependencies can't be converted into a list of strings
124127
:return: List of dependencies, ``None`` if there are none.
125128
"""
126129

127-
if not self.apk_dependencies:
130+
if not self.apt_dependencies:
128131
return None
129132

130133
try:
131-
dependencies = list([str(x).strip() for x in self.apk_dependencies])
134+
dependencies = list([str(x).strip() for x in self.apt_dependencies])
132135
except (TypeError, ValueError):
133136
raise ArcaMisconfigured("Apk dependencies can't be converted into a list of strings")
134137

@@ -303,23 +306,34 @@ def get_arca_base(self, pull=True):
303306

304307
pyenv_installer = "https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer"
305308
dockerfile = f"""
306-
FROM alpine:3.5
307-
RUN apk add --no-cache curl bash git nano g++ make jpeg-dev zlib-dev ca-certificates openssl-dev \
308-
readline-dev bzip2-dev sqlite-dev ncurses-dev linux-headers build-base \
309-
openssh
310-
311-
RUN curl -L {pyenv_installer} -o /pyenv-installer && \
312-
touch /root/.bashrc && \
313-
/bin/ln -s /root/.bashrc /root/.bash_profile && \
314-
/bin/bash /pyenv-installer && \
315-
rm /pyenv-installer && \
316-
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile && \
317-
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile && \
318-
echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
319-
320-
ENV HOME /root
321-
ENV PYENV_ROOT $HOME/.pyenv
322-
ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH
309+
FROM debian:stretch-slim
310+
RUN apt-get update && \
311+
apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
312+
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
313+
xz-utils tk-dev libffi-dev git && \
314+
apt-get clean
315+
316+
RUN adduser --system --disabled-password --shell /bin/bash arca
317+
318+
USER arca
319+
WORKDIR /home/arca
320+
RUN echo "source $HOME/.bash_profile" >> .bashrc
321+
322+
RUN curl -L {pyenv_installer} -o ~/pyenv-installer && \
323+
/bin/bash ~/pyenv-installer && \
324+
rm ~/pyenv-installer && \
325+
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> /home/arca/.bash_profile && \
326+
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> /home/arca/.bash_profile && \
327+
echo 'eval "$(pyenv init -)"' >> /home/arca/.bash_profile
328+
329+
USER root
330+
331+
RUN apt-get remove -y --autoremove curl && \
332+
apt-get clean -y
333+
334+
RUN mkdir /srv/scripts
335+
336+
USER arca
323337
324338
SHELL ["bash", "-lc"]
325339
CMD bash -i
@@ -341,10 +355,10 @@ def get_dockerfile():
341355

342356
return f"""
343357
FROM {base_arca_name}:{base_arca_tag}
344-
RUN pyenv update
345-
RUN pyenv install {python_version}
358+
RUN pyenv update && \
359+
pyenv install {python_version}
346360
ENV PYENV_VERSION {python_version}
347-
RUN mkdir /srv/scripts
361+
ENV PATH "/home/arca/.pyenv/shims:$PATH"
348362
CMD bash -i
349363
"""
350364

@@ -407,7 +421,7 @@ def get_image_with_installed_dependencies(self, image_name: str,
407421
dependencies: Optional[List[str]]) -> Tuple[str, str]:
408422
"""
409423
Return name and tag of a image, based on the Arca python image, with installed dependencies defined
410-
by ``apk_dependencies``.
424+
by ``apt_dependencies``.
411425
412426
:param image_name: Name of the image which will be ultimately used for the image.
413427
:param dependencies: List of dependencies in the standardized format.
@@ -637,7 +651,11 @@ def start_container(self, image, container_name: str, repo_path: Path):
637651
:type image: docker.models.images.Image
638652
:rtype: docker.models.container.Container
639653
"""
640-
container = self.client.containers.run(image, command="bash -i", detach=True, tty=True, name=container_name,
654+
command = "bash -i"
655+
if self.inherit_image:
656+
command = "sh -i"
657+
658+
container = self.client.containers.run(image, command=command, detach=True, tty=True, name=container_name,
641659
working_dir=str((Path("/srv/data") / self.cwd).resolve()),
642660
auto_remove=True)
643661

docs/backends.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ with ``docker`` (see `documentation <https://docs.docker.com/install/linux/linux
9797
This backend firstly creates an image with requirements and dependencies installed so the installation only runs one.
9898
By default the images are based on `custom images <https://hub.docker.com/r/mikicz/arca/tags/>`_, which have Python
9999
and several build tools pre-installed.
100-
These images are based on ``alpine`` and use `pyenv <https://github.com/pyenv/pyenv>`_ to install Python.
100+
These images are based on ``debian`` (slim ``stretch`` version) and use `pyenv <https://github.com/pyenv/pyenv>`_
101+
to install Python.
101102
You can specify you want to base your images on a different image with the ``inherit_image`` setting.
102103

103104
Once arca has an image with the requirements installed, it launches a container for each task and
@@ -120,9 +121,9 @@ Settings:
120121
but only CPython 3.6 has been tested. The default is the Python version of the current environment.
121122
This setting is ignored if ``inherit_image`` is set.
122123
* **keep_container_running**: When ``True``, containers aren't killed once the task finishes. Default is ``False``.
123-
* **apk_dependencies**: For some python libraries, system dependencies are required,
124+
* **apt_dependencies**: For some python libraries, system dependencies are required,
124125
for example ``libxml2-dev`` and ``libxslt-dev`` are needed for ``lxml``.
125-
With this settings you can specify a list of system dependencies that will be installed via alpine ``apk``.
126+
With this settings you can specify a list of system dependencies that will be installed via debian ``apt-get``.
126127
This setting is ignored if ``inherit_image`` is set since arca can't
127128
determined how to install requirements on an unknown system.
128129
* **disable_pull**: Disable pulling prebuilt arca images from Docker Hub and build even the base images locally.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def long_description():
2020

2121
setup(
2222
name="arca",
23-
version="0.1.1",
23+
version="0.2.0a0",
2424
author="Mikuláš Poul",
2525
author_email="[email protected]",
2626
description="A library for running Python functions (callables) from git repositories "

tests/common.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,17 @@ def return_str_function():
3737
"""
3838

3939
RETURN_PYTHON_VERSION_FUNCTION = """
40-
import sys
40+
import platform
4141
4242
def return_python_version():
43-
return "{}.{}.{}".format(sys.version_info.major, sys.version_info.minor, sys.version_info.micro)
43+
return platform.python_version()
4444
"""
4545

46-
RETURN_FREETYPE_VERSION = """
47-
import freetype
46+
RETURN_ALSAAUDIO_INSTALLED = """
47+
import alsaaudio
4848
49-
def return_freetype_version():
50-
return freetype.version()
49+
def return_alsaaudio_installed():
50+
return alsaaudio is not None
5151
"""
5252

5353
RETURN_PLATFORM = """

tests/test_docker.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import platform
2+
13
import pytest
24

35
from arca import Arca, DockerBackend, Task
46
from arca.exceptions import ArcaMisconfigured, PushToRegistryError
57
from common import (RETURN_COLORAMA_VERSION_FUNCTION, BASE_DIR, RETURN_PLATFORM,
6-
RETURN_PYTHON_VERSION_FUNCTION, RETURN_FREETYPE_VERSION)
8+
RETURN_PYTHON_VERSION_FUNCTION, RETURN_ALSAAUDIO_INSTALLED)
79

810

911
def test_keep_container_running(temp_repo_func):
@@ -35,7 +37,7 @@ def test_keep_container_running(temp_repo_func):
3537
assert count_after_stop == container_count
3638

3739

38-
@pytest.mark.parametrize("python_version", ["3.6.0", "3.6.3"])
40+
@pytest.mark.parametrize("python_version", ["3.6.0", platform.python_version()])
3941
def test_python_version(temp_repo_func, python_version):
4042
backend = DockerBackend(verbosity=2, python_version=python_version)
4143

@@ -49,25 +51,25 @@ def test_python_version(temp_repo_func, python_version):
4951
assert arca.run(temp_repo_func.url, temp_repo_func.branch, task).output == python_version
5052

5153

52-
def test_apk_dependencies(temp_repo_func):
53-
backend = DockerBackend(verbosity=2, apk_dependencies=["freetype-dev"])
54+
def test_apt_dependencies(temp_repo_func):
55+
backend = DockerBackend(verbosity=2, apt_dependencies=["libasound2-dev"])
5456

5557
arca = Arca(backend=backend, base_dir=BASE_DIR)
5658

5759
requirements_path = temp_repo_func.repo_path / "requirements.txt"
58-
requirements_path.write_text("freetype-py")
60+
requirements_path.write_text("pyalsaaudio==0.8.4")
5961

60-
temp_repo_func.file_path.write_text(RETURN_FREETYPE_VERSION)
62+
temp_repo_func.file_path.write_text(RETURN_ALSAAUDIO_INSTALLED)
6163
temp_repo_func.repo.index.add([str(temp_repo_func.file_path), str(requirements_path)])
6264
temp_repo_func.repo.index.commit("Added requirements, changed to lxml")
6365

64-
# ``import freetype`` raises an error if the library ``freetype-dev`` is not installed
65-
task = Task("test_file:return_freetype_version")
66+
# pyalsaaudio can't be installed if libasound2-dev is missing
67+
task = Task("test_file:return_alsaaudio_installed")
6668
assert arca.run(temp_repo_func.url, temp_repo_func.branch, task).output
6769

6870

6971
def test_inherit_image(temp_repo_func):
70-
backend = DockerBackend(verbosity=2, inherit_image="python:3.6")
72+
backend = DockerBackend(verbosity=2, inherit_image="python:alpine3.6")
7173

7274
arca = Arca(backend=backend, base_dir=BASE_DIR)
7375
task = Task("test_file:return_str_function")
@@ -80,8 +82,8 @@ def test_inherit_image(temp_repo_func):
8082

8183
task = Task("test_file:return_platform")
8284

83-
# alpine is the default, dist() returns ('', '', '') on - so this fails when the default image is used
84-
assert arca.run(temp_repo_func.url, temp_repo_func.branch, task).output == "debian"
85+
# debian is the default, alpine dist() returns ('', '', '') on - so this fails when the default image is used
86+
assert arca.run(temp_repo_func.url, temp_repo_func.branch, task).output != "debian"
8587

8688
requirements_path = temp_repo_func.repo_path / backend.requirements_location
8789
requirements_path.write_text("colorama==0.3.9")
@@ -161,6 +163,6 @@ def test_push_to_registry_fail(temp_repo_func):
161163

162164

163165
def test_inherit_image_with_dependecies():
164-
backend = DockerBackend(inherit_image="python:alpine3.6", apk_dependencies=["freetype-dev"])
166+
backend = DockerBackend(inherit_image="python:alpine3.6", apt_dependencies=["libasound2-dev"])
165167
with pytest.raises(ArcaMisconfigured):
166168
Arca(backend=backend)

0 commit comments

Comments
 (0)