Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/releases/pending/4793.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description: |
The generated Containerfile for bootc image-mode builds is now
transferred via ``push()`` instead of being inlined on the SSH
command line, preventing ``ARG_MAX`` overflow when plan environment
contains large values.
1 change: 1 addition & 0 deletions tests/prepare/large-env/data/.fmf/version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
17 changes: 17 additions & 0 deletions tests/prepare/large-env/data/plans.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
discover:
how: shell
tests:
- name: /smoke
test: /usr/bin/true

provision:
how: virtual

prepare:
- how: shell
script: echo "prepare phase 1"
- how: shell
script: echo "prepare phase 2"

execute:
how: tmt
15 changes: 15 additions & 0 deletions tests/prepare/large-env/main.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
summary: Test prepare with large plan environment
description: |
Verify that prepare succeeds when plan environment contains large
values (e.g. base64-encoded secrets). Without the push() fix,
the generated Containerfile was passed as a cat heredoc SSH
command-line argument, exceeding the OS ARG_MAX limit.
link:
- relates: https://github.com/teemtee/tmt/issues/4518
tag+:
- provision-only
- provision-virtual
- image-mode
require:
- tmt+provision-virtual
duration: 2h
59 changes: 59 additions & 0 deletions tests/prepare/large-env/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/bash
. /usr/share/beakerlib/beakerlib.sh || exit 1
. ../../images.sh || exit 1

rlJournalStart
rlPhaseStartSetup
rlRun "PROVISION_HOW=${PROVISION_HOW:-virtual}"
rlRun "IMAGE_MODE=${IMAGE_MODE:-yes}"

# TODO: Enable IMAGES for container and virtual provision methods
# once the large-env fix is verified for those backends.
if [ "$IMAGE_MODE" = "yes" ]; then
rlRun "IMAGES='$TEST_IMAGE_MODE_IMAGES'"

elif [ "$PROVISION_HOW" = "container" ]; then
rlRun "IMAGES="

elif [ "$PROVISION_HOW" = "virtual" ]; then
rlRun "IMAGES="

else
rlRun "IMAGES="
fi
Comment thread
psss marked this conversation as resolved.

rlRun "run=\$(mktemp -d -p /var/tmp)" 0 "Create run directory"
rlRun "pushd data"

# Size the env dynamically so 2 shell prepare steps produce a
# Containerfile that exceeds MAX_ARG_STRLEN (PAGE_SIZE * 32).
# Each RUN inlines the full export; raw_bytes * 4/3 (base64) ≈
# half the limit, so two RUNs combined just exceed it.
rlRun "max_arg_strlen=\$(( \$(getconf PAGE_SIZE) * 32 ))"
rlRun "raw_bytes=\$(( max_arg_strlen * 5 / 12 ))"
rlRun "rlLogInfo \"MAX_ARG_STRLEN: \$max_arg_strlen raw_bytes: \$raw_bytes\""
rlRun "python3 -c 'import base64; print(\"LARGE_SECRET: \" + base64.b64encode(b\"x\" * '\$raw_bytes').decode())' > large-env.yaml"
rlRun "rlLogInfo \"LARGE_SECRET size: \$(wc -c < large-env.yaml) bytes\""

rlRun "export TMT_BOOT_TIMEOUT=300"
rlRun "export TMT_CONNECT_TIMEOUT=300"
rlPhaseEnd

while IFS= read -r image; do
phase_prefix="$(test_phase_prefix "$image")"

rlPhaseStartTest "$phase_prefix Prepare/install with large environment"
rlRun -s "tmt -vvv run --all --scratch -i \$run --environment-file large-env.yaml provision --how $PROVISION_HOW --image $image"

rlAssertGrep "building container image" $rlRun_LOG
rlAssertGrep "switching to new image" $rlRun_LOG
rlAssertGrep "rebooting to apply new image" $rlRun_LOG
rlPhaseEnd
done <<< "$IMAGES"

rlPhaseStartCleanup
rlRun "rm -f large-env.yaml" 0 "Remove generated env file"
rlRun "popd"
rlRun "rm -rf \$run" 0 "Remove run directory"
rlPhaseEnd
rlJournalEnd
14 changes: 12 additions & 2 deletions tmt/package_managers/bootc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import tmt.utils
from tmt.container import PYDANTIC_V1, ConfigDict, MetadataContainer
from tmt.guest import TransferOptions
from tmt.package_managers import (
Installable,
Options,
Expand Down Expand Up @@ -258,8 +259,17 @@ def build_container(self) -> Optional[CommandOutput]:
')"'
)
)
self.guest.execute(
ShellScript(f'cat <<EOF > {containerfile_path!s} \n{containerfile} \nEOF')
# Write Containerfile via push() to avoid exceeding
# the OS ARG_MAX limit on the SSH command line.
local_containerfile = self.guest.guest_workdir / 'Containerfile'
local_containerfile.write_text(containerfile)
self.guest.push(
source=local_containerfile,
destination=containerfile_path,
options=TransferOptions(
recursive=False,
compress=True,
),
)

self.debug(f"containerfile content: {containerfile}")
Expand Down
Loading