使用通配符复制文件夹的Docker COPY

29

假设有如下的文件结构:

project root
|-- X.sln
|-- src
|    |-- Foo
|    |    |-- Foo.fsproj
|    |    |-- Foo.fs
|    |-- Bar
|         |-- Bar.fsproj
|         |-- Bar.fs
|-- test
     |-- Baz
          |-- Baz.fsproj

我想先将所有的.fsproj文件添加到我的Docker镜像中,然后运行一个命令,最后再添加其余的文件。我尝试了以下方法,但显然它没有起作用

COPY X.sln .
COPY **/*.fsproj .
RUN dotnet restore
COPY . .
RUN dotnet build

这个想法是,在前两个 COPY 步骤之后,映像文件系统上的文件树就像这样:

working dir
|-- X.sln
|-- src
|    |-- Foo
|    |    |-- Foo.fsproj
|    |-- Bar
|         |-- Bar.fsproj
|-- test
     |-- Baz
          |-- Baz.fsproj

只有在 RUN dotnet restore 之后,树的其余部分才被添加。

有没有一种方法可以模拟这种行为,最好不用求助于 dockerfile 外的脚本


除了缓存,你还有什么理由要这样做吗? - Tarun Lalwani
3
主要原因在于dotnet restore命令通常需要相当长的时间,所以在不必要时避免使用它,可以大幅减少构建时间。 - Tomas Aschan
@TomasAschan 你在此期间找到解决方案了吗? - Kirill Rakhman
@KirillRakhman 不,我仍在逐个复制每个项目文件。 - Tomas Aschan
@PhilippHaider:是的,对于F#,我可能不会费心。 - Tomas Aschan
显示剩余3条评论
4个回答

8

如果您使用dotnet命令管理解决方案,您可以使用以下代码:

  1. 将解决方案和所有项目文件复制到WORKDIR目录中
  2. 使用dotnet sln list列出解决方案中的项目
  3. 遍历项目列表并将相应的*proj文件移动到新创建的目录中。
COPY *.sln ./
COPY */*/*.*proj ./
RUN dotnet sln list | \
      tail -n +3 | \
      xargs -I {} sh -c \
        'target="{}"; dir="${target%/*}"; file="${target##*/}"; mkdir -p -- "$dir"; mv -- "$file" "$target"'

我因错误被踩了,但已经过了50分钟,无法取消我的投票。 - denisvlah

7

你可以使用两个运行命令来解决这个问题,使用 shell 命令(find、sed 和 xargs)。

按照以下步骤操作:

  1. 使用正则表达式查找所有的 fsproj 文件并提取不带扩展名的文件名,然后使用 xargs 将这些数据用于创建带有 mkdir 的目录;
  2. 基于先前的脚本,将正则表达式更改为从-到语法,并使用 mv 命令将文件移动到新创建的文件夹中。

例如:

    COPY *.sln ./
    COPY */*.fsproj ./
    RUN find *.fsproj | sed -e 's/.fsproj//g' | xargs mkdir
    RUN find *.fsproj | sed -r -e 's/((.+).fsproj)/.\/\1 .\/\2/g' | xargs -I % sh -c 'mv %'

参考资料:

如何使用xargs和sed进行搜索模式


sed s 命令允许您使用不同的分隔符,例如 '|' 而不是 '/'. 这样做可以避免在替换字符串中转义 /,使其更易读。 - user829755

0

好问题,我相信我已经找到了解决方案。 像这样有一个 .dockerignore 文件

# Ignore everything except *.fsproj.
**/*.*
!**/*.fsproj

请将您的Dockerfile-AA设置为以下内容(请更新ls)

FROM your-image
USER root
RUN mkdir -p /home/aa
WORKDIR /home/aa
COPY . .
RUN ls
RUN ls src
RUN ls src/p1
RUN ls src/p2
RUN ls src/p3
RUN dotnet restore

将您的Docker命令设置为这样

sudo docker build --rm -t my-new-img -f Dockerfile-AA .

第一次运行它,它只会显示 fsproj 文件被复制。 再次运行它,你看不到 ls 的结果,因为它正在使用缓存,很棒。

显然,你不能在同一个 Dockerfile 中拥有 dotnet restore,需要另一个名为 Dockerfile-BB 的 docker 文件。

FROM my-new-img
COPY . .
RUN dotnet build

所以,让你的脚本像这样

docker build --rm -t my-new-img -f Dockerfile-AA .
rm -f .dockerignore
docker build --rm -t my-new-img2 -f Dockerfile-BB .

这应该可以工作。在构建 my-new-img 时,它将会飞快。你需要至少尝试两次,因为缓存在我过去的经验中不会立即创建。这比逐行复制项目文件要好得多。


0

有一种模式可以实现您想要的功能,而不必使用Dockerfile外部的脚本,具体方法如下:

    COPY <project root> .    
    RUN <command to tar/zip the directory to save a copy inside the container> \
         <command the removes all the files you don't want> \
         dotnet restore \
         <command to unpack tar/zip and restore the files> \
         <command to remove the tar/zip> \
         dotnet build

这将使您的所有操作都在容器内完成。我将它们全部捆绑在一个RUN命令中,以便将所有活动保存在构建的单个层中。如果需要,您可以将它们分开。
以下是Linux上如何递归删除除所需文件之外的所有文件的示例:https://unix.stackexchange.com/a/15701/327086。根据您的示例,我的假设是这对您来说不会是昂贵的操作。

10
然而,这并没有解决我最初问题的“为什么”:因为您将COPY . .作为第一步,当我更改源文件时,构建上下文已经发生了变化,强制我在没有更改任何软件包的情况下等待dotnet restore。因此,与不关心首先复制项目文件的幼稚COPY . .RUN dotnet restoreRUN dotnet build相比,并没有提供任何价值。 - Tomas Aschan
你可能需要使用 .dockerignore 文件,至少过滤掉像 .gitDockerfile 等辅助目录和文件 - 否则任何编辑都会破坏 Docker 构建缓存,在开发中导致延长的构建时间。 - Ed Randall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接