diff --git a/bin/ch-image.py.in b/bin/ch-image.py.in index ef5452bed..bfdd2a09e 100644 --- a/bin/ch-image.py.in +++ b/bin/ch-image.py.in @@ -189,8 +189,8 @@ def main(): help="set build-time variable ARG to VAL, or $ARG if no VAL") sp.add_argument("-f", "--file", metavar="DOCKERFILE", help="Dockerfile to use (default: CONTEXT/Dockerfile)") - sp.add_argument("--force", metavar="MODE", nargs="?", default=None, - choices=["fakeroot", "seccomp"], const="seccomp", + sp.add_argument("--force", metavar="MODE", nargs="?", default="seccomp", + type=ch.Force_Mode, const="seccomp", help="inject unprivileged build workarounds") sp.add_argument("--force-cmd", metavar="CMD,ARG1[,ARG2...]", action="append", default=[], diff --git a/doc/ch-image.rst b/doc/ch-image.rst index ce517a874..1a587d2bf 100644 --- a/doc/ch-image.rst +++ b/doc/ch-image.rst @@ -559,9 +559,10 @@ Options: this case. :code:`--force[=MODE]` - Use unprivileged build workarounds of mode :code:`MODE`, which can be - :code:`fakeroot` or :code:`seccomp` (the default). See section “Privilege - model” below for details on what this does and when you might need it. + Use unprivileged build with root emulation mode :code:`MODE`, which can be + :code:`fakeroot`, :code:`seccomp` (the default), or :code:`none`. See + section “Privilege model” below for details on what this does and when you + might need it. :code:`--force-cmd=CMD,ARG1[,ARG2...]` If command :code:`CMD` is found in a :code:`RUN` instruction, add the @@ -612,20 +613,18 @@ or “`fakeroot `_” mod of some competing builders, which do require privileged supporting code or utilities. -Without workarounds provided by :code:`--force`, this approach does confuse -programs that expect to have real root privileges, most notably distribution -package installers. This subsection describes why that happens and what you -can do about it. +Without root emulation, this approach does confuse programs that expect to have +real root privileges, most notably distribution package installers. This +subsection describes why that happens and what you can do about it. :code:`ch-image` executes all instructions as the normal user who invokes it. For :code:`RUN`, this is accomplished with :code:`ch-run` arguments including -:code:`-w --uid=0 --gid=0`. That is, your host EUID and EGID are both mapped -to zero inside the container, and only one UID (zero) and GID (zero) are -available inside the container. Under this arrangement, processes running in -the container for each :code:`RUN` *appear* to be running as root, but many -privileged system calls will fail without the workarounds described below. -**This affects any fully unprivileged container build, not just -Charliecloud.** +:code:`-w --uid=0 --gid=0`. That is, your host EUID and EGID are both mapped to +zero inside the container, and only one UID (zero) and GID (zero) are available +inside the container. Under this arrangement, processes running in the container +for each :code:`RUN` *appear* to be running as root, but many privileged system +calls will fail without the root emulation methods described below. **This +affects any fully unprivileged container build, not just Charliecloud.** The most common time to see this is installing packages. For example, here is RPM failing to :code:`chown(2)` a file, which makes the package update fail: @@ -651,8 +650,8 @@ Charliecloud provides two different mechanisms to avoid these problems. Both involve lying to the containerized process about privileged system calls, but at very different levels of complexity. -Workaround mode :code:`fakeroot` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Root emulation mode :code:`fakeroot` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This mode uses :code:`fakeroot(1)` to maintain an elaborate web of deceit that is internally consistent. This program intercepts both privileged system calls @@ -700,8 +699,8 @@ exactly what it is doing. :code:`fakeroot` mode works and :code:`seccomp` does not, please let us know. -Workaround mode :code:`seccomp` (default) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Root emulation mode :code:`seccomp` (default) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This mode uses the kernel’s :code:`seccomp(2)` system call filtering to intercept certain privileged system calls, do absolutely nothing, and return @@ -742,6 +741,17 @@ filter and has no knowledge of which instructions actually used the intercepted system calls. Therefore, the printed “instructions modified” number is only a count of instructions with a hook applied as described above. +:code:`RUN` instruction +~~~~~~~~~~~~~~~~~~~~~~~ + +In terminal output, image metadata, and the build cache, the :code:`RUN` +instruction is always logged as :code:`RUN.S`, :code:`RUN.F`, or :code:`RUN.N`. +The letter appended to the instruction reflects the root emulation mode used +during the build in which the instruction was executed. :code:`RUN.S` indicates +:code:`seccomp`, :code:`RUN.F` indicates :code:`fakeroot`, and :code:`RUN.N` +indicates that neither form of root emulation was used (:code:`--force=none`). + + Compatibility with other Dockerfile interpreters ------------------------------------------------ diff --git a/doc/faq.rst b/doc/faq.rst index 5d089787c..f9faf20dd 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -99,31 +99,6 @@ two solutions: processes and one writes a file in the image that another is reading or writing). -:code:`ch-image` fails with "argument --force: invalid choice" --------------------------------------------------------------- - -This happens when specifying the context directly after the :code:`--force` -option, e.g. - -:: - - $ ch-image build --force examples/hello/ - [...] - ch-image build: error: argument --force: invalid choice: 'examples/hello/' (choose from 'fakeroot', 'seccomp') - -This happens because the command line interprets the argument after -:code:`--force` as the optional input for :code:`--force`. When said argument -isn’t :code:`fakeroot` or :code:`seccomp`, the program throws an error. The -solution is to add a :code:`--` after :code:`--force` to indicate the end of -the command line options, e.g. - -:: - - $ ch-image build --force -- . - inferred image name: hello - [...] - grown in 3 instructions: hello - :code:`ch-image` fails with "certificate verify failed" ------------------------------------------------------- diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 8cf066a44..79aa2a3a4 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -41,7 +41,7 @@ should be able to tell you if this is linked in. :: $ cd /usr/local/share/doc/charliecloud/examples/hello - $ ch-image build --force -- . + $ ch-image build . inferred image name: hello [...] grown in 3 instructions: hello @@ -67,7 +67,7 @@ section. :: $ cd /usr/local/share/doc/charliecloud/examples/hello - $ ch-image build --force . + $ ch-image build . inferred image name: hello [...] grown in 4 instructions: hello @@ -444,16 +444,14 @@ These two build should take about 15 minutes total, depending on the speed of your system. Note that Charliecloud infers their names from the Dockerfile name, so we -don’t need to specify :code:`-t`. Also, :code:`--force` enables some -workarounds for tools like distribution package managers that expect to really -be root. +don’t need to specify :code:`-t`. :: - $ ch-image build --force \ + $ ch-image build \ -f /usr/local/share/doc/charliecloud/examples/Dockerfile.almalinux_8ch \ /usr/local/share/doc/charliecloud/examples - $ ch-image build --force \ + $ ch-image build \ -f /usr/local/share/doc/charliecloud/examples/Dockerfile.openmpi \ /usr/local/share/doc/charliecloud/examples diff --git a/lib/build.py b/lib/build.py index 7cc6df32f..3f9906927 100644 --- a/lib/build.py +++ b/lib/build.py @@ -88,12 +88,12 @@ def main(cli_): ch.INFO("inferred image name: %s" % cli.tag) # --force and friends. - if (cli.force_cmd and cli.force == "fakeroot"): + if (cli.force_cmd and cli.force == ch.Force_Mode.FAKEROOT): ch.FATAL("--force-cmd and --force=fakeroot are incompatible") if (not cli.force_cmd): cli.force_cmd = force.FORCE_CMD_DEFAULT else: - cli.force = "seccomp" + cli.force = ch.Force_Mode.SECCOMP # convert cli.force_cmd to parsed dict force_cmd = dict() for line in cli.force_cmd: @@ -101,10 +101,10 @@ def main(cli_): force_cmd[cmd] = args cli.force_cmd = force_cmd ch.VERBOSE("force mode: %s" % cli.force) - if (cli.force == "seccomp"): + if (cli.force == ch.Force_Mode.SECCOMP): for (cmd, args) in cli.force_cmd.items(): ch.VERBOSE("force command: %s" % ch.argv_to_string([cmd] + args)) - if ( cli.force == "seccomp" + if ( cli.force == ch.Force_Mode.SECCOMP and ch.cmd([ch.CH_BIN + "/ch-run", "--feature=seccomp"], fail_ok=True) != 0): ch.FATAL("ch-run was not built with seccomp(2) support") @@ -199,9 +199,9 @@ def build_arg_get(arg): if (ml.instruction_total_ct == 0): ch.FATAL("no instructions found: %s" % cli.file) assert (ml.inst_prev.image_i + 1 == image_ct) # should’ve errored already - if (cli.force and ml.miss_ct != 0): + if ((cli.force != ch.Force_Mode.NONE) and ml.miss_ct != 0): ch.INFO("--force=%s: modified %d RUN instructions" - % (cli.force, forcer.run_modified_ct)) + % (cli.force.value, forcer.run_modified_ct)) ch.INFO("grown in %d instructions: %s" % (ml.instruction_total_ct, ml.inst_prev.image)) # FIXME: remove when we’re done encouraging people to use the build cache. @@ -1155,9 +1155,9 @@ class Run(Instruction): def str_name(self): # Can’t get this from the forcer object because it might not have been # initialized yet. - if (cli.force is None): - tag = "" - elif (cli.force == "fakeroot"): + if (cli.force == ch.Force_Mode.NONE): + tag = ".N" + elif (cli.force == ch.Force_Mode.FAKEROOT): # FIXME: This causes spurious misses because it adds the force tag to # *all* RUN instructions, not just those that actually were modified # (i.e, any RUN instruction will miss the equivalent RUN without @@ -1165,7 +1165,7 @@ def str_name(self): # modifications until the result is checked out, which happens after # we check the cache. See issue #1339. tag = ".F" - elif (cli.force == "seccomp"): + elif (cli.force == ch.Force_Mode.SECCOMP): tag = ".S" else: assert False, "unreachable code reached" diff --git a/lib/charliecloud.py b/lib/charliecloud.py index 6cff7303d..4b7626037 100644 --- a/lib/charliecloud.py +++ b/lib/charliecloud.py @@ -44,6 +44,12 @@ class Download_Mode(enum.Enum): ENABLED = "enabled" WRITE_ONLY = "write-only" +# Root emulation mode +class Force_Mode(enum.Enum): + FAKEROOT="fakeroot" + SECCOMP="seccomp" + NONE="none" + ## Constants ## diff --git a/lib/force.py b/lib/force.py index 4cc33bdd9..06b5ceb65 100644 --- a/lib/force.py +++ b/lib/force.py @@ -292,11 +292,11 @@ def new(image_path, force_mode, force_cmds): """Return a new forcer object appropriate for image at image_path in mode force_mode. If no such object can be found, exit with error.""" - if (force_mode is None): + if (force_mode == ch.Force_Mode.NONE): return Nope() - elif (force_mode == "fakeroot"): + elif (force_mode == ch.Force_Mode.FAKEROOT): return Fakeroot(image_path) - elif (force_mode == "seccomp"): + elif (force_mode == ch.Force_Mode.SECCOMP): return Seccomp(force_cmds) else: assert False, "unreachable code reached" diff --git a/test/approved-trailing-whitespace b/test/approved-trailing-whitespace index 7b1dee981..0a9416ada 100644 --- a/test/approved-trailing-whitespace +++ b/test/approved-trailing-whitespace @@ -9,6 +9,6 @@ ./test/build/50_dockerfile.bats:93:RUN echo test4 \ ./test/build/50_dockerfile.bats:96:RUN echo test4 \ ./test/build/50_dockerfile.bats:97:b \ -./test/build/50_dockerfile.bats:131: 4. RUN true -./test/build/50_dockerfile.bats:433:#ENV chse_1a value 1a -./test/build/50_dockerfile.bats:436:#ENV chse_1c=value\ 1c\ +./test/build/50_dockerfile.bats:131: 4. RUN.S true +./test/build/50_dockerfile.bats:434:#ENV chse_1a value 1a +./test/build/50_dockerfile.bats:437:#ENV chse_1c=value\ 1c\ diff --git a/test/build/50_ch-image.bats b/test/build/50_ch-image.bats index e96f3a458..8d86fa552 100644 --- a/test/build/50_ch-image.bats +++ b/test/build/50_ch-image.bats @@ -615,7 +615,7 @@ EOF }, { "created": "2021-11-30T20:40:24Z", - "created_by": "RUN echo \"cwd1: $PWD\"" + "created_by": "RUN.S echo \"cwd1: $PWD\"" }, { "created": "2021-11-30T20:40:24Z", @@ -623,11 +623,11 @@ EOF }, { "created": "2021-11-30T20:40:24Z", - "created_by": "RUN echo \"cwd2: $PWD\"" + "created_by": "RUN.S echo \"cwd2: $PWD\"" }, { "created": "2021-11-30T20:40:24Z", - "created_by": "RUN env | egrep '^(PATH=|ch_)' | sed -E 's/^/env1: /' | sort" + "created_by": "RUN.S env | egrep '^(PATH=|ch_)' | sed -E 's/^/env1: /' | sort" }, { "created": "2021-11-30T20:40:24Z", @@ -635,11 +635,11 @@ EOF }, { "created": "2021-11-30T20:40:24Z", - "created_by": "RUN env | egrep '^(PATH=|ch_)' | sed -E 's/^/env2: /' | sort" + "created_by": "RUN.S env | egrep '^(PATH=|ch_)' | sed -E 's/^/env2: /' | sort" }, { "created": "2021-11-30T20:40:25Z", - "created_by": "RUN echo \"shell1: $0\"" + "created_by": "RUN.S echo \"shell1: $0\"" }, { "created": "2021-11-30T20:40:25Z", @@ -647,7 +647,7 @@ EOF }, { "created": "2021-11-30T20:40:25Z", - "created_by": "RUN echo \"shell2: $0\"" + "created_by": "RUN.S echo \"shell2: $0\"" } ], "labels": { @@ -878,7 +878,7 @@ EOF echo "$output" [[ $status -eq 0 ]] [[ $output = *'1* FROM alpine:3.17'* ]] - [[ $output = *'2. RUN true'* ]] + [[ $output = *'2. RUN.S true'* ]] echo echo '*** Build again: hit' @@ -886,7 +886,7 @@ EOF echo "$output" [[ $status -eq 0 ]] [[ $output = *'1* FROM alpine:3.17'* ]] - [[ $output = *'2* RUN true'* ]] + [[ $output = *'2* RUN.S true'* ]] echo echo '*** Build a 3rd time with the second base image: should now miss' @@ -894,5 +894,5 @@ EOF echo "$output" [[ $status -eq 0 ]] [[ $output = *'1* FROM alpine:3.16'* ]] - [[ $output = *'2. RUN true'* ]] + [[ $output = *'2. RUN.S true'* ]] } diff --git a/test/build/50_dockerfile.bats b/test/build/50_dockerfile.bats index 99fc44e83..23745bd48 100644 --- a/test/build/50_dockerfile.bats +++ b/test/build/50_dockerfile.bats @@ -128,35 +128,36 @@ EOF warning: not yet supported, ignored: issue #777: .dockerignore file 1. FROM alpine:3.17 copying image ... - 4. RUN true - 13. RUN echo test1a + 4. RUN.S true + 13. RUN.S echo test1a test1a - 16. RUN echo test1bc + 16. RUN.S echo test1bc test1bc - 21. RUN echo test2 a + 21. RUN.S echo test2 a test2 a - 24. RUN echo test2 b c + 24. RUN.S echo test2 b c test2 b c - 29. RUN echo test3a + 29. RUN.S echo test3a test3a - 32. RUN echo test3bc + 32. RUN.S echo test3bc test3bc - 37. RUN echo test4 a + 37. RUN.S echo test4 a test4 a - 40. RUN echo test4 b c + 40. RUN.S echo test4 b c test4 b c - 45. RUN echo test5 a + 45. RUN.S echo test5 a test5 a - 48. RUN echo test5 b c + 48. RUN.S echo test5 b c test5 b c - 53. RUN echo test6 a + 53. RUN.S echo test6 a test6 a - 57. RUN echo test6 b + 57. RUN.S echo test6 b test6 b - 63. RUN echo test\ 7a + 63. RUN.S echo test\ 7a test 7a - 66. RUN echo test\ 7\ b + 66. RUN.S echo test\ 7\ b test 7 b +--force=seccomp: modified 0 RUN instructions grown in 16 instructions: tmpimg build slow? consider enabling the new build cache hint: https://hpc.github.io/charliecloud/command-usage.html#build-cache @@ -905,11 +906,11 @@ EOF if [[ $CH_TEST_BUILDER = ch-image ]]; then [[ $output = *"ARG BASEIMG='alpine:3.17'"* ]] [[ $output = *'FROM alpine:3.17 AS a'* ]] - [[ $output = *'RUN true'* ]] + [[ $output = *'RUN.S true'* ]] [[ $output = *'FROM a AS b'* ]] - [[ $output = *'RUN true'* ]] + [[ $output = *'RUN.S true'* ]] [[ $output = *'FROM b'* ]] - [[ $output = *'RUN true'* ]] + [[ $output = *'RUN.S true'* ]] run ch-image list echo "$output" [[ $status -eq 0 ]] diff --git a/test/build/55_cache.bats b/test/build/55_cache.bats index 1490d43d0..f334146c7 100644 --- a/test/build/55_cache.bats +++ b/test/build/55_cache.bats @@ -114,8 +114,8 @@ EOF ch-image build -t a -f bucache/a.df . blessed_out=$(cat << 'EOF' -* (a) RUN echo bar -* RUN echo foo +* (a) RUN.S echo bar +* RUN.S echo foo * (alpine+3.17) PULL alpine:3.17 * (root) ROOT EOF @@ -134,9 +134,9 @@ EOF ch-image build -t b -f bucache/b.df . blessed_out=$(cat << 'EOF' -* (b) RUN echo baz -* (a) RUN echo bar -* RUN echo foo +* (b) RUN.S echo baz +* (a) RUN.S echo bar +* RUN.S echo foo * (alpine+3.17) PULL alpine:3.17 * (root) ROOT EOF @@ -157,11 +157,11 @@ EOF ch-image build -t c -f bucache/c.df . blessed_out=$(cat << 'EOF' -* (c) RUN echo qux -| * (b) RUN echo baz -| * (a) RUN echo bar +* (c) RUN.S echo qux +| * (b) RUN.S echo baz +| * (a) RUN.S echo bar |/ -* RUN echo foo +* RUN.S echo foo * (alpine+3.17) PULL alpine:3.17 * (root) ROOT EOF @@ -177,13 +177,13 @@ EOF # Forcing a rebuild show produce a new pair of FOO and BAR commits from # from the alpine branch. blessed_out=$(cat << 'EOF' -* (a) RUN echo bar -* RUN echo foo -| * (c) RUN echo qux -| | * (b) RUN echo baz -| | * RUN echo bar +* (a) RUN.S echo bar +* RUN.S echo foo +| * (c) RUN.S echo qux +| | * (b) RUN.S echo baz +| | * RUN.S echo bar | |/ -| * RUN echo foo +| * RUN.S echo foo |/ * (alpine+3.17) PULL alpine:3.17 * (root) ROOT @@ -202,14 +202,14 @@ EOF # the rebuild behavior only forces misses on non-FROM instructions, it # should now be based on A's new commits. blessed_out=$(cat << 'EOF' -* (b) RUN echo baz -* (a) RUN echo bar -* RUN echo foo -| * (c) RUN echo qux -| | * RUN echo baz -| | * RUN echo bar +* (b) RUN.S echo baz +* (a) RUN.S echo bar +* RUN.S echo foo +| * (c) RUN.S echo qux +| | * RUN.S echo baz +| | * RUN.S echo bar | |/ -| * RUN echo foo +| * RUN.S echo foo |/ * (alpine+3.17) PULL alpine:3.17 * (root) ROOT @@ -230,17 +230,17 @@ EOF # - No! Rebuild forces misses; since c.df has it’s own FOO it should miss. # --jogas 2/24 blessed_out=$(cat << 'EOF' -* (c) RUN echo qux -* RUN echo foo -| * (b) RUN echo baz -| * (a) RUN echo bar -| * RUN echo foo +* (c) RUN.S echo qux +* RUN.S echo foo +| * (b) RUN.S echo baz +| * (a) RUN.S echo bar +| * RUN.S echo foo |/ -| * RUN echo qux -| | * RUN echo baz -| | * RUN echo bar +| * RUN.S echo qux +| | * RUN.S echo baz +| | * RUN.S echo bar | |/ -| * RUN echo foo +| * RUN.S echo foo |/ * (alpine+3.17) PULL alpine:3.17 * (root) ROOT @@ -264,10 +264,10 @@ EOF ch-image build -t e -f bucache/c.df . blessed_out=$(cat << 'EOF' -* (e) RUN echo qux -| * RUN echo bar +* (e) RUN.S echo qux +| * RUN.S echo bar |/ -* RUN echo foo +* RUN.S echo foo * (alpine+3.17) PULL alpine:3.17 * (root) ROOT EOF @@ -281,10 +281,10 @@ EOF ch-image build -t e -f bucache/a.df . blessed_out=$(cat << 'EOF' -* RUN echo qux -| * (e) RUN echo bar +* RUN.S echo qux +| * (e) RUN.S echo bar |/ -* RUN echo foo +* RUN.S echo foo * (alpine+3.17) PULL alpine:3.17 * (root) ROOT EOF @@ -334,7 +334,7 @@ RUN cat /worldchampion EOF ) tree_ours_before=$(cat <<'EOF' -* (wc) RUN cat /worldchampion +* (wc) RUN.S cat /worldchampion * (localhost+5000%champ) PULL localhost:5000/champ * (root) ROOT EOF @@ -367,7 +367,7 @@ EOF [[ $output = *'manifest list: downloading'* ]] [[ $output != *'manifest: downloading'* ]] [[ $output = *'config: downloading'* ]] - [[ $output = *'. RUN'* ]] + [[ $output = *'. RUN.S'* ]] run ch-image -s "$so" --tls-no-verify build -t wc -f <(echo "$df_ours") /tmp echo "$output" [[ $status -eq 0 ]] @@ -375,7 +375,7 @@ EOF [[ $output != *'manifest list: downloading'* ]] [[ $output != *'manifest: downloading'* ]] [[ $output != *'config: downloading'* ]] - [[ $output = *'* RUN'* ]] + [[ $output = *'* RUN.S'* ]] run ch-image -s "$so" build-cache --tree echo "$output" [[ $status -eq 0 ]] @@ -415,7 +415,7 @@ EOF [[ $output != *'manifest list: downloading'* ]] [[ $output != *'manifest: downloading'* ]] [[ $output != *'config: downloading'* ]] - [[ $output = *'* RUN'* ]] + [[ $output = *'* RUN.S'* ]] run ch-image -s "$so" build-cache --tree echo "$output" [[ $status -eq 0 ]] @@ -437,7 +437,7 @@ EOF [[ $status -eq 0 ]] diff -u - <(echo "$output" | treeonly) <<'EOF' * (localhost+5000%champ) PULL localhost:5000/champ -| * (wc) RUN cat /worldchampion +| * (wc) RUN.S cat /worldchampion | * PULL localhost:5000/champ |/ * (root) ROOT @@ -455,14 +455,14 @@ EOF [[ $output != *'manifest list: downloading'* ]] [[ $output != *'manifest: downloading'* ]] [[ $output != *'config: using existing file'* ]] - [[ $output = *'. RUN'* ]] + [[ $output = *'. RUN.S'* ]] run ch-image -s "$so" build-cache --tree echo "$output" [[ $status -eq 0 ]] diff -u - <(echo "$output" | treeonly) <<'EOF' -* (wc) RUN cat /worldchampion +* (wc) RUN.S cat /worldchampion * (localhost+5000%champ) PULL localhost:5000/champ -| * RUN cat /worldchampion +| * RUN.S cat /worldchampion | * PULL localhost:5000/champ |/ * (root) ROOT @@ -492,8 +492,8 @@ EOF echo "$output" [[ $status -eq 0 ]] blessed_out=$(cat << 'EOF' -* (foo) RUN echo bar -* (foo#) RUN echo foo +* (foo) RUN.S echo bar +* (foo#) RUN.S echo foo * (alpine+3.17) PULL alpine:3.17 * (root) ROOT EOF @@ -507,10 +507,10 @@ EOF echo "$output" [[ $status -eq 0 ]] blessed_out=$(cat << 'EOF' -* (foo) RUN echo qux -| * RUN echo bar +* (foo) RUN.S echo qux +| * RUN.S echo bar |/ -* RUN echo foo +* RUN.S echo foo * (alpine+3.17) PULL alpine:3.17 * (root) ROOT EOF @@ -523,11 +523,11 @@ EOF ch-image build-cache --reset # First build, without --force. - ch-image build -t force -f ./bucache/force.df ./bucache + ch-image build --force=none -t force -f ./bucache/force.df ./bucache # Second build, with --force. This should diverge after the first WORKDIR. sleep 1 - ch-image build --force -t force -f ./bucache/force.df ./bucache + ch-image build --force=seccomp -t force -f ./bucache/force.df ./bucache run ch-image build-cache --tree echo "$output" [[ $status -eq 0 ]] @@ -535,7 +535,7 @@ EOF * (force) WORKDIR /usr * RUN.S dnf install -y ed # doesn’t need --force | * WORKDIR /usr -| * RUN dnf install -y ed # doesn’t need --force +| * RUN.N dnf install -y ed # doesn’t need --force |/ * WORKDIR / * (almalinux+8) PULL almalinux:8 @@ -544,7 +544,7 @@ EOF # Third build, without --force. This should re-use the first build. sleep 1 - ch-image build -t force -f ./bucache/force.df ./bucache + ch-image build --force=none -t force -f ./bucache/force.df ./bucache run ch-image build-cache --tree echo "$output" [[ $status -eq 0 ]] @@ -552,7 +552,7 @@ EOF * WORKDIR /usr * RUN.S dnf install -y ed # doesn’t need --force | * (force) WORKDIR /usr -| * RUN dnf install -y ed # doesn’t need --force +| * RUN.N dnf install -y ed # doesn’t need --force |/ * WORKDIR / * (almalinux+8) PULL almalinux:8 @@ -575,13 +575,13 @@ EOF echo "$output" [[ $status -eq 0 ]] [[ $output = *'* FROM'* ]] - [[ $output = *'. RUN echo foo'* ]] - [[ $output = *'. RUN echo bar'* ]] + [[ $output = *'. RUN.S echo foo'* ]] + [[ $output = *'. RUN.S echo bar'* ]] blessed_out=$(cat << 'EOF' -* (a) RUN echo bar -* RUN echo foo -| * RUN echo bar -| * RUN echo foo +* (a) RUN.S echo bar +* RUN.S echo foo +| * RUN.S echo bar +| * RUN.S echo foo |/ * (alpine+3.17) PULL alpine:3.17 * (root) ROOT @@ -667,7 +667,7 @@ EOF run ch-image build -t ae1 -f ./bucache/argenv.df ./bucache echo "$output" [[ $status -eq 0 ]] - [[ $output = *'5* RUN'* ]] + [[ $output = *'5* RUN.S'* ]] # Re-build, with partial hits. ARG and ENV from first build should pass # through with correct values. @@ -707,17 +707,17 @@ EOF echo "$output" [[ $status -eq 0 ]] blessed_out=$(cat << 'EOF' -* (ae5) RUN echo 1 $argA $argB $envA $envB +* (ae5) RUN.S echo 1 $argA $argB $envA $envB * ENV envB='venvBvargA' * ENV envA='venvA' * ARG argB='bar' -| * (ae4, ae3) RUN echo 1 $argA $argB $envA $envB +| * (ae4, ae3) RUN.S echo 1 $argA $argB $envA $envB | * ENV envB='venvBvargA' | * ENV envA='venvA' | * ARG argB='foo' |/ -| * (ae2) RUN echo 2 $argA $argB $envA $envB -| | * (ae1) RUN echo 1 $argA $argB $envA $envB +| * (ae2) RUN.S echo 2 $argA $argB $envA $envB +| | * (ae1) RUN.S echo 1 $argA $argB $envA $envB | |/ | * ENV envB='venvBvargA' | * ENV envA='venvA' @@ -743,7 +743,7 @@ EOF [[ $output = *'1. FROM'* ]] [[ $output = *'2. ARG'* ]] [[ $output = *'3. ARG'* ]] - [[ $output = *'4. RUN'* ]] + [[ $output = *'4. RUN.S'* ]] [[ $output = *'vargA sockA'* ]] # Re-build. All hits. @@ -753,7 +753,7 @@ EOF [[ $output = *'1* FROM'* ]] [[ $output = *'2* ARG'* ]] [[ $output = *'3* ARG'* ]] - [[ $output = *'4* RUN'* ]] + [[ $output = *'4* RUN.S'* ]] [[ $output != *'vargA sockA'* ]] # Re-build with new value from command line. All hits again. @@ -764,7 +764,7 @@ EOF [[ $output = *'1* FROM'* ]] [[ $output = *'2* ARG'* ]] [[ $output = *'3* ARG'* ]] - [[ $output = *'4* RUN'* ]] + [[ $output = *'4* RUN.S'* ]] [[ $output != *'vargA sockA'* ]] [[ $output != *'vargA sockB'* ]] } @@ -884,11 +884,11 @@ EOF [[ $status -eq 0 ]] [[ $output = *'. FROM'* ]] [[ $output = *'base image only exists non-cached; adding to cache'* ]] - [[ $output = *'. RUN'* ]] + [[ $output = *'. RUN.S'* ]] # Check tree. blessed_out=$(cat << 'EOF' -* (foo) RUN echo foo +* (foo) RUN.S echo foo * (alpine+3.17) IMPORT alpine:3.17 * (root) ROOT EOF @@ -904,8 +904,8 @@ EOF ch-image build-cache --reset blessed_out=$(cat << 'EOF' -* (a2, a) RUN echo bar -* RUN echo foo +* (a2, a) RUN.S echo bar +* RUN.S echo foo * (alpine+3.17) PULL alpine:3.17 * (root) ROOT EOF @@ -942,7 +942,7 @@ EOF printf 'FROM alpine:3.17\n' | ch-image build -t foo - printf 'FROM foo\nRUN echo foo\n' | ch-image build -t alpine:3.17 - blessed_out=$(cat << 'EOF' -* (alpine+3.17) RUN echo foo +* (alpine+3.17) RUN.S echo foo * (foo) PULL alpine:3.17 * (root) ROOT EOF @@ -959,7 +959,7 @@ EOF [[ $output != *'pulled image: adding to build cache'* ]] # C1, C4 [[ $output = *'pulled image: found in build cache'* ]] # C2, C3 blessed_out=$(cat << 'EOF' -* RUN echo foo +* RUN.S echo foo * (foo, alpine+3.17) PULL alpine:3.17 * (root) ROOT EOF @@ -973,7 +973,7 @@ EOF sleep 1 printf 'FROM alpine:3.17\n' | ch-image build -t alpine:3.16 - blessed_out=$(cat << 'EOF' -* RUN echo foo +* RUN.S echo foo * (foo, alpine+3.17, alpine+3.16) PULL alpine:3.17 * (root) ROOT EOF @@ -990,7 +990,7 @@ EOF [[ $output != *'pulled image: found in build cache'* ]] # C2, C3 blessed_out=$(cat << 'EOF' * (alpine+3.16) PULL alpine:3.16 -| * RUN echo foo +| * RUN.S echo foo | * (foo, alpine+3.17) PULL alpine:3.17 |/ * (root) ROOT @@ -1151,7 +1151,7 @@ EOF echo "$output" [[ $status -eq 0 ]] [[ $output = *'* FROM'* ]] - [[ $output = *'* RUN'* ]] + [[ $output = *'* RUN.S'* ]] [[ $output = *"no image found: $CH_IMAGE_STORAGE/img/tmpimg"* ]] [[ $output = *'created worktree'* ]] } @@ -1174,8 +1174,11 @@ EOF umask 0027 # Build it. Every instruction does a quick restore, so this validates that - # works, aside from mtime and atime which are expected to vary. - ch-image build -t tmpimg -f ./bucache/difficult.df . + # works, aside from mtime and atime which are expected to vary. Note that + # “--force=none” is necessary because the dockerfile includes a call to + # mkfifo(1), which uses the system call mknod(2), which is intercepted by + # our seccomp(2) filter (see also: #1646). + ch-image build --force=none -t tmpimg -f ./bucache/difficult.df . stat "$CH_IMAGE_STORAGE"/img/tmpimg/test/fifo_ stat1=$(statwalk) diff -u - <(echo "$stat1" | sed -E 's/([am])=[0-9T:.-]+/\1=:::/g') <<'EOF' @@ -1226,10 +1229,10 @@ EOF # including timestamps. ch-image delete tmpimg [[ ! -e $CH_IMAGE_STORAGE/img/tmpimg ]] - run ch-image build -t tmpimg -f ./bucache/difficult.df . + run ch-image build --force=none -t tmpimg -f ./bucache/difficult.df . echo "$output" [[ $status -eq 0 ]] - [[ $output = *'* RUN echo last'* ]] + [[ $output = *'* RUN.N echo last'* ]] statwalk | diff -u <(echo "$stat1") - } diff --git a/test/force-auto.py.in b/test/force-auto.py.in index 8120aaa97..f74600b4c 100644 --- a/test/force-auto.py.in +++ b/test/force-auto.py.in @@ -145,7 +145,7 @@ echo "$output" [[ $status -eq 0 ]] {self.build1_post_hook}""" # force - force = "--force=%s" % (self.force) if self.force else "" + force = "--force=%s" % (self.force) if self.force else "--force=none" # run command we’re testing try: run = self.runs[self.run]