docker-py icon indicating copy to clipboard operation
docker-py copied to clipboard

Can not pass `fileobj` together with `path` context to `build`

Open majkrzak opened this issue 7 years ago • 12 comments

It seems to be impossible to pass ephemeral Dockerfile to build command together with build context. Here is command line use case:

docker build -t . -f-<<EOF
Lorem ipsum docker file
EOF

According to the documentation https://docker-py.readthedocs.io/en/stable/images.htmlit should be possible to pass Dockerfile as file object, but in current implementation path param with context is ignored.

Here is path that fix part of this issue (it don't work when path.startswith(('http://', 'https://','git://', 'github.com/', 'git@')))

diff --git a/docker/api/build.py b/docker/api/build.py
index 0486dce..711def2 100644
--- a/docker/api/build.py
+++ b/docker/api/build.py
@@ -133,7 +133,7 @@ class BuildApiMixin(object):
            if not fileobj:
                raise TypeError("You must specify fileobj with custom_context")
            context = fileobj
-        elif fileobj is not None:
+        elif path is None and fileobj is not None:
            context = utils.mkbuildcontext(fileobj)
        elif path.startswith(('http://', 'https://',
                            'git://', 'github.com/', 'git@')):
@@ -149,7 +149,7 @@ class BuildApiMixin(object):
                        lambda x: x != '' and x[0] != '#',
                        [l.strip() for l in f.read().splitlines()]
                    ))
-            dockerfile = process_dockerfile(dockerfile, path)
+            dockerfile = process_dockerfile(dockerfile, path, fileobj)
            context = utils.tar(
                path, exclude=exclude, dockerfile=dockerfile, gzip=gzip
            )
@@ -332,7 +332,13 @@ class BuildApiMixin(object):
            log.debug('No auth config found')


-def process_dockerfile(dockerfile, path):
+def process_dockerfile(dockerfile, path, fileobj):
+    if fileobj is not None:
+        return (
+            '.dockerfile.{0:x}'.format(random.getrandbits(160)),
+            fileobj.read()
+        )
+
    if not dockerfile:
        return (None, None)

majkrzak avatar Aug 07 '18 18:08 majkrzak

In my opinion perfect solution will be to pass context as context and Dockerfile as dockerfile then it might be distinguished if context is an directory path, URL, or tar fileobject same with dockerfile but little harder.

client.images.build(context='.', dockerfile='Dockerfile.local')
client.images.build(context='http://remote.uri', dockerfile=''' FROM ubuntu
facny docker file
''')
client.images.build(context=open('adas'), dockerfile=open('dasdasd'))

majkrzak avatar Aug 07 '18 18:08 majkrzak

OK, here is even better fix. It handles multiline dockerfile as content of Dockerfile. It should also let us embed Dockerfile in docker-compose.yml

diff --git a/docker/api/build.py b/docker/api/build.py
index 0486dce..41649b7 100644
--- a/docker/api/build.py
+++ b/docker/api/build.py
@@ -336,6 +336,12 @@ def process_dockerfile(dockerfile, path):
    if not dockerfile:
        return (None, None)

+    if '\n' in dockerfile:
+        return (
+            '.dockerfile.{0:x}'.format(random.getrandbits(160)),
+            dockerfile
+        )
+
    abs_dockerfile = dockerfile
    if not os.path.isabs(dockerfile):
        abs_dockerfile = os.path.join(path, dockerfile)

majkrzak avatar Aug 07 '18 18:08 majkrzak

Why not include the dockerfile in the context?

TomasTomecek avatar Aug 11 '18 08:08 TomasTomecek

If dockerfile it's not part of context for some reason, it have to be affected to be dockerized. I know that it can be passed as absolute path, but if dockerfile is ephemeral it requires saving is somewhere is filesystem which is little more complicated. Generally it py lib lacs functionality of cli, and arguments could be simplified a bit.

majkrzak avatar Aug 11 '18 08:08 majkrzak

I experience the same problem. The API seems to assume that Dockerfile is always placed in build context which is a more restricted view than the one used by Docker itself. I can't find a way to easily implement the build command docker -f Dockerfile ${DIR} without manually copying file to build context.

mcopik avatar Jan 13 '20 13:01 mcopik

Similarly I would also like to use an in-memory generated Dockerfile (mainly to dynamically pick a FROM line for tracking an upstream image.) I would like to copy static files in the build context still like test.txt. It looks like I will just need to write my dynamically generated Dockerfile out to a file and do a normal build.

fileobj = io.BytesIO(b'''FROM upstreamimage:upstreamtag
COPY test.txt /
''')
client.images.build(
    fileobj=fileobj,
    # no way to set context while using fileobj
)

danieladams456 avatar Apr 14 '20 20:04 danieladams456

@danieladams456 I end up with the PR that they don't want to merge and this simple polyfill that might help you:

import docker.api.build
docker.api.build.process_dockerfile = lambda dockerfile, path: ('Dockerfile', dockerfile)

And then simply:

client.images.build(path='.',dockerfile='FROM ...')

majkrzak avatar Apr 14 '20 21:04 majkrzak

Is the project dead or what?

majkrzak avatar Jun 26 '20 13:06 majkrzak

any update to pass the context along with fileobj????

nitishxp avatar Oct 07 '20 08:10 nitishxp

@danieladams456 I end up with the PR that they don't want to merge and this simple polyfill that might help you:

import docker.api.build
docker.api.build.process_dockerfile = lambda dockerfile, path: ('Dockerfile', dockerfile)

And then simply:

client.images.build(path='.',dockerfile='FROM ...')

Hi @mcopik how to use this??

nitishxp avatar Oct 07 '20 08:10 nitishxp

@danieladams456 I end up with the PR that they don't want to merge and this simple polyfill that might help you:

import docker.api.build
docker.api.build.process_dockerfile = lambda dockerfile, path: ('Dockerfile', dockerfile)

And then simply:

client.images.build(path='.',dockerfile='FROM ...')

Thank you u're the best

imgVOID avatar Aug 16 '21 18:08 imgVOID

+1 for the ability to specify build context together with fileobj. My use-case: I'm creating a utility that is intended to be used across our org mainly by it people but not engineers. The utility gets a python code in the directory, packages it into a docker image pushes it and then call another service that will run the image in a dedicated env. The approach is to store a Docker file as a string inside the utility and pass as a fileobj. But that cannot be done, as image build process requires context for copying data from user machine.

viktorsobol avatar Mar 15 '22 10:03 viktorsobol