From 05aa01ea3615eb13145d6b1fe577096e10eaa632 Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Mon, 9 Sep 2024 09:03:27 +0000 Subject: [PATCH] Stop including pip in the final app image (#264) After #254, pip is now installed into its own layer rather than into the system site-packages directory inside the Python layer. This means its now possible to exclude pip from the final app image, by making the pip layer be a build-only layer. Excluding pip from the final app image: - Prevents several classes of user error/confusion/bad app design patterns seen in support tickets (see #255 for more details). - Reduces app image supply chain surface area. - Reduces app image size by 13 MB and layer count by 1, meaning less to have to push to the remote registry. - Matches the approach used for Poetry, where we don't make Poetry available at run-time either. Users that need pip at run-time for a temporary debugging task can run `python -m ensurepip --default-pip` in the container at run-time to make it available again (this command doesn't even have to download anything - it uses the pip bundled with Python). Or if pip is an actual run-time dependency of the app, then the app can add `pip` to its `requirements.txt` (which much more clearly conveys the requirements of the app, and also allows the app to pick what pip version it needs at run-time). Should we find that pip's absence causes confusion in the future, we could always add a wrapper/shim `pip` script in the app image which does something like: ``` echo "pip isn't installed at run-time, if you need it temporarily run 'python -m ensurepip --default-pip' to install it" exit 1 ``` ...to improve discoverability. We'll also document pip (and Poetry) being available at build-time only in the docs that will be added by #11. Closes #255. GUS-W-16697386. --- CHANGELOG.md | 4 ++++ src/layers/pip.rs | 6 +++--- tests/pip_test.rs | 18 +++++------------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bb96fe..0a6f277 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- pip is now only available during the build, and is longer included in the final app image. ([#264](https://github.com/heroku/buildpacks-python/pull/264)) + ## [0.17.1] - 2024-09-07 ### Changed diff --git a/src/layers/pip.rs b/src/layers/pip.rs index a828621..347adfa 100644 --- a/src/layers/pip.rs +++ b/src/layers/pip.rs @@ -31,7 +31,7 @@ pub(crate) fn install_pip( layer_name!("pip"), CachedLayerDefinition { build: true, - launch: true, + launch: false, invalid_metadata_action: &|_| InvalidMetadataAction::DeleteLayer, restored_layer_action: &|cached_metadata: &PipLayerMetadata, _| { let cached_pip_version = cached_metadata.pip_version.clone(); @@ -49,7 +49,7 @@ pub(crate) fn install_pip( // reduce build log spam and prevent users from thinking they need to manually upgrade. // https://pip.pypa.io/en/stable/cli/pip/#cmdoption-disable-pip-version-check .chainable_insert( - Scope::All, + Scope::Build, ModificationBehavior::Override, "PIP_DISABLE_PIP_VERSION_CHECK", "1", @@ -57,7 +57,7 @@ pub(crate) fn install_pip( // Move the Python user base directory to this layer instead of under HOME: // https://docs.python.org/3/using/cmdline.html#envvar-PYTHONUSERBASE .chainable_insert( - Scope::All, + Scope::Build, ModificationBehavior::Override, "PYTHONUSERBASE", layer.path(), diff --git a/tests/pip_test.rs b/tests/pip_test.rs index f29d313..c9ebf7a 100644 --- a/tests/pip_test.rs +++ b/tests/pip_test.rs @@ -5,7 +5,6 @@ use libcnb_test::{assert_contains, assert_empty, BuildpackReference, PackResult, #[test] #[ignore = "integration test"] -#[allow(clippy::too_many_lines)] fn pip_basic_install_and_cache_reuse() { let mut config = default_build_config("tests/fixtures/pip_basic"); config.buildpacks(vec![ @@ -69,14 +68,13 @@ fn pip_basic_install_and_cache_reuse() { // Check that at run-time: // - The correct env vars are set. - // - pip is available (rather than just during the build). - // - Both pip and Python can find the typing-extensions package. + // - pip isn't available. + // - Python can find the typing-extensions package. let command_output = context.run_shell_command( indoc! {" set -euo pipefail printenv | sort | grep -vE '^(_|HOME|HOSTNAME|OLDPWD|PWD|SHLVL)=' - echo - pip list + ! command -v pip > /dev/null || { echo 'pip unexpectedly found!' && exit 1; } python -c 'import typing_extensions' "} ); @@ -85,18 +83,12 @@ fn pip_basic_install_and_cache_reuse() { command_output.stdout, formatdoc! {" LANG=C.UTF-8 - LD_LIBRARY_PATH=/layers/heroku_python/venv/lib:/layers/heroku_python/python/lib:/layers/heroku_python/pip/lib - PATH=/layers/heroku_python/venv/bin:/layers/heroku_python/python/bin:/layers/heroku_python/pip/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - PIP_DISABLE_PIP_VERSION_CHECK=1 + LD_LIBRARY_PATH=/layers/heroku_python/venv/lib:/layers/heroku_python/python/lib + PATH=/layers/heroku_python/venv/bin:/layers/heroku_python/python/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PIP_PYTHON=/layers/heroku_python/venv PYTHONHOME=/layers/heroku_python/python PYTHONUNBUFFERED=1 - PYTHONUSERBASE=/layers/heroku_python/pip VIRTUAL_ENV=/layers/heroku_python/venv - - Package Version - ----------------- ------- - typing_extensions 4.12.2 "} );