Repository navigation
Keep the Docker image's toolchain layers stable across releases, and slim them #439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # The image needs only the package source, its build metadata and the entrypoint | ||
| # script. Keep dist/: Dockerfile.preview copies the preview wheel from it. | ||
| .git | ||
| .github | ||
| .context | ||
| .venv | ||
| .venv-test | ||
| venv | ||
| **/__pycache__ | ||
| **/*.pyc | ||
| .pytest_cache | ||
| .coverage* | ||
| htmlcov | ||
| tests | ||
| benchmarks | ||
| docs | ||
| examples | ||
| instructions | ||
| workflows | ||
| session.md | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| name: E2E Image | ||
|
|
||
| # Runs reachability inside the Docker image built from this commit, so a missing | ||
| # or broken toolchain fails here rather than in a user's pipeline. e2e-test.yml | ||
| # covers reachability run directly on the runner. | ||
| on: | ||
| pull_request: | ||
| paths: | ||
| - "Dockerfile" | ||
| - ".dockerignore" | ||
| - "scripts/docker-entrypoint.sh" | ||
| - "socketsecurity/core/tools/**" | ||
| - "tests/e2e/**" | ||
| - ".github/workflows/e2e-image.yml" | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - "Dockerfile" | ||
| - ".dockerignore" | ||
| - "scripts/docker-entrypoint.sh" | ||
| - "socketsecurity/core/tools/**" | ||
| - "tests/e2e/**" | ||
| - ".github/workflows/e2e-image.yml" | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| reachability: | ||
| # Needs SOCKET_CLI_API_TOKEN, which fork and Dependabot PRs don't get. | ||
| if: >- | ||
| (github.event_name != 'pull_request' || | ||
| github.event.pull_request.head.repo.full_name == github.repository) && | ||
| github.event.pull_request.user.login != 'dependabot[bot]' | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 45 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| fixture: [simple-npm, simple-cargo] | ||
|
Comment on lines
+40
to
+41
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [agent] Each matrix entry builds the ~3.4 GB image separately and pulls the cached layers from Docker Hub on its own, so every run builds the image twice. That build is by far the most expensive step here. One job could build once and loop over both fixtures, recording each fixture's result so one failure doesn't hide the other. The trade-off is losing a separate check name per fixture. |
||
| name: e2e-image-reachability (${{ matrix.fixture }}) | ||
| env: | ||
| IMAGE: socket-python-cli:e2e | ||
| FIXTURE: tests/e2e/fixtures/${{ matrix.fixture }} | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 | ||
|
|
||
| - name: Build the image | ||
| uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 | ||
| with: | ||
| context: . | ||
| load: true | ||
| tags: ${{ env.IMAGE }} | ||
| build-args: | | ||
| USE_LOCAL_INSTALL=true | ||
| # Read-only: reuse the release build's toolchain layers without | ||
| # writing this commit's layers back. | ||
| cache-from: type=registry,ref=socketdev/cli:buildcache | ||
|
|
||
| - name: Run reachability in the image | ||
| id: run-cli | ||
| env: | ||
| SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_CLI_API_TOKEN }} | ||
| run: | | ||
| set -o pipefail | ||
| # The checkout belongs to the runner user and the container runs as | ||
| # root, so git needs the mount marked safe to read repository details. | ||
| # The named volume keeps the downloaded reachability engine across | ||
| # retries. | ||
| in_image() { | ||
| docker run --rm -v "$PWD:/repo" -w /repo \ | ||
| -v socket-cache:/root/.cache/socket \ | ||
| -e SOCKET_SECURITY_API_KEY \ | ||
| -e GIT_CONFIG_COUNT=1 -e GIT_CONFIG_KEY_0=safe.directory -e GIT_CONFIG_VALUE_0=/repo \ | ||
| "$@" | ||
| } | ||
| # Same retry policy as e2e-test.yml's reachability job: the backend | ||
| # intermittently returns nothing to analyze while the CLI reports | ||
| # success, so retry while the facts file has no alerted components | ||
| # and let validation classify a result that stays empty. | ||
| max_attempts=3 | ||
| attempt=1 | ||
| while :; do | ||
| in_image "$IMAGE" --target-path "$FIXTURE" --reach --disable-blocking --enable-debug 2>&1 | tee /tmp/e2e-output.log | ||
| if in_image --entrypoint python "$IMAGE" tests/e2e/validate-image-reachability.py "$FIXTURE" --probe; then | ||
| break | ||
| fi | ||
| if [ "$attempt" -ge "$max_attempts" ]; then | ||
| echo "::warning title=e2e-image incomplete results::no alerted components after ${max_attempts} attempts; letting validation classify the result" | ||
| echo "diagnostics=true" >> "$GITHUB_OUTPUT" | ||
| break | ||
| fi | ||
| echo "::warning title=e2e-image transient retry::attempt ${attempt} produced no alerted components (suspected backend transient); retrying" | ||
| attempt=$((attempt+1)) | ||
| sleep 30 | ||
| done | ||
|
Comment on lines
+87
to
+102
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [agent] This retry loop is a copy of the one in |
||
|
|
||
| - name: Validate results | ||
| run: >- | ||
| docker run --rm -v "$PWD:/repo" -v /tmp/e2e-output.log:/tmp/e2e-output.log:ro -w /repo | ||
| --entrypoint python "$IMAGE" | ||
| tests/e2e/validate-image-reachability.py "$FIXTURE" --log /tmp/e2e-output.log | ||
|
|
||
| - name: Upload diagnostics | ||
| if: failure() || steps.run-cli.outputs.diagnostics == 'true' | ||
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | ||
| with: | ||
| name: e2e-image-${{ matrix.fixture }}-diagnostics-attempt${{ github.run_attempt }} | ||
| path: | | ||
| /tmp/e2e-output.log | ||
| ${{ env.FIXTURE }}/.socket.facts.json | ||
| if-no-files-found: ignore | ||
| include-hidden-files: true | ||
| retention-days: 14 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,20 +11,13 @@ ARG GO_VERSION=system | |
| ARG JAVA_VERSION=17 | ||
| ARG DOTNET_VERSION=8 | ||
|
|
||
| # CLI and SDK arguments | ||
| ARG CLI_VERSION | ||
| ARG SDK_VERSION | ||
| ARG PIP_INDEX_URL=https://pypi.org/simple | ||
| ARG PIP_EXTRA_INDEX_URL=https://pypi.org/simple | ||
| ARG USE_LOCAL_INSTALL=false | ||
|
|
||
| # Install base packages first | ||
| # ca-certificates must be 20261020-r0 or newer, which drops the roots the | ||
| # compromised eMudhra intermediates chain to. The base image ships an older | ||
| # one, and `apk add` does not upgrade installed packages, so upgrade it here. | ||
| RUN apk update && apk add --no-cache \ | ||
| RUN apk add --no-cache \ | ||
| git curl wget \ | ||
| ruby ruby-dev build-base strace && \ | ||
| ruby ruby-dev build-base && \ | ||
| apk upgrade --no-cache ca-certificates && \ | ||
| [ "$(apk version -t "$(apk list -I ca-certificates | awk '{print $1}' | sed 's/^ca-certificates-//')" 20261020-r0)" != "<" ] | ||
|
|
||
|
|
@@ -104,11 +97,14 @@ RUN if [ "$TARGETARCH" = "amd64" ]; then \ | |
| fi | ||
|
|
||
| # Install additional tools | ||
| # Reachability only runs cargo, so install the minimal Rust profile (rustc, | ||
| # cargo, rust-std). The default profile adds about 900 MB of offline HTML docs. | ||
| RUN npm install socket -g && \ | ||
| npm cache clean --force && \ | ||
| gem install bundler && \ | ||
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \ | ||
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal && \ | ||
| . ~/.cargo/env && \ | ||
| rustup component add rustfmt clippy | ||
| cargo --version | ||
|
|
||
| # Set environment paths | ||
| ENV PATH="/usr/local/go/bin:/usr/lib/go/bin:/root/.cargo/bin:${PATH}" | ||
|
|
@@ -148,18 +144,29 @@ COPY pyproject.toml uv.lock /tmp/socket-cli-lock/ | |
| # at TestPyPI don't leak into the locked dependency install via pip's env vars. | ||
| RUN uv export --directory /tmp/socket-cli-lock --frozen --no-dev --no-emit-project \ | ||
| --format requirements-txt -o /tmp/socket-cli-lock/requirements.txt && \ | ||
| pip install --require-hashes --no-deps \ | ||
| pip install --no-cache-dir --require-hashes --no-deps \ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [agent] |
||
| --index-url https://pypi.org/simple --extra-index-url https://pypi.org/simple \ | ||
| -r /tmp/socket-cli-lock/requirements.txt | ||
|
|
||
| # CLI and SDK arguments | ||
| # Declared here rather than at the top: every RUN after an ARG is cache-keyed on | ||
| # its value, and CLI_VERSION changes on every release. Declared earlier, it | ||
| # would rebuild every toolchain layer above, so each release would be a full | ||
| # re-pull for anyone upgrading. | ||
|
Comment on lines
+152
to
+155
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [agent] Minor: this comment and the cache comment in |
||
| ARG CLI_VERSION | ||
| ARG SDK_VERSION | ||
| ARG PIP_INDEX_URL=https://pypi.org/simple | ||
| ARG PIP_EXTRA_INDEX_URL=https://pypi.org/simple | ||
| ARG USE_LOCAL_INSTALL=false | ||
|
|
||
| # Install CLI based on build mode | ||
| RUN if [ "$USE_LOCAL_INSTALL" = "true" ]; then \ | ||
| echo "Using local development install"; \ | ||
| else \ | ||
| cli_installed=false; \ | ||
| for i in $(seq 1 10); do \ | ||
| echo "Attempt $i/10: Installing socketsecurity==$CLI_VERSION"; \ | ||
| if pip install --no-deps --index-url ${PIP_INDEX_URL} --extra-index-url ${PIP_EXTRA_INDEX_URL} socketsecurity==$CLI_VERSION; then \ | ||
| if pip install --no-cache-dir --no-deps --index-url ${PIP_INDEX_URL} --extra-index-url ${PIP_EXTRA_INDEX_URL} socketsecurity==$CLI_VERSION; then \ | ||
| cli_installed=true; \ | ||
| break; \ | ||
| fi; \ | ||
|
|
@@ -174,15 +181,15 @@ RUN if [ "$USE_LOCAL_INSTALL" = "true" ]; then \ | |
| fi; \ | ||
| pip check || exit 1; \ | ||
| if [ ! -z "$SDK_VERSION" ]; then \ | ||
| pip install --index-url ${PIP_INDEX_URL} --extra-index-url ${PIP_EXTRA_INDEX_URL} socketdev==${SDK_VERSION}; \ | ||
| pip install --no-cache-dir --index-url ${PIP_INDEX_URL} --extra-index-url ${PIP_EXTRA_INDEX_URL} socketdev==${SDK_VERSION}; \ | ||
| fi; \ | ||
| fi | ||
|
|
||
| # Copy local source and install in editable mode if USE_LOCAL_INSTALL is true | ||
| COPY . /app | ||
| WORKDIR /app | ||
| RUN if [ "$USE_LOCAL_INSTALL" = "true" ]; then \ | ||
| pip install --no-deps -e . && pip check; \ | ||
| pip install --no-cache-dir --no-deps -e . && pip check; \ | ||
| fi | ||
|
|
||
| # Create workspace directory with proper permissions | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| __author__ = 'socket.dev' | ||
| __version__ = '2.11.11' | ||
| __version__ = '2.11.12' | ||
| USER_AGENT_NAME = 'socket-python-cli' | ||
| USER_AGENT = f'{USER_AGENT_NAME}/{__version__}' |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| [package] | ||
| name = "simple-cargo" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| publish = false | ||
|
|
||
| [dependencies] | ||
| # Pinned to a release affected by GHSA-43w2-9j62-hq99 (insert_many buffer | ||
| # overflow), so reachability has a known vulnerability to analyze. | ||
| smallvec = "=1.6.0" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| use smallvec::SmallVec; | ||
|
|
||
| fn main() { | ||
| let mut values: SmallVec<[u8; 4]> = SmallVec::new(); | ||
| values.insert_many(0, vec![1, 2, 3]); | ||
| println!("{:?}", values); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[agent] Optional: the header says the image needs only the package source, its build metadata and the entrypoint script, but the file lists exclusions, which must grow whenever a new top-level directory appears. An allow-list would say what the comment says: