Skip to content

Commit

Permalink
docker configfile added
Browse files Browse the repository at this point in the history
  • Loading branch information
davidsanfal committed Apr 18, 2024
1 parent 360316f commit db89115
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 12 deletions.
86 changes: 79 additions & 7 deletions conan/internal/runner/docker.py
@@ -1,8 +1,9 @@
from collections import namedtuple
import os
import json
import platform
import shutil
import itertools
import yaml
from conan.api.model import ListPattern
from conan.api.output import Color, ConanOutput
from conan.api.conan_api import ConfigAPI
Expand All @@ -13,6 +14,61 @@
from conans.model.version import Version


def config_parser(file_path):
'''
- image (str)
run:
- name -> name (str)
- containerEnv -> environment (dict)
- containerUser -> user (str or int)
- privileged -> privileged (boolean)
- capAdd -> cap_add (list)
- securityOpt -> security_opt (list)
- mounts -> volumes (dict)
dockerfile:
- build.dockerfile -> dockerfile (str) -> dockerfile.read()
- build.context -> path (str)
- build.args -> buildargs (dict)
build.options -> X (python extra params [extra_hosts, network_mode, ...])
- build.cacheFrom -> cache_from (list)
'''
Build = namedtuple('Build', ['dockerfile', 'build_context', 'build_args', 'cache_from'])
Run = namedtuple('Run', ['name', 'environment', 'user', 'privileged', 'cap_add', 'security_opt', 'volumes'])
Conf = namedtuple('Conf', ['image', 'build', 'run'])
if file_path:
def _instans_or_error(value, obj):
if value and (not isinstance(value, obj)):
raise Exception(f"{value} must be a {obj}")
return value
with open(file_path, 'r') as f:
runnerfile = yaml.safe_load(f)
return Conf(
image=_instans_or_error(runnerfile.get('image'), str),
build=Build(
dockerfile=_instans_or_error(runnerfile.get('build', {}).get('dockerfile'), str),
build_context=_instans_or_error(runnerfile.get('build', {}).get('build_context'), str),
build_args=_instans_or_error(runnerfile.get('build', {}).get('build_args'), dict),
cache_from=_instans_or_error(runnerfile.get('build', {}).get('cacheFrom'), list),
),
run=Run(
name=_instans_or_error(runnerfile.get('run', {}).get('name'), str),
environment=_instans_or_error(runnerfile.get('run', {}).get('containerEnv'), dict),
user=_instans_or_error(runnerfile.get('run', {}).get('containerUser'), str),
privileged=_instans_or_error(runnerfile.get('run', {}).get('privileged'), bool),
cap_add=_instans_or_error(runnerfile.get('run', {}).get('capAdd'), list),
security_opt=_instans_or_error(runnerfile.get('run', {}).get('securityOpt'), dict),
volumes=_instans_or_error(runnerfile.get('run', {}).get('mounts'), dict),
)
)
else:
return Conf(
image=None,
build=Build(dockerfile=None, build_context=None, build_args=None, cache_from=None),
run=Run(name=None, environment=None, user=None, privileged=None, cap_add=None, security_opt=None, volumes=None)
)


def docker_info(msg, error=False):
fg=Color.BRIGHT_MAGENTA
if error:
Expand Down Expand Up @@ -68,13 +124,15 @@ def __init__(self, conan_api, command, profile, args, raw_args):
self.command = ' '.join([f'conan {command}'] + [f'"{raw_arg}"' if ' ' in raw_arg else raw_arg for raw_arg in raw_args] + ['-f json > create.json'])

# Container config
self.dockerfile = profile.runner.get('dockerfile')
self.docker_build_context = profile.runner.get('docker_build_context')
self.image = profile.runner.get('image')
# https://containers.dev/implementors/json_reference/
self.configfile = config_parser(profile.runner.get('configfile'))
self.dockerfile = profile.runner.get('dockerfile') or self.configfile.build.dockerfile
self.docker_build_context = profile.runner.get('build_context') or self.configfile.build.build_context
self.image = profile.runner.get('image') or self.configfile.image
if not (self.dockerfile or self.image):
raise ConanException("'dockerfile' or docker image name is needed")
self.image = self.image or 'conan-runner-default'
self.name = f'conan-runner-{profile.runner.get("suffix", "docker")}'
self.name = self.configfile.image or f'conan-runner-{profile.runner.get("suffix", "docker")}'
self.remove = str(profile.runner.get('remove', 'false')).lower() == 'true'
self.cache = str(profile.runner.get('cache', 'clean'))
self.container = None
Expand All @@ -94,13 +152,21 @@ def run(self):
self.container = self.docker_client.containers.get(self.name)
self.container.start()
else:
if self.configfile.run.environment:
environment.update(self.configfile.run.environment)
if self.configfile.run.volumes:
volumes.update(self.configfile.run.volumes)
docker_info('Creating the docker container')
self.container = self.docker_client.containers.run(
self.image,
"/bin/bash -c 'while true; do sleep 30; done;'",
name=self.name,
volumes=volumes,
environment=environment,
user=self.configfile.run.user,
privileged=self.configfile.run.privileged,
cap_add=self.configfile.run.cap_add,
security_opt=self.configfile.run.security_opt,
detach=True,
auto_remove=False)
docker_info(f'Container {self.name} running')
Expand Down Expand Up @@ -130,12 +196,18 @@ def run(self):
def build_image(self):
dockerfile_file_path = self.dockerfile
if os.path.isdir(self.dockerfile):
dockerfile_file_path = os.path.join(self.dockerfile, 'Dockerfile')
dockerfile_file_path = os.path.join(self.dockerfile, 'Dockerfile')
with open(dockerfile_file_path) as f:
build_path = self.docker_build_context or os.path.dirname(dockerfile_file_path)
ConanOutput().highlight(f"Dockerfile path: '{dockerfile_file_path}'")
ConanOutput().highlight(f"Docker build context: '{build_path}'\n")
docker_build_logs = self.docker_api.build(path=build_path, dockerfile=f.read(), tag=self.image)
docker_build_logs = self.docker_api.build(
path=build_path,
dockerfile=f.read(),
tag=self.image,
buildargs=self.configfile.build.build_args,
cache_from=self.configfile.build.cache_from,
)
for chunk in docker_build_logs:
for line in chunk.decode("utf-8").split('\r\n'):
if line:
Expand Down
77 changes: 72 additions & 5 deletions conans/test/integration/command/runner_test.py
Expand Up @@ -63,7 +63,7 @@ def test_create_docker_runner_dockerfile_folder_path():
[runner]
type=docker
dockerfile={dockerfile_path()}
docker_build_context={conan_base_path()}
build_context={conan_base_path()}
image=conan-runner-default-test
cache=copy
remove=True
Expand All @@ -81,7 +81,7 @@ def test_create_docker_runner_dockerfile_folder_path():
[runner]
type=docker
dockerfile={dockerfile_path()}
docker_build_context={conan_base_path()}
build_context={conan_base_path()}
image=conan-runner-default-test
cache=clean
remove=True
Expand Down Expand Up @@ -149,7 +149,7 @@ def test_create_docker_runner_dockerfile_file_path():
[runner]
type=docker
dockerfile={dockerfile_path("Dockerfile_test")}
docker_build_context={conan_base_path()}
build_context={conan_base_path()}
image=conan-runner-default-test
cache=copy
remove=True
Expand Down Expand Up @@ -235,7 +235,7 @@ def package(self):
type=docker
image=conan-runner-ninja-test
dockerfile={dockerfile_path("Dockerfile_ninja")}
docker_build_context={conan_base_path()}
build_context={conan_base_path()}
cache=copy
remove=True
""")
Expand Down Expand Up @@ -275,7 +275,7 @@ def test_create_docker_runner_profile_abs_path():
[runner]
type=docker
dockerfile={dockerfile_path("Dockerfile_test")}
docker_build_context={conan_base_path()}
build_context={conan_base_path()}
image=conan-runner-default-test
cache=copy
remove=True
Expand All @@ -300,6 +300,73 @@ class MyTest(ConanFile):
client.save({"conanfile.py": conanfile, "host": profile_host, "build": profile_build})
client.run(f"create . -pr:h '{os.path.join(client.current_folder, 'host')}' -pr:b '{os.path.join(client.current_folder, 'build')}'")

assert "Restore: pkg/0.2" in client.out
assert "Restore: pkg/0.2:a0826e5ee3b340fcc7a8ccde40224e3562316307" in client.out
assert "Restore: pkg/0.2:a0826e5ee3b340fcc7a8ccde40224e3562316307 metadata" in client.out
assert "Removing container" in client.out


@pytest.mark.skipif(docker_skip(), reason="Only docker running")
# @pytest.mark.xfail(reason="conan inside docker optional test")
def test_create_docker_runner_profile_abs_path_from_configfile():
"""
Tests the ``conan create . ``
"""
client = TestClient()
configfile = textwrap.dedent(f"""
image: conan-runner-default-test
build:
dockerfile: {dockerfile_path("Dockerfile_test")}
build_context: {conan_base_path()}
""")
client.save({"configfile.yaml": configfile})


profile_build = textwrap.dedent(f"""\
[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.cppstd=gnu17
compiler.libcxx=libstdc++11
compiler.version=11
os=Linux
""")
profile_host = textwrap.dedent(f"""\
[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.cppstd=gnu17
compiler.libcxx=libstdc++11
compiler.version=11
os=Linux
[runner]
type=docker
configfile={os.path.join(client.current_folder, 'configfile.yaml')}
cache=copy
remove=True
""")
conanfile = textwrap.dedent("""
from conan import ConanFile
class MyTest(ConanFile):
name = "pkg"
version = "0.2"
settings = "build_type", "compiler"
author = "John Doe"
license = "MIT"
url = "https://foo.bar.baz"
homepage = "https://foo.bar.site"
topics = "foo", "bar", "qux"
provides = "libjpeg", "libjpg"
deprecated = "other-pkg"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": False, "fPIC": True}
""")
client.save({"conanfile.py": conanfile, "host": profile_host, "build": profile_build})
client.run(f"create . -pr:h '{os.path.join(client.current_folder, 'host')}' -pr:b '{os.path.join(client.current_folder, 'build')}'")

assert "Restore: pkg/0.2" in client.out
assert "Restore: pkg/0.2:a0826e5ee3b340fcc7a8ccde40224e3562316307" in client.out
assert "Restore: pkg/0.2:a0826e5ee3b340fcc7a8ccde40224e3562316307 metadata" in client.out
Expand Down

0 comments on commit db89115

Please sign in to comment.