macOS: make docker bind mounts asymmetric so macOS hosts inject correctly - #3947
macOS: make docker bind mounts asymmetric so macOS hosts inject correctly#3947moflwi wants to merge 4 commits into
Conversation
/usr is on the SIP-sealed system volume on modern macOS, but the codebase hardcoded /usr/share/opentelemetry_shell and /usr/bin/... as absolute paths for cross-file sourcing. Make the bootstrap (agent.sh, api.sh), curl/wget/ netcat instrumentation, the CI/GH-Action installers, and the test suite's package entrypoint resolve to /usr/local/... on Darwin instead, leaving Linux behavior untouched. Also: drop the dead macos-13 runner, use `ps` instead of the unreliable $SHELL fallback for shell detection, fix the Homebrew formula's post_install chicken-and-egg bug and deprecated python@3.9 dependency, template the formula's version/sha256 in build-brew, and restore the perl fallback that was dropped when the timeout fallback was added.
Extracting the whole tarball with --strip-components=2 into /usr/local also stripped the opt/ prefix, landing opt/opentelemetry_shell/requirements.txt at /usr/local/opentelemetry_shell/requirements.txt. The subsequent `pip3 install -r /opt/opentelemetry_shell/requirements.txt` would then fail, and api.sh looks for the SDK venv at /opt/opentelemetry_shell/venv too. /opt is not SIP-sealed (Homebrew itself lives at /opt/homebrew), so only the usr subtree needs relocating. Extract the two subtrees separately and create the venv, matching what the debian postinst does.
find -executable is a GNU extension. BSD find on macOS rejects it with "unknown primary or operator", and _otel_list_path_executables redirects stderr to /dev/null and swallows the exit code with `|| true`, so the failure was silent: the function returned nothing, _otel_list_path_commands returned nothing, and the auto-instrumentation loop had no commands to instrument. macOS would have appeared to run while instrumenting nothing. Select the predicate once at load time, using -perm +111 on Darwin, the same conditional-definition pattern already used elsewhere in the codebase. Linux keeps -executable and is unaffected.
The docker instrumentation mounts the host's opentelemetry-shell install into the container using identical source and target paths. That assumption breaks on macOS: /usr is SIP-sealed, so the package installs to /usr/local/share/ opentelemetry_shell and /usr/local/bin/otel*.sh instead. Mirroring the host path into the container would both mount from a non-existent source and place the files where the container does not look for them. Containers are always Linux, even when the docker host is a Mac, so source and target have to be decoupled: - source (host side) is now OS aware: $_otel_shell_home for the share directory, and the new $_otel_shell_bin_home (/usr/local/bin on Darwin, /usr/bin elsewhere) for the launcher scripts. - target (container side) always stays on the Linux convention: /usr/share/opentelemetry_shell and /usr/bin/<basename>. /opt/opentelemetry_shell is unchanged, it lives at the same path on both platforms. Also add _otel_find_executables to api.sh, because find -executable is a GNU extension that BSD find (macOS) does not have; there the equivalent is -perm +111. It follows the same conditional definition pattern already used for _otel_escape_arg_format. On Linux $_otel_shell_home and $_otel_shell_bin_home resolve to the previous hardcoded paths and the find invocation is unchanged, so the emitted docker arguments and compose volumes are identical to before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Hello, thanks for contributing for the first time! |
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Aligns host vs container path expectations so macOS installs (which use /usr/local/...) can still inject into Linux containers (which expect /usr/share/... + /usr/bin/...), and updates tests/CI to support the new layout.
Changes:
- Introduces host-OS-aware install roots (
_otel_shell_home,_otel_shell_bin_home) and uses them throughout runtime/agent scripts. - Makes Docker/Compose bind mounts asymmetric (host source via resolved paths, Linux target paths fixed).
- Updates tests and GitHub Actions/Homebrew packaging paths for macOS compatibility.
Reviewed changes
Copilot reviewed 72 out of 72 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_unit_escape_arg.sh | Allow overriding API entrypoint path in tests via env var |
| tests/unit/test_unit_busybox_parses_otelapi.sh | Allow overriding API entrypoint path for busybox syntax check |
| tests/sdk/test_sdk_traceparent.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_span_event.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_span_error.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_span_attribute.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_span.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_resource_attributes.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_observe_whitespaces.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_observe_stdout.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_observe_stderr.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_observe_pipes_empty.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_observe_pipes_detached_stdout.bash | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_observe_pipes.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_observe_log.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_observe_failed.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_observe_exit.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_observe.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_metric.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_sdk_init_shutdown.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/test_log_sideeffects.sh | Make API entrypoint path configurable in SDK tests |
| tests/sdk/DISABLED_test_sdk_observe_pipes_detached_stdin.sh | Make API entrypoint path configurable in disabled SDK test |
| tests/run_tests.sh | Set macOS-specific test entrypoint env vars; add local fallbacks for missing tools |
| tests/performance/test_performance_script.sh | Use configurable entrypoint for performance tests |
| tests/performance/test_performance_full.sh | Use configurable entrypoint for performance tests |
| tests/integration/test_integration_debconf_second.sh | Use configurable entrypoint for integration test |
| tests/integration/test_integration_debconf_first.sh | Use configurable entrypoint for integration test |
| tests/integration/test_integration_debconf_arguments.bash | Use configurable entrypoint for integration test |
| tests/integration/test_integration__which__IFS.sh | Use configurable entrypoint for integration test |
| tests/integration/test_integration__renovate.sh | Use configurable entrypoint for integration test |
| tests/integration/test_integration__philbot_stop__multi_tier_injection.sh | Use configurable entrypoint for integration test |
| tests/integration/test_integration__bash_ai__shebang_vs_alias_injection.sh | Use configurable entrypoint for integration test |
| tests/auto/wget2.sh | Use configurable entrypoint for auto wrapper |
| tests/auto/wget.sh | Use configurable entrypoint for auto wrapper |
| tests/auto/test_auto_traceparent.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_minimal_instrumentation.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_lifecycle.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_instrument.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_xargs.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_whitespaces.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_timeout.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_time.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_sudo_whitespaces.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_sudo.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_stdin.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_shebang.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_parallel.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_flock.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_find.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_docker.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection_c.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_injection.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_init_in_if.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_http_injection_cleanup.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_alias.sh | Use configurable entrypoint for auto tests |
| tests/auto/test_auto_[.sh | Use configurable entrypoint for auto tests |
| tests/auto/exec.sh | Use configurable entrypoint for auto tests |
| tests/auto/curl_subshell.sh | Use configurable entrypoint for auto wrapper |
| tests/auto/curl.sh | Use configurable entrypoint for auto wrapper |
| tests/auto/DISABLED_test_auto_injection_with_script.bash | Use configurable entrypoint for disabled auto test |
| tests/auto/DISABLED_test_auto_injection_with_path.bash | Use configurable entrypoint for disabled auto test |
| src/usr/share/opentelemetry_shell/api.sh | Add Darwin-aware install roots; fix no-/proc shell detection; make find portable; use resolved home for includes/SDK |
| src/usr/share/opentelemetry_shell/agent.sh | Source correct api.sh on Darwin; use resolved home for instrumentation files; make find portable; adjust self-detection |
| src/usr/share/opentelemetry_shell/agent.instrumentation.wget.sh | Use resolved home for lib paths; add otool dependency check on Darwin |
| src/usr/share/opentelemetry_shell/agent.instrumentation.github.aw.docker.sh | Make docker-compose mounts asymmetric for macOS hosts; use resolved bin/home and fixed Linux targets |
| src/usr/share/opentelemetry_shell/agent.instrumentation.docker.sh | Make docker --mount sources host-aware while targets remain Linux paths |
| src/usr/share/opentelemetry_shell/agent.instrumentation.curl.sh | Use resolved home for lib paths; add otool dependency check on Darwin |
| src/usr/share/opentelemetry_shell/agent.injection.netcat.sh | Use resolved home for injected instrumentation code path |
| meta/homebrew/opentelemetry-shell.rb | Update Python dependency and venv creation; document macOS path caveat |
| actions/instrument/shared/install.sh | Install tarball on macOS into /usr/local + /opt split (SIP considerations) |
| .github/workflows/test_shell.yml | Update macOS extraction layout for SIP; remove macos-13 from matrix |
| .github/workflows/build.yml | Generate a ready-to-use Homebrew formula artifact (version + sha256 substitution) |
Suppressed comments (1)
tests/run_tests.sh:1
function name()is not POSIX and will fail on some shells (notablydash). Sincerun_tests.shappears intended to run across different shells/distros, use POSIX function syntax (perl() { ...; }) to avoid syntax errors.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| sudo tar --strip-components=2 -xzf "$tarball_file" -C /usr/local usr | ||
| sudo tar --strip-components=1 -xzf "$tarball_file" -C / opt | ||
| sudo python3 -m venv /opt/opentelemetry_shell/venv | ||
| sudo /opt/opentelemetry_shell/venv/bin/pip3 install -r /opt/opentelemetry_shell/requirements.txt |
| sudo mkdir -p /usr/local/bin /usr/local/share | ||
| sudo tar --strip-components=2 -xzf opentelemetry-shell_*.tar.gz -C /usr/local usr | ||
| sudo tar --strip-components=1 -xzf opentelemetry-shell_*.tar.gz -C / opt |
| if \[ -d /proc ] && \[ "$(\readlink -f "$hint")" = "$(\readlink -f "/proc/$$/exe")" ]; then local _otel_hint_is_self=TRUE; else local _otel_hint_is_self=FALSE; fi | ||
| { \[ -f "$hint" ] && \[ "$_otel_hint_is_self" = FALSE ] && \[ "$(\readlink -f "$hint")" != "$_otel_shell_home"/agent.sh ] && \cat "$hint" || \echo "$hint"; } | \tr -s ' $=";(){}/\\!#~^'\' '\n' | _otel_filter_by_validity | \sort -u |
| if \[ "$(\uname -s)" = Darwin ]; then | ||
| _otel_find_executables() { | ||
| \find "$1" -perm +111 -iname "$2" | ||
| } | ||
| else |
| if \[ "$(\uname -s)" = Darwin ]; then | ||
| _otel_find_type_executable() { \find "$1" -maxdepth 1 -type "$2" -perm +111 2> /dev/null || \true; } | ||
| else | ||
| _otel_find_type_executable() { \find "$1" -maxdepth 1 -type "$2" -executable 2> /dev/null || \true; } | ||
| fi |
| \echo -n ' '; _otel_escape_args --mount type=bind,source=/usr/share/opentelemetry_shell,target=/usr/share/opentelemetry_shell | ||
| # the container is always linux, so sources follow the host layout while targets stay on the linux paths the container expects | ||
| { _otel_find_executables "$_otel_shell_bin_home" 'otel*.sh'; _otel_find_executables "$_otel_shell_bin_home" 'opentelemetry_shell*.sh'; } | while read -r file; do \echo -n ' '; _otel_escape_args --mount type=bind,source="$file",target=/usr/bin/"${file##*/}",readonly; done | ||
| \echo -n ' '; _otel_escape_args --mount type=bind,source="$_otel_shell_home",target=/usr/share/opentelemetry_shell |
plengauer
left a comment
There was a problem hiding this comment.
as discussed:
*) lets rebase
*) lets not make sure they are not overlapping
*) i suggest finding a new solve for the dynamic path resolution everywhere - like rewriting paths when buillding the tarball
Summary
`agent.instrumentation.docker.sh` and `agent.instrumentation.github.aw.docker.sh` bind-mount the host's otel install into containers with identical source and target paths (`source=/usr/share/opentelemetry_shell, target=/usr/share/opentelemetry_shell`). Containers are always Linux (even under Docker Desktop on a Mac), so the target must stay the Linux convention path regardless of host OS — but on a Darwin host the source is now `/usr/local/share/opentelemetry_shell` (per #3942). These need to be asymmetric: source resolved via `$_otel_shell_home`/`$_otel_shell_bin_home` (host-OS-aware), target always the literal Linux path.
Depends on #3942 (base branch).
Test plan
🤖 Generated with Claude Code