Skip to content

Commit 21a802d

Browse files
leliaclaude
andcommitted
Install the pinned reachability engine in the Docker image
The image installed @coana-tech/cli unpinned while the launcher asks npx for DEFAULT_COANA_CLI_VERSION. npx reuses the global install only when the versions match, so once the two diverged every scan re-downloaded the engine (~119 MB) and the launcher took roughly three times as long, silently. The Dockerfile now reads the pinned version out of reachability.py, so the image and the runtime cannot drift and the pin stays bumped in exactly one place. Splitting the coana install out of the toolchain RUN keeps a pin bump to a 353 MB layer rather than rebuilding the 2.3 GB combined layer. Marking a release stable now builds that version from its own tag, so an older wheel is no longer paired with the default branch's build recipe and pin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 6076acf commit 21a802d

8 files changed

Lines changed: 92 additions & 6 deletions

File tree

.github/workflows/docker-stable.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ jobs:
1515
steps:
1616
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
1717
with:
18+
# Build the requested version from its own tag. The image installs
19+
# socketsecurity==inputs.version from PyPI, and the Dockerfile reads the pinned
20+
# @coana-tech/cli version out of the checked-out source, so building from the
21+
# default branch would pair an old wheel with a newer build recipe and pin.
22+
ref: v${{ inputs.version }}
1823
persist-credentials: false
1924

2025
- name: Check if version exists in PyPI

.github/workflows/python-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ on:
1212
- "tests/core/**/*.py"
1313
- "pyproject.toml"
1414
- "uv.lock"
15+
- "Dockerfile"
1516
- ".github/workflows/python-tests.yml"
1617
pull_request:
1718
paths:
@@ -20,6 +21,7 @@ on:
2021
- "tests/core/**/*.py"
2122
- "pyproject.toml"
2223
- "uv.lock"
24+
- "Dockerfile"
2325
- ".github/workflows/python-tests.yml"
2426
workflow_dispatch:
2527

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## 2.7.2
4+
5+
### Fixed: the image now installs the pinned reachability engine
6+
7+
- The Docker image installed `@coana-tech/cli` unpinned while the CLI asks npx for the
8+
version in `DEFAULT_COANA_CLI_VERSION`. npx reuses the image's global install only when
9+
the versions match, so once they diverged every scan downloaded the engine again. The
10+
Dockerfile now reads the pinned version out of the source, keeping the image and the
11+
runtime aligned without a second place to bump.
12+
- Marking a release stable now builds that version from its own tag instead of the default
13+
branch, so an older release is rebuilt with its own pin and build recipe.
14+
315
## 2.7.1
416

517
### Changed: bump pinned @coana-tech/cli to 15.10.36

Dockerfile

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,30 @@ RUN if [ "$TARGETARCH" = "amd64" ]; then \
7474
pypy3 --version; \
7575
fi
7676

77-
# Install additional tools
78-
RUN npm install @coana-tech/cli socket -g && \
79-
gem install bundler && \
77+
# Install Ruby bundler and the Rust toolchain
78+
RUN gem install bundler && \
8079
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
8180
. ~/.cargo/env && \
8281
rustup component add rustfmt clippy
8382

83+
# Install the reachability engine at exactly the version the CLI will ask for.
84+
# The launcher runs `npx @coana-tech/cli@<DEFAULT_COANA_CLI_VERSION>`, and npx reuses this
85+
# global install only when the versions match; on a mismatch it downloads the engine again
86+
# on every scan. Reading the version out of reachability.py keeps the image and the runtime
87+
# aligned by construction, so the pin stays bumped in exactly one place.
88+
# reachability.py is copied on its own so bumping the pin rebuilds only this layer rather
89+
# than the toolchain layer above it.
90+
COPY socketsecurity/core/tools/reachability.py /tmp/coana-pin/reachability.py
91+
RUN COANA_CLI_VERSION="$(sed -n 's/^DEFAULT_COANA_CLI_VERSION[^"]*"\([^"]*\)".*/\1/p' \
92+
/tmp/coana-pin/reachability.py)" && \
93+
if [ -z "$COANA_CLI_VERSION" ]; then \
94+
echo "Could not read DEFAULT_COANA_CLI_VERSION from reachability.py" >&2; \
95+
exit 1; \
96+
fi && \
97+
echo "Installing @coana-tech/cli@${COANA_CLI_VERSION} (pinned by reachability.py)" && \
98+
npm install "@coana-tech/cli@${COANA_CLI_VERSION}" socket -g && \
99+
rm -rf /tmp/coana-pin
100+
84101
# Set environment paths
85102
ENV PATH="/usr/local/go/bin:/usr/lib/go/bin:/root/.cargo/bin:${PATH}"
86103
ENV GOPATH="/go"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.7.1"
9+
version = "2.7.2"
1010
requires-python = ">= 3.11"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

socketsecurity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.7.1'
2+
__version__ = '2.7.2'
33
USER_AGENT = f'SocketPythonCLI/{__version__}'
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Guards the image's ``@coana-tech/cli`` install against drifting from the runtime pin.
2+
3+
The launcher asks npx for ``@coana-tech/cli@DEFAULT_COANA_CLI_VERSION``, and npx reuses the
4+
image's global install only when the versions match; on a mismatch it downloads the engine
5+
again on every scan. The Dockerfile therefore derives the version from ``reachability.py``
6+
with a ``sed`` expression instead of repeating it. These tests run that expression against
7+
the real source, so a reformatted constant or a broken expression fails here rather than
8+
silently producing a mismatched image.
9+
"""
10+
11+
import re
12+
import subprocess
13+
from pathlib import Path
14+
15+
from socketsecurity.core.tools.reachability import DEFAULT_COANA_CLI_VERSION
16+
17+
REPO_ROOT = Path(__file__).resolve().parents[2]
18+
DOCKERFILE = REPO_ROOT / "Dockerfile"
19+
REACHABILITY_SOURCE = (
20+
REPO_ROOT / "socketsecurity" / "core" / "tools" / "reachability.py"
21+
)
22+
23+
24+
def _dockerfile_sed_expression() -> str:
25+
"""Return the pin-extraction expression the Dockerfile runs."""
26+
match = re.search(
27+
r"sed -n '(s/\^DEFAULT_COANA_CLI_VERSION[^']*)'", DOCKERFILE.read_text()
28+
)
29+
assert match, "Dockerfile no longer extracts DEFAULT_COANA_CLI_VERSION with sed"
30+
return match.group(1)
31+
32+
33+
def test_dockerfile_expression_extracts_the_pinned_version():
34+
extracted = subprocess.run(
35+
["sed", "-n", _dockerfile_sed_expression(), str(REACHABILITY_SOURCE)],
36+
capture_output=True,
37+
text=True,
38+
check=True,
39+
).stdout.strip()
40+
assert extracted == DEFAULT_COANA_CLI_VERSION
41+
42+
43+
def test_dockerfile_never_installs_coana_unpinned():
44+
install_lines = [
45+
line for line in DOCKERFILE.read_text().splitlines() if "npm install" in line
46+
]
47+
assert install_lines, "Dockerfile no longer installs anything with npm"
48+
for line in install_lines:
49+
if "@coana-tech/cli" in line:
50+
assert "@coana-tech/cli@" in line, f"unpinned coana install: {line.strip()}"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)