From 5da406d3c30b04a213ac5eabc45d5436b58ce3dd Mon Sep 17 00:00:00 2001 From: Sean Mooney Date: Wed, 12 Aug 2026 17:08:21 +0100 Subject: [PATCH 1/2] Standardize lock generation on Python 3.12 Dependency markers are evaluated by the Python interpreter running the resolver. Allowing different Python minor versions and unpinned generator tools therefore produces host-dependent lock files and noisy generator metadata. Require Python 3.12 for source and lock generation, pin the resolver tools, and normalize annotations, headers, and index configuration from generated locks. Add a pinned-source reproducibility workflow and architecture checks, then regenerate the tracked lock files with the canonical environment. Assisted-By: Pi gpt-5.6-sol Signed-off-by: Sean Mooney (cherry picked from commit d28af263061f18db6fda7cc9b19dfc1835f77528) --- .github/actions/setup-tox/action.yml | 6 +- .github/workflows/test-update-sources.yml | 17 +- build.sh | 27 +- containers/base/buildrequirements.lock.master | 4 - containers/base/requirements.lock.master | 14 - .../cyborg/buildrequirements.lock.master | 4 - containers/cyborg/requirements.lock.master | 483 -------------- .../glance/buildrequirements.lock.master | 6 +- containers/glance/requirements.lock.master | 610 ------------------ .../manila/buildrequirements.lock.master | 6 +- containers/manila/requirements.lock.master | 593 ----------------- .../watcher/buildrequirements.lock.master | 4 - containers/watcher/requirements.lock.master | 609 ----------------- docs/TESTING.md | 10 + docs/developer-guide.md | 12 +- tox.ini | 19 +- 16 files changed, 75 insertions(+), 2349 deletions(-) diff --git a/.github/actions/setup-tox/action.yml b/.github/actions/setup-tox/action.yml index bef55cb0..007939b0 100644 --- a/.github/actions/setup-tox/action.yml +++ b/.github/actions/setup-tox/action.yml @@ -1,13 +1,13 @@ name: Setup Python and tox -description: Install Python and tox (repository must already be checked out) +description: Install tox with the repository's canonical Python 3.12 runs: using: composite steps: - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 with: - python-version: "3.x" + python-version: "3.12" - name: Install tox shell: bash - run: pip install tox + run: python3 -m pip install tox diff --git a/.github/workflows/test-update-sources.yml b/.github/workflows/test-update-sources.yml index 1e5c6275..e93daf23 100644 --- a/.github/workflows/test-update-sources.yml +++ b/.github/workflows/test-update-sources.yml @@ -1,26 +1,35 @@ -name: Tests (update-sources) +name: Tests (source reproducibility) on: pull_request: paths: + - .github/actions/setup-tox/action.yml + - .github/workflows/test-update-sources.yml - build.sh - containers/** + - tox.ini - '!**/OWNERS' - '!**/OWNERS_ALIASES' push: paths: + - .github/actions/setup-tox/action.yml + - .github/workflows/test-update-sources.yml - build.sh - containers/** + - tox.ini - '!**/OWNERS' - '!**/OWNERS_ALIASES' workflow_dispatch: jobs: - update-sources: + update-lockfiles: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: ./.github/actions/setup-tox - - name: Verify update-sources runs successfully - run: tox -e update-sources + - name: Regenerate from committed source pins + run: tox -e update-lockfiles + + - name: Require reproducible generated files + run: git diff --exit-code -- containers/ diff --git a/build.sh b/build.sh index 9b6f4bab..a3f934a0 100755 --- a/build.sh +++ b/build.sh @@ -618,6 +618,24 @@ filter_lockfile_rpm_packages() { rm "${tmp}" } +# Remove generator-runtime and package-index details from a lock file. +normalize_generated_lock() { + local lockfile="$1" + local tmp + tmp=$(mktemp) + + awk ' + !seen_package && /^--(index-url|extra-index-url|trusted-host)[[:space:]]/ { + next + } + !seen_package && (/^#/ || /^$/) { next } + !/^#/ { seen_package = 1 } + { print } + ' "${lockfile}" > "${tmp}" + install -m 644 "${tmp}" "${lockfile}" + rm "${tmp}" +} + # Generate a single requirements.lock for a project by running pip-compile # against requirements.txt from all source packages (project + all images) # plus pythondeps.txt and pythonbuilddeps.txt from every image, @@ -676,11 +694,11 @@ generate_requirements_lock() { echo "--- Generating ${project_dir}/${lock_file} ---" (cd "${project_dir}" && \ - pip-compile --allow-unsafe --strip-extras \ + pip-compile --allow-unsafe --no-annotate --strip-extras \ -c "${UPSTREAM_CONSTRAINTS}.${stream}" \ -o "${lock_file}" \ "${input_files[@]}" && \ - sed -i "/# This file is autogenerated/{N;d;}" "${lock_file}") + normalize_generated_lock "${lock_file}") local rpm_pkgs rpm_pkgs=$(collect_rpm_python_packages "${project_dir}") @@ -738,10 +756,11 @@ generate_buildrequirements_lock() { echo "--- Generating ${project_dir}/${build_lock_file} ---" (cd "${project_dir}" && \ - pybuild-deps compile --no-annotate \ + pybuild-deps compile \ + --no-annotate \ -o "${build_lock_file}" \ "${CONSTRAINTS_FILE}.${stream}" && \ - sed -i "/# This file is autogenerated/{N;d;}" "${build_lock_file}") + normalize_generated_lock "${build_lock_file}") } # Generate buildrequirements.lock for each project in the target scope. diff --git a/containers/base/buildrequirements.lock.master b/containers/base/buildrequirements.lock.master index bde466c0..9cd81cad 100644 --- a/containers/base/buildrequirements.lock.master +++ b/containers/base/buildrequirements.lock.master @@ -1,7 +1,3 @@ -# -# -# pybuild-deps compile --no-annotate --output-file=buildrequirements.lock.master requirements.lock.master -# flit-core==4.0.2 packaging==26.3 setuptools-scm==10.2.1 diff --git a/containers/base/requirements.lock.master b/containers/base/requirements.lock.master index ea0d8ab6..f94e40f5 100644 --- a/containers/base/requirements.lock.master +++ b/containers/base/requirements.lock.master @@ -1,22 +1,8 @@ -# -# -# pip-compile --allow-unsafe --constraint=upper-constraints.txt.master --output-file=requirements.lock.master --strip-extras pythondeps.txt -# crudini==0.9.6 - # via -r pythondeps.txt dumb-init==1.2.5.post1 - # via -r pythondeps.txt iniparse==0.5 - # via crudini pbr==7.0.3 - # via - # -c upper-constraints.txt.master - # -r pythondeps.txt six==1.17.0 - # via - # -c upper-constraints.txt.master - # iniparse # The following packages are considered to be unsafe in a requirements file: setuptools==82.0.1 - # via pbr diff --git a/containers/cyborg/buildrequirements.lock.master b/containers/cyborg/buildrequirements.lock.master index eee1c155..23f26bc2 100644 --- a/containers/cyborg/buildrequirements.lock.master +++ b/containers/cyborg/buildrequirements.lock.master @@ -1,7 +1,3 @@ -# -# -# pybuild-deps compile --no-annotate --output-file=buildrequirements.lock.master requirements.lock.master -# calver==2025.10.20 coherent-licensed==0.5.2 cython==3.2.9 diff --git a/containers/cyborg/requirements.lock.master b/containers/cyborg/requirements.lock.master index 87747144..1bc56468 100644 --- a/containers/cyborg/requirements.lock.master +++ b/containers/cyborg/requirements.lock.master @@ -1,590 +1,107 @@ -# -# -# pip-compile --allow-unsafe --constraint=upper-constraints.txt.master --output-file=requirements.lock.master --strip-extras cyborg-agent/pythonbuilddeps.txt cyborg-agent/pythondeps.txt cyborg/pythonbuilddeps.txt cyborg/pythondeps.txt src/cyborg/requirements.txt -# alembic==1.18.5 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # oslo-db amqp==5.3.1 - # via - # -c upper-constraints.txt.master - # kombu - # oslo-messaging attrs==26.1.0 - # via - # -c upper-constraints.txt.master - # jsonschema - # referencing bcrypt==5.0.0 - # via - # -c upper-constraints.txt.master - # oslo-middleware cachetools==7.1.4 - # via - # -c upper-constraints.txt.master - # oslo-messaging certifi==2026.6.17 - # via requests cffi==2.0.0 - # via - # -c upper-constraints.txt.master - # cryptography - # oslo-privsep charset-normalizer==3.4.7 - # via - # -c upper-constraints.txt.master - # requests cotyledon==2.2.0 - # via - # -c upper-constraints.txt.master - # oslo-service debtcollector==3.1.0 - # via - # -c upper-constraints.txt.master - # futurist - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-privsep - # oslo-service - # python-keystoneclient decorator==5.3.1 - # via - # -c upper-constraints.txt.master - # dogpile-cache - # openstacksdk dnspython==2.8.0 - # via - # -c upper-constraints.txt.master - # eventlet dogpile-cache==1.5.0 - # via - # -c upper-constraints.txt.master - # openstacksdk - # oslo-cache eventlet==0.41.0 - # via - # -c upper-constraints.txt.master - # oslo-service fasteners==0.20 - # via - # -c upper-constraints.txt.master - # oslo-concurrency futurist==3.4.0 - # via - # -c upper-constraints.txt.master - # oslo-messaging - # oslo-service greenlet==3.5.3 - # via - # -c upper-constraints.txt.master - # eventlet - # oslo-service - # sqlalchemy idna==3.18 - # via - # -c upper-constraints.txt.master - # requests importlib-metadata==9.0.0 - # via - # -c upper-constraints.txt.master - # wsme iso8601==2.1.0 - # via - # -c upper-constraints.txt.master - # keystoneauth1 - # openstacksdk - # oslo-utils jinja2==3.1.6 - # via - # -c upper-constraints.txt.master - # oslo-middleware jmespath==1.1.0 - # via - # -c upper-constraints.txt.master - # openstacksdk jsonpatch==1.33 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # openstacksdk - # warlock jsonpointer==3.1.1 - # via - # -c upper-constraints.txt.master - # jsonpatch jsonschema==4.26.0 - # via - # -c upper-constraints.txt.master - # warlock jsonschema-specifications==2025.9.1 - # via - # -c upper-constraints.txt.master - # jsonschema keystoneauth1==5.15.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware - # openstacksdk - # python-glanceclient - # python-keystoneclient keystonemiddleware==13.0.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt kombu==5.6.2 - # via - # -c upper-constraints.txt.master - # oslo-messaging mako==1.3.12 - # via - # -c upper-constraints.txt.master - # alembic - # pecan markupsafe==3.0.3 - # via - # -c upper-constraints.txt.master - # jinja2 - # mako microversion-parse==2.1.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt msgpack==1.2.1 - # via - # -c upper-constraints.txt.master - # oslo-privsep - # oslo-serialization netaddr==1.3.0 - # via - # -c upper-constraints.txt.master - # oslo-config - # oslo-utils - # oslo-versionedobjects - # wsme openstacksdk==4.18.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt os-resource-classes==1.1.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt os-service-types==1.8.2 - # via - # -c upper-constraints.txt.master - # keystoneauth1 - # openstacksdk oslo-cache==4.3.0 - # via - # -c upper-constraints.txt.master - # -r cyborg-agent/pythondeps.txt - # -r cyborg/pythondeps.txt - # keystonemiddleware oslo-concurrency==7.6.1 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # oslo-service oslo-config==10.6.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # keystonemiddleware - # oslo-cache - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-policy - # oslo-privsep - # oslo-service - # oslo-upgradecheck - # oslo-versionedobjects - # pycadf - # python-keystoneclient oslo-context==6.5.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # keystonemiddleware - # oslo-log - # oslo-messaging - # oslo-middleware - # oslo-policy - # oslo-versionedobjects oslo-db==18.1.0 - # via - # -c upper-constraints.txt.master - # -r cyborg-agent/pythondeps.txt - # -r cyborg/pythondeps.txt - # -r src/cyborg/requirements.txt oslo-i18n==6.9.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # keystonemiddleware - # oslo-cache - # oslo-concurrency - # oslo-config - # oslo-db - # oslo-log - # oslo-middleware - # oslo-policy - # oslo-privsep - # oslo-service - # oslo-upgradecheck - # oslo-utils - # oslo-versionedobjects - # python-glanceclient - # python-keystoneclient oslo-log==8.3.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # keystonemiddleware - # oslo-cache - # oslo-messaging - # oslo-metrics - # oslo-privsep - # oslo-service - # oslo-versionedobjects oslo-messaging==18.2.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # oslo-versionedobjects oslo-metrics==0.16.0 - # via - # -c upper-constraints.txt.master - # oslo-messaging oslo-middleware==8.2.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # oslo-messaging oslo-policy==6.0.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # oslo-upgradecheck oslo-privsep==3.12.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt oslo-serialization==5.11.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware - # oslo-log - # oslo-messaging - # oslo-policy - # pycadf - # python-keystoneclient oslo-service==4.8.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # oslo-messaging - # oslo-service oslo-upgradecheck==2.8.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt oslo-utils==10.1.1 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # keystonemiddleware - # oslo-cache - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-policy - # oslo-privsep - # oslo-serialization - # oslo-service - # oslo-upgradecheck - # oslo-versionedobjects - # python-glanceclient - # python-keystoneclient oslo-versionedobjects==3.11.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt packaging==26.2 - # via - # -c upper-constraints.txt.master - # kombu - # oslo-utils - # python-keystoneclient - # wheel paste==3.10.1 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # oslo-service pastedeploy==3.1.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # oslo-service pbr==7.0.3 - # via - # -c upper-constraints.txt.master - # -r cyborg-agent/pythonbuilddeps.txt - # -r cyborg/pythonbuilddeps.txt - # -r src/cyborg/requirements.txt - # keystonemiddleware - # openstacksdk - # os-resource-classes - # os-service-types - # oslo-concurrency - # oslo-context - # oslo-i18n - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-utils - # python-glanceclient - # python-keystoneclient pecan==1.8.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt platformdirs==4.10.0 - # via - # -c upper-constraints.txt.master - # openstacksdk prettytable==3.18.0 - # via - # -c upper-constraints.txt.master - # oslo-upgradecheck - # python-glanceclient prometheus-client==0.25.0 - # via - # -c upper-constraints.txt.master - # oslo-metrics psutil==7.2.2 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # openstacksdk - # oslo-utils pycadf==4.1.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware pycparser==3.0 - # via - # -c upper-constraints.txt.master - # cffi pyjwt==2.13.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware pymemcache==4.0.0 - # via - # -c upper-constraints.txt.master - # oslo-cache pymysql==1.2.0 - # via - # -c upper-constraints.txt.master - # oslo-db pyopenssl==24.2.1 - # via - # -c upper-constraints.txt.master - # python-glanceclient pyparsing==3.3.2 - # via - # -c upper-constraints.txt.master - # oslo-utils python-binary-memcached==0.32.0 - # via - # -c upper-constraints.txt.master - # oslo-cache python-dateutil==2.9.0.post0 - # via - # -c upper-constraints.txt.master - # oslo-log python-glanceclient==4.12.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt python-keystoneclient==5.8.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware python-memcached==1.62 - # via - # -c upper-constraints.txt.master - # oslo-cache pytz==2026.2 - # via - # -c upper-constraints.txt.master - # wsme pyyaml==6.0.3 - # via - # -c upper-constraints.txt.master - # openstacksdk - # oslo-config - # oslo-messaging - # oslo-policy - # oslo-utils redis==8.0.1 - # via - # -c upper-constraints.txt.master - # oslo-cache referencing==0.37.0 - # via - # -c upper-constraints.txt.master - # jsonschema - # jsonschema-specifications repoze-lru==0.8 - # via - # -c upper-constraints.txt.master - # routes requests==2.34.2 - # via - # -c upper-constraints.txt.master - # keystoneauth1 - # keystonemiddleware - # oslo-config - # oslo-policy - # python-glanceclient - # python-keystoneclient rfc3986==2.0.0 - # via - # -c upper-constraints.txt.master - # oslo-config routes==2.5.1 - # via - # -c upper-constraints.txt.master - # oslo-service rpds-py==2026.5.1 - # via - # -c upper-constraints.txt.master - # jsonschema - # referencing setproctitle==1.3.7 - # via - # -c upper-constraints.txt.master - # cotyledon simplegeneric==0.8.1 - # via - # -c upper-constraints.txt.master - # wsme six==1.17.0 - # via - # -c upper-constraints.txt.master - # python-binary-memcached - # python-dateutil - # routes sqlalchemy==2.0.51 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # alembic - # oslo-db statsd==4.0.1 - # via - # -c upper-constraints.txt.master - # oslo-middleware stevedore==5.9.0 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt - # dogpile-cache - # keystoneauth1 - # oslo-config - # oslo-db - # oslo-messaging - # oslo-middleware - # oslo-policy - # python-keystoneclient testresources==2.1.2 - # via - # -c upper-constraints.txt.master - # oslo-db testscenarios==0.6.2 - # via - # -c upper-constraints.txt.master - # oslo-db typing-extensions==4.15.0 - # via - # -c upper-constraints.txt.master - # alembic - # cotyledon - # keystoneauth1 - # os-service-types - # oslo-context - # oslo-middleware - # referencing - # sqlalchemy tzdata==2026.2 - # via - # -c upper-constraints.txt.master - # kombu uhashring==2.4 - # via - # -c upper-constraints.txt.master - # python-binary-memcached urllib3==2.7.0 - # via - # -c upper-constraints.txt.master - # requests vine==5.1.0 - # via - # -c upper-constraints.txt.master - # amqp - # kombu warlock==2.1.0 - # via - # -c upper-constraints.txt.master - # python-glanceclient wcwidth==0.8.1 - # via - # -c upper-constraints.txt.master - # prettytable webob==1.8.10 - # via - # -c upper-constraints.txt.master - # keystonemiddleware - # microversion-parse - # oslo-messaging - # oslo-middleware - # oslo-service - # pecan - # wsme wheel==0.47.0 - # via - # -r cyborg-agent/pythonbuilddeps.txt - # -r cyborg/pythonbuilddeps.txt wrapt==2.2.2 - # via - # -c upper-constraints.txt.master - # debtcollector - # python-glanceclient wsme==0.12.1 - # via - # -c upper-constraints.txt.master - # -r src/cyborg/requirements.txt yappi==1.7.6 - # via - # -c upper-constraints.txt.master - # oslo-service zipp==4.1.0 - # via - # -c upper-constraints.txt.master - # importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/containers/glance/buildrequirements.lock.master b/containers/glance/buildrequirements.lock.master index a4625a84..87851e32 100644 --- a/containers/glance/buildrequirements.lock.master +++ b/containers/glance/buildrequirements.lock.master @@ -1,7 +1,3 @@ -# -# -# pybuild-deps compile --no-annotate --output-file=buildrequirements.lock.master requirements.lock.master -# build==1.5.0 calver==2025.10.20 coherent-licensed==0.5.2 @@ -26,7 +22,7 @@ tomlkit==0.15.1 trove-classifiers==2026.6.1.19 typing-extensions==4.15.0 vcs-versioning==2.2.4 -wheel==0.47.0 +wheel==0.48.0 # The following packages are considered to be unsafe in a requirements file: setuptools==82.0.1 diff --git a/containers/glance/requirements.lock.master b/containers/glance/requirements.lock.master index 2fa70252..7f0f33c4 100644 --- a/containers/glance/requirements.lock.master +++ b/containers/glance/requirements.lock.master @@ -1,736 +1,126 @@ -# -# -# pip-compile --allow-unsafe --constraint=upper-constraints.txt.master --output-file=requirements.lock.master --strip-extras glance-api/pythonbuilddeps.txt glance-api/pythondeps.txt src/glance/requirements.txt -# alembic==1.18.5 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # oslo-db amqp==5.3.1 - # via - # -c upper-constraints.txt.master - # kombu - # oslo-messaging attrs==26.1.0 - # via - # -c upper-constraints.txt.master - # jsonschema - # referencing automaton==3.4.0 - # via - # -c upper-constraints.txt.master - # taskflow autopage==0.6.0 - # via - # -c upper-constraints.txt.master - # cliff bcrypt==5.0.0 - # via - # -c upper-constraints.txt.master - # oslo-middleware cachetools==7.1.4 - # via - # -c upper-constraints.txt.master - # oslo-messaging - # taskflow castellan==5.8.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # cursive certifi==2026.7.22 - # via requests cffi==2.0.0 - # via - # -c upper-constraints.txt.master - # cryptography - # oslo-privsep charset-normalizer==3.4.7 - # via - # -c upper-constraints.txt.master - # requests cliff==4.15.0 - # via - # -c upper-constraints.txt.master - # python-barbicanclient cmd2==4.0.0 - # via - # -c upper-constraints.txt.master - # cliff cursive==0.3.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt debtcollector==3.1.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # futurist - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-privsep - # oslo-rootwrap - # oslo-service - # python-keystoneclient - # taskflow decorator==5.3.1 - # via - # -c upper-constraints.txt.master - # dogpile-cache - # openstacksdk defusedxml==0.7.1 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt dnspython==2.8.0 - # via - # -c upper-constraints.txt.master - # eventlet dogpile-cache==1.5.0 - # via - # -c upper-constraints.txt.master - # openstacksdk - # oslo-cache eventlet==0.41.0 - # via - # -c upper-constraints.txt.master - # oslo-service fasteners==0.20 - # via - # -c upper-constraints.txt.master - # oslo-concurrency - # taskflow futurist==3.4.0 - # via - # -c upper-constraints.txt.master - # glance-store - # oslo-messaging - # taskflow glance-store==5.6.0 - # via - # -c upper-constraints.txt.master - # -r glance-api/pythondeps.txt - # -r src/glance/requirements.txt greenlet==3.5.3 - # via - # -c upper-constraints.txt.master - # eventlet - # oslo-service - # sqlalchemy httplib2==0.32.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt idna==3.18 - # via - # -c upper-constraints.txt.master - # requests importlib-metadata==9.0.0 - # via - # -c upper-constraints.txt.master - # wsme iso8601==2.1.0 - # via - # -c upper-constraints.txt.master - # keystoneauth1 - # openstacksdk - # oslo-utils jinja2==3.1.6 - # via - # -c upper-constraints.txt.master - # oslo-middleware - # oslo-reports jmespath==1.1.0 - # via - # -c upper-constraints.txt.master - # openstacksdk jsonpatch==1.33 - # via - # -c upper-constraints.txt.master - # openstacksdk jsonpointer==3.1.1 - # via - # -c upper-constraints.txt.master - # jsonpatch jsonschema==4.26.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # glance-store - # taskflow jsonschema-specifications==2025.9.1 - # via - # -c upper-constraints.txt.master - # jsonschema keystoneauth1==5.15.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # castellan - # glance-store - # keystonemiddleware - # openstacksdk - # oslo-limit - # python-barbicanclient - # python-cinderclient - # python-keystoneclient keystonemiddleware==13.0.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt kombu==5.6.2 - # via - # -c upper-constraints.txt.master - # oslo-messaging mako==1.3.12 - # via - # -c upper-constraints.txt.master - # alembic markdown-it-py==4.2.0 - # via - # -c upper-constraints.txt.master - # rich markupsafe==3.0.3 - # via - # -c upper-constraints.txt.master - # jinja2 - # mako mdurl==0.1.2 - # via - # -c upper-constraints.txt.master - # markdown-it-py msgpack==1.2.1 - # via - # -c upper-constraints.txt.master - # oslo-privsep - # oslo-serialization netaddr==1.3.0 - # via - # -c upper-constraints.txt.master - # oslo-config - # oslo-utils - # osprofiler - # wsme networkx==3.6.1 - # via - # -c upper-constraints.txt.master - # taskflow openstacksdk==4.18.0 - # via - # -c upper-constraints.txt.master - # oslo-limit os-brick==7.1.0 - # via - # -c upper-constraints.txt.master - # glance-store os-service-types==1.8.2 - # via - # -c upper-constraints.txt.master - # keystoneauth1 - # openstacksdk oslo-cache==4.3.0 - # via - # -c upper-constraints.txt.master - # -r glance-api/pythondeps.txt - # keystonemiddleware oslo-concurrency==7.6.1 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # glance-store - # os-brick - # oslo-service - # osprofiler oslo-config==10.6.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # castellan - # glance-store - # keystonemiddleware - # os-brick - # oslo-cache - # oslo-concurrency - # oslo-db - # oslo-limit - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-policy - # oslo-privsep - # oslo-reports - # oslo-service - # oslo-upgradecheck - # osprofiler - # pycadf - # python-keystoneclient oslo-context==6.5.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # castellan - # keystonemiddleware - # os-brick - # oslo-log - # oslo-messaging - # oslo-middleware - # oslo-policy oslo-db==18.1.0 - # via - # -c upper-constraints.txt.master - # -r glance-api/pythondeps.txt - # -r src/glance/requirements.txt oslo-i18n==6.9.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # castellan - # cursive - # glance-store - # keystonemiddleware - # os-brick - # oslo-cache - # oslo-concurrency - # oslo-config - # oslo-db - # oslo-limit - # oslo-log - # oslo-middleware - # oslo-policy - # oslo-privsep - # oslo-reports - # oslo-service - # oslo-upgradecheck - # oslo-utils - # python-barbicanclient - # python-cinderclient - # python-keystoneclient oslo-limit==2.12.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt oslo-log==8.3.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # castellan - # cursive - # keystonemiddleware - # os-brick - # oslo-cache - # oslo-limit - # oslo-messaging - # oslo-metrics - # oslo-privsep - # oslo-service oslo-messaging==18.2.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt oslo-metrics==0.16.0 - # via - # -c upper-constraints.txt.master - # oslo-messaging oslo-middleware==8.2.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # oslo-messaging oslo-policy==6.0.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # oslo-upgradecheck oslo-privsep==3.12.0 - # via - # -c upper-constraints.txt.master - # glance-store - # os-brick oslo-reports==3.9.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt oslo-rootwrap==7.10.0 - # via - # -c upper-constraints.txt.master - # glance-store oslo-serialization==5.11.0 - # via - # -c upper-constraints.txt.master - # cursive - # glance-store - # keystonemiddleware - # os-brick - # oslo-log - # oslo-messaging - # oslo-policy - # oslo-reports - # osprofiler - # pycadf - # python-barbicanclient - # python-keystoneclient - # taskflow oslo-service==4.8.0 - # via - # -c upper-constraints.txt.master - # os-brick - # oslo-messaging oslo-upgradecheck==2.8.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt oslo-utils==10.1.1 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # castellan - # cursive - # glance-store - # keystonemiddleware - # os-brick - # oslo-cache - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-policy - # oslo-privsep - # oslo-reports - # oslo-serialization - # oslo-service - # oslo-upgradecheck - # osprofiler - # python-barbicanclient - # python-cinderclient - # python-keystoneclient - # taskflow osprofiler==4.4.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt packaging==26.2 - # via - # -c upper-constraints.txt.master - # kombu - # oslo-utils - # python-keystoneclient paste==3.10.1 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # oslo-service pastedeploy==3.1.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # oslo-service pbr==7.0.3 - # via - # -c upper-constraints.txt.master - # -r glance-api/pythonbuilddeps.txt - # -r src/glance/requirements.txt - # castellan - # cursive - # keystonemiddleware - # openstacksdk - # os-brick - # os-service-types - # oslo-concurrency - # oslo-context - # oslo-i18n - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-reports - # oslo-rootwrap - # oslo-utils - # python-barbicanclient - # python-cinderclient - # python-keystoneclient - # taskflow platformdirs==4.10.0 - # via - # -c upper-constraints.txt.master - # openstacksdk prettytable==3.18.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # automaton - # cliff - # oslo-upgradecheck - # osprofiler - # python-cinderclient prometheus-client==0.25.0 - # via - # -c upper-constraints.txt.master - # oslo-metrics prompt-toolkit==3.0.52 - # via - # -c upper-constraints.txt.master - # cmd2 psutil==7.2.2 - # via - # -c upper-constraints.txt.master - # openstacksdk - # os-brick - # oslo-reports - # oslo-utils pycadf==4.1.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware pycparser==3.0 - # via - # -c upper-constraints.txt.master - # cffi pydot==4.0.1 - # via - # -c upper-constraints.txt.master - # taskflow pygments==2.20.0 - # via - # -c upper-constraints.txt.master - # rich pyjwt==2.13.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware pymemcache==4.0.0 - # via - # -c upper-constraints.txt.master - # oslo-cache pymysql==1.2.0 - # via - # -c upper-constraints.txt.master - # oslo-db pyparsing==3.3.2 - # via - # -c upper-constraints.txt.master - # httplib2 - # oslo-utils - # pydot pyperclip==1.11.0 - # via - # -c upper-constraints.txt.master - # cmd2 python-barbicanclient==7.5.0 - # via - # -c upper-constraints.txt.master - # castellan python-binary-memcached==0.32.0 - # via - # -c upper-constraints.txt.master - # oslo-cache python-cinderclient==9.9.0 - # via - # -c upper-constraints.txt.master - # glance-store python-dateutil==2.9.0.post0 - # via - # -c upper-constraints.txt.master - # oslo-log python-keystoneclient==5.8.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # glance-store - # keystonemiddleware python-memcached==1.62 - # via - # -c upper-constraints.txt.master - # oslo-cache python-swiftclient==4.10.0 - # via - # -c upper-constraints.txt.master - # glance-store pytz==2026.2 - # via - # -c upper-constraints.txt.master - # wsme pyyaml==6.0.3 - # via - # -c upper-constraints.txt.master - # cliff - # openstacksdk - # oslo-config - # oslo-messaging - # oslo-policy - # oslo-utils redis==8.0.1 - # via - # -c upper-constraints.txt.master - # oslo-cache referencing==0.37.0 - # via - # -c upper-constraints.txt.master - # jsonschema - # jsonschema-specifications repoze-lru==0.8 - # via - # -c upper-constraints.txt.master - # routes requests==2.34.2 - # via - # -c upper-constraints.txt.master - # castellan - # glance-store - # keystoneauth1 - # keystonemiddleware - # os-brick - # oslo-config - # oslo-policy - # osprofiler - # python-barbicanclient - # python-cinderclient - # python-keystoneclient - # python-swiftclient retrying==1.4.2 - # via - # -c upper-constraints.txt.master - # glance-store rfc3986==2.0.0 - # via - # -c upper-constraints.txt.master - # oslo-config rich==15.0.0 - # via - # -c upper-constraints.txt.master - # cmd2 - # rich-argparse rich-argparse==1.8.0 - # via - # -c upper-constraints.txt.master - # cmd2 routes==2.5.1 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # oslo-service rpds-py==2026.5.1 - # via - # -c upper-constraints.txt.master - # jsonschema - # referencing simplegeneric==0.8.1 - # via - # -c upper-constraints.txt.master - # wsme six==1.17.0 - # via - # -c upper-constraints.txt.master - # python-binary-memcached - # python-dateutil - # routes sqlalchemy==2.0.51 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # alembic - # oslo-db statsd==4.0.1 - # via - # -c upper-constraints.txt.master - # oslo-middleware stevedore==5.9.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # castellan - # cliff - # dogpile-cache - # glance-store - # keystoneauth1 - # oslo-config - # oslo-db - # oslo-messaging - # oslo-middleware - # oslo-policy - # python-cinderclient - # python-keystoneclient - # taskflow taskflow==6.3.0 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt tenacity==9.1.4 - # via - # -c upper-constraints.txt.master - # os-brick - # taskflow testresources==2.1.2 - # via - # -c upper-constraints.txt.master - # oslo-db testscenarios==0.6.2 - # via - # -c upper-constraints.txt.master - # oslo-db typing-extensions==4.15.0 - # via - # -c upper-constraints.txt.master - # alembic - # automaton - # keystoneauth1 - # os-service-types - # oslo-context - # oslo-middleware - # referencing - # sqlalchemy tzdata==2026.2 - # via - # -c upper-constraints.txt.master - # kombu uhashring==2.4 - # via - # -c upper-constraints.txt.master - # python-binary-memcached urllib3==2.7.0 - # via - # -c upper-constraints.txt.master - # requests vine==5.1.0 - # via - # -c upper-constraints.txt.master - # amqp - # kombu wcwidth==0.8.1 - # via - # -c upper-constraints.txt.master - # prettytable - # prompt-toolkit webob==1.8.10 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt - # keystonemiddleware - # oslo-messaging - # oslo-middleware - # oslo-service - # osprofiler - # wsme wrapt==2.2.2 - # via - # -c upper-constraints.txt.master - # debtcollector wsme==0.12.1 - # via - # -c upper-constraints.txt.master - # -r src/glance/requirements.txt yappi==1.7.6 - # via - # -c upper-constraints.txt.master - # oslo-service zipp==4.1.0 - # via - # -c upper-constraints.txt.master - # importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/containers/manila/buildrequirements.lock.master b/containers/manila/buildrequirements.lock.master index c9246d98..db159a4b 100644 --- a/containers/manila/buildrequirements.lock.master +++ b/containers/manila/buildrequirements.lock.master @@ -1,7 +1,3 @@ -# -# -# pybuild-deps compile --no-annotate --output-file=buildrequirements.lock.master requirements.lock.master -# build==1.5.0 calver==2025.10.20 cffi==2.0.0 @@ -27,7 +23,7 @@ tomlkit==0.15.1 trove-classifiers==2026.6.1.19 typing-extensions==4.15.0 vcs-versioning==2.2.4 -wheel==0.47.0 +wheel==0.48.0 # The following packages are considered to be unsafe in a requirements file: setuptools==82.0.1 diff --git a/containers/manila/requirements.lock.master b/containers/manila/requirements.lock.master index c6a6a0d7..57fe0dfa 100644 --- a/containers/manila/requirements.lock.master +++ b/containers/manila/requirements.lock.master @@ -1,715 +1,122 @@ -# -# -# pip-compile --allow-unsafe --constraint=upper-constraints.txt.master --output-file=requirements.lock.master --strip-extras pythonbuilddeps.txt pythondeps.txt src/manila/requirements.txt -# alembic==1.18.5 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-db amqp==5.3.1 - # via - # -c upper-constraints.txt.master - # kombu - # oslo-messaging attrs==26.1.0 - # via - # -c upper-constraints.txt.master - # jsonschema - # referencing autopage==0.6.0 - # via - # -c upper-constraints.txt.master - # cliff bcrypt==5.0.0 - # via - # -c upper-constraints.txt.master - # oslo-middleware - # paramiko cachetools==7.1.4 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-messaging castellan==5.8.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt certifi==2026.7.22 - # via requests cffi==2.0.0 - # via - # -c upper-constraints.txt.master - # cryptography - # oslo-privsep - # pynacl charset-normalizer==3.4.7 - # via - # -c upper-constraints.txt.master - # requests cliff==4.15.0 - # via - # -c upper-constraints.txt.master - # osc-lib - # python-barbicanclient - # python-neutronclient cmd2==4.0.0 - # via - # -c upper-constraints.txt.master - # cliff cotyledon==2.2.0 - # via - # -c upper-constraints.txt.master - # oslo-service debtcollector==3.1.0 - # via - # -c upper-constraints.txt.master - # futurist - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-privsep - # oslo-rootwrap - # oslo-service - # python-keystoneclient - # python-neutronclient decorator==5.3.1 - # via - # -c upper-constraints.txt.master - # dogpile-cache - # openstacksdk defusedxml==0.7.1 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt dnspython==2.8.0 - # via - # -c upper-constraints.txt.master - # eventlet dogpile-cache==1.5.0 - # via - # -c upper-constraints.txt.master - # openstacksdk - # oslo-cache eventlet==0.41.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-service fasteners==0.20 - # via - # -c upper-constraints.txt.master - # oslo-concurrency - # tooz futurist==3.4.0 - # via - # -c upper-constraints.txt.master - # oslo-messaging - # oslo-service - # tooz greenlet==3.5.3 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # eventlet - # oslo-service - # sqlalchemy idna==3.18 - # via - # -c upper-constraints.txt.master - # requests invoke==3.0.3 - # via - # -c upper-constraints.txt.master - # paramiko iso8601==2.1.0 - # via - # -c upper-constraints.txt.master - # keystoneauth1 - # openstacksdk - # oslo-utils - # python-novaclient jinja2==3.1.6 - # via - # -c upper-constraints.txt.master - # oslo-middleware - # oslo-reports jmespath==1.1.0 - # via - # -c upper-constraints.txt.master - # openstacksdk jsonpatch==1.33 - # via - # -c upper-constraints.txt.master - # openstacksdk jsonpointer==3.1.1 - # via - # -c upper-constraints.txt.master - # jsonpatch jsonschema==4.26.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt jsonschema-specifications==2025.9.1 - # via - # -c upper-constraints.txt.master - # jsonschema keystoneauth1==5.15.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # castellan - # keystonemiddleware - # openstacksdk - # osc-lib - # python-barbicanclient - # python-cinderclient - # python-keystoneclient - # python-neutronclient - # python-novaclient keystonemiddleware==13.0.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt kombu==5.6.2 - # via - # -c upper-constraints.txt.master - # oslo-messaging lxml==6.1.1 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt mako==1.3.12 - # via - # -c upper-constraints.txt.master - # alembic markdown-it-py==4.2.0 - # via - # -c upper-constraints.txt.master - # rich markupsafe==3.0.3 - # via - # -c upper-constraints.txt.master - # jinja2 - # mako mdurl==0.1.2 - # via - # -c upper-constraints.txt.master - # markdown-it-py msgpack==1.2.1 - # via - # -c upper-constraints.txt.master - # oslo-privsep - # oslo-serialization - # tooz netaddr==1.3.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-config - # oslo-utils - # osprofiler - # python-neutronclient openstacksdk==4.18.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # osc-lib - # python-neutronclient os-service-types==1.8.2 - # via - # -c upper-constraints.txt.master - # keystoneauth1 - # openstacksdk osc-lib==4.7.0 - # via - # -c upper-constraints.txt.master - # python-neutronclient oslo-cache==4.3.0 - # via - # -c upper-constraints.txt.master - # -r pythondeps.txt - # keystonemiddleware oslo-concurrency==7.6.1 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-service - # osprofiler oslo-config==10.6.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # castellan - # keystonemiddleware - # oslo-cache - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-policy - # oslo-privsep - # oslo-reports - # oslo-service - # oslo-upgradecheck - # osprofiler - # pycadf - # python-keystoneclient oslo-context==6.5.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # castellan - # keystonemiddleware - # oslo-log - # oslo-messaging - # oslo-middleware - # oslo-policy oslo-db==18.1.0 - # via - # -c upper-constraints.txt.master - # -r pythondeps.txt - # -r src/manila/requirements.txt oslo-i18n==6.9.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # castellan - # keystonemiddleware - # osc-lib - # oslo-cache - # oslo-concurrency - # oslo-config - # oslo-db - # oslo-log - # oslo-middleware - # oslo-policy - # oslo-privsep - # oslo-reports - # oslo-service - # oslo-upgradecheck - # oslo-utils - # python-barbicanclient - # python-cinderclient - # python-keystoneclient - # python-neutronclient - # python-novaclient oslo-log==8.3.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # castellan - # keystonemiddleware - # oslo-cache - # oslo-messaging - # oslo-metrics - # oslo-privsep - # oslo-service - # python-neutronclient oslo-messaging==18.2.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt oslo-metrics==0.16.0 - # via - # -c upper-constraints.txt.master - # oslo-messaging oslo-middleware==8.2.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-messaging oslo-policy==6.0.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-upgradecheck oslo-privsep==3.12.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt oslo-reports==3.9.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt oslo-rootwrap==7.10.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt oslo-serialization==5.11.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # keystonemiddleware - # oslo-log - # oslo-messaging - # oslo-policy - # oslo-reports - # osprofiler - # pycadf - # python-barbicanclient - # python-keystoneclient - # python-neutronclient - # python-novaclient - # tooz oslo-service==4.8.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-messaging oslo-upgradecheck==2.8.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt oslo-utils==10.1.1 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # castellan - # keystonemiddleware - # osc-lib - # oslo-cache - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-policy - # oslo-privsep - # oslo-reports - # oslo-serialization - # oslo-service - # oslo-upgradecheck - # osprofiler - # python-barbicanclient - # python-cinderclient - # python-keystoneclient - # python-neutronclient - # python-novaclient - # tooz osprofiler==4.4.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt packaging==26.2 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # kombu - # oslo-utils - # python-keystoneclient paramiko==4.0.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt paste==3.10.1 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-service pastedeploy==3.1.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-service pbr==7.0.3 - # via - # -c upper-constraints.txt.master - # -r pythonbuilddeps.txt - # -r src/manila/requirements.txt - # castellan - # keystonemiddleware - # openstacksdk - # os-service-types - # osc-lib - # oslo-concurrency - # oslo-context - # oslo-i18n - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-reports - # oslo-rootwrap - # oslo-utils - # python-barbicanclient - # python-cinderclient - # python-keystoneclient - # python-neutronclient - # python-novaclient platformdirs==4.10.0 - # via - # -c upper-constraints.txt.master - # openstacksdk prettytable==3.18.0 - # via - # -c upper-constraints.txt.master - # cliff - # oslo-upgradecheck - # osprofiler - # python-cinderclient - # python-novaclient prometheus-client==0.25.0 - # via - # -c upper-constraints.txt.master - # oslo-metrics prompt-toolkit==3.0.52 - # via - # -c upper-constraints.txt.master - # cmd2 psutil==7.2.2 - # via - # -c upper-constraints.txt.master - # openstacksdk - # oslo-reports - # oslo-utils pycadf==4.1.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware pycparser==3.0 - # via - # -c upper-constraints.txt.master - # cffi pygments==2.20.0 - # via - # -c upper-constraints.txt.master - # rich pyjwt==2.13.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware pymemcache==4.0.0 - # via - # -c upper-constraints.txt.master - # oslo-cache pymysql==1.2.0 - # via - # -c upper-constraints.txt.master - # oslo-db pynacl==1.6.2 - # via - # -c upper-constraints.txt.master - # paramiko pyparsing==3.3.2 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-utils pyperclip==1.11.0 - # via - # -c upper-constraints.txt.master - # cmd2 python-barbicanclient==7.5.0 - # via - # -c upper-constraints.txt.master - # castellan python-binary-memcached==0.32.0 - # via - # -c upper-constraints.txt.master - # oslo-cache python-cinderclient==9.9.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt python-dateutil==2.9.0.post0 - # via - # -c upper-constraints.txt.master - # oslo-log python-keystoneclient==5.8.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware - # python-neutronclient python-memcached==1.62 - # via - # -c upper-constraints.txt.master - # oslo-cache python-neutronclient==13.0.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt python-novaclient==18.13.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt pyyaml==6.0.3 - # via - # -c upper-constraints.txt.master - # cliff - # openstacksdk - # oslo-config - # oslo-messaging - # oslo-policy - # oslo-utils redis==8.0.1 - # via - # -c upper-constraints.txt.master - # oslo-cache referencing==0.37.0 - # via - # -c upper-constraints.txt.master - # jsonschema - # jsonschema-specifications repoze-lru==0.8 - # via - # -c upper-constraints.txt.master - # routes requests==2.34.2 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # castellan - # keystoneauth1 - # keystonemiddleware - # osc-lib - # oslo-config - # oslo-policy - # osprofiler - # python-barbicanclient - # python-cinderclient - # python-keystoneclient - # python-neutronclient rfc3986==2.0.0 - # via - # -c upper-constraints.txt.master - # oslo-config rich==15.0.0 - # via - # -c upper-constraints.txt.master - # cmd2 - # rich-argparse rich-argparse==1.8.0 - # via - # -c upper-constraints.txt.master - # cmd2 routes==2.5.1 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # oslo-service rpds-py==2026.5.1 - # via - # -c upper-constraints.txt.master - # jsonschema - # referencing setproctitle==1.3.7 - # via - # -c upper-constraints.txt.master - # cotyledon six==1.17.0 - # via - # -c upper-constraints.txt.master - # python-binary-memcached - # python-dateutil - # routes sqlalchemy==2.0.51 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # alembic - # oslo-db - # sqlalchemy-utils sqlalchemy-utils==0.42.1 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt statsd==4.0.1 - # via - # -c upper-constraints.txt.master - # oslo-middleware stevedore==5.9.0 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # castellan - # cliff - # dogpile-cache - # keystoneauth1 - # osc-lib - # oslo-config - # oslo-db - # oslo-messaging - # oslo-middleware - # oslo-policy - # python-cinderclient - # python-keystoneclient - # python-novaclient - # tooz tenacity==9.1.4 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # tooz testresources==2.1.2 - # via - # -c upper-constraints.txt.master - # oslo-db testscenarios==0.6.2 - # via - # -c upper-constraints.txt.master - # oslo-db tooz==9.0.1 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt typing-extensions==4.15.0 - # via - # -c upper-constraints.txt.master - # alembic - # cotyledon - # keystoneauth1 - # os-service-types - # oslo-context - # oslo-middleware - # referencing - # sqlalchemy tzdata==2026.2 - # via - # -c upper-constraints.txt.master - # kombu uhashring==2.4 - # via - # -c upper-constraints.txt.master - # python-binary-memcached urllib3==2.7.0 - # via - # -c upper-constraints.txt.master - # requests vine==5.1.0 - # via - # -c upper-constraints.txt.master - # amqp - # kombu voluptuous==0.16.0 - # via - # -c upper-constraints.txt.master - # tooz wcwidth==0.8.1 - # via - # -c upper-constraints.txt.master - # prettytable - # prompt-toolkit webob==1.8.10 - # via - # -c upper-constraints.txt.master - # -r src/manila/requirements.txt - # keystonemiddleware - # oslo-messaging - # oslo-middleware - # oslo-service - # osprofiler wrapt==2.2.2 - # via - # -c upper-constraints.txt.master - # debtcollector yappi==1.7.6 - # via - # -c upper-constraints.txt.master - # oslo-service # The following packages are considered to be unsafe in a requirements file: diff --git a/containers/watcher/buildrequirements.lock.master b/containers/watcher/buildrequirements.lock.master index f9077f6e..dafe28fa 100644 --- a/containers/watcher/buildrequirements.lock.master +++ b/containers/watcher/buildrequirements.lock.master @@ -1,7 +1,3 @@ -# -# -# pybuild-deps compile --no-annotate --output-file=buildrequirements.lock.master requirements.lock.master -# build==1.5.0 calver==2025.10.20 coherent-licensed==0.5.2 diff --git a/containers/watcher/requirements.lock.master b/containers/watcher/requirements.lock.master index 7cfa9716..3c7e735e 100644 --- a/containers/watcher/requirements.lock.master +++ b/containers/watcher/requirements.lock.master @@ -1,739 +1,130 @@ -# -# -# pip-compile --allow-unsafe --constraint=upper-constraints.txt.master --output-file=requirements.lock.master --strip-extras src/watcher/requirements.txt watcher-base/pythonbuilddeps.txt watcher-base/pythondeps.txt -# alembic==1.18.5 - # via - # -c upper-constraints.txt.master - # oslo-db amqp==5.3.1 - # via - # -c upper-constraints.txt.master - # kombu - # oslo-messaging apscheduler==3.11.2 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt attrs==26.1.0 - # via - # -c upper-constraints.txt.master - # jsonschema - # referencing automaton==3.4.0 - # via - # -c upper-constraints.txt.master - # taskflow autopage==0.6.0 - # via - # -c upper-constraints.txt.master - # cliff bcrypt==5.0.0 - # via - # -c upper-constraints.txt.master - # oslo-middleware cachetools==7.1.4 - # via - # -c upper-constraints.txt.master - # oslo-messaging - # taskflow certifi==2026.6.17 - # via requests cffi==2.0.0 - # via - # -c upper-constraints.txt.master - # cryptography charset-normalizer==3.4.7 - # via - # -c upper-constraints.txt.master - # requests cliff==4.15.0 - # via - # -c upper-constraints.txt.master - # gnocchiclient - # osc-lib - # python-ironicclient - # python-observabilityclient - # python-openstackclient cmd2==4.0.0 - # via - # -c upper-constraints.txt.master - # cliff cotyledon==2.2.0 - # via - # -c upper-constraints.txt.master - # oslo-service croniter==6.2.2 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt debtcollector==3.1.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # futurist - # gnocchiclient - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-service - # python-keystoneclient - # python-manilaclient - # taskflow decorator==5.3.1 - # via - # -c upper-constraints.txt.master - # dogpile-cache - # openstacksdk dnspython==2.8.0 - # via - # -c upper-constraints.txt.master - # eventlet dogpile-cache==1.5.0 - # via - # -c upper-constraints.txt.master - # openstacksdk - # oslo-cache - # python-ironicclient eventlet==0.41.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # oslo-service fasteners==0.20 - # via - # -c upper-constraints.txt.master - # oslo-concurrency - # taskflow futurist==3.4.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # gnocchiclient - # oslo-messaging - # oslo-service - # taskflow gnocchiclient==7.2.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt greenlet==3.5.3 - # via - # -c upper-constraints.txt.master - # eventlet - # oslo-service - # sqlalchemy idna==3.18 - # via - # -c upper-constraints.txt.master - # requests importlib-metadata==9.0.0 - # via - # -c upper-constraints.txt.master - # wsme iso8601==2.1.0 - # via - # -c upper-constraints.txt.master - # gnocchiclient - # keystoneauth1 - # openstacksdk - # oslo-utils - # python-openstackclient jinja2==3.1.6 - # via - # -c upper-constraints.txt.master - # oslo-middleware - # oslo-reports jmespath==1.1.0 - # via - # -c upper-constraints.txt.master - # openstacksdk jsonpatch==1.33 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # openstacksdk jsonpointer==3.1.1 - # via - # -c upper-constraints.txt.master - # jsonpatch jsonschema==4.26.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # python-ironicclient - # taskflow jsonschema-specifications==2025.9.1 - # via - # -c upper-constraints.txt.master - # jsonschema keystoneauth1==5.15.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # gnocchiclient - # keystonemiddleware - # openstacksdk - # osc-lib - # python-ironicclient - # python-keystoneclient - # python-manilaclient - # python-observabilityclient keystonemiddleware==13.0.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt kombu==5.6.2 - # via - # -c upper-constraints.txt.master - # oslo-messaging lxml==6.1.1 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt mako==1.3.12 - # via - # -c upper-constraints.txt.master - # alembic - # pecan markdown-it-py==4.2.0 - # via - # -c upper-constraints.txt.master - # rich markupsafe==3.0.3 - # via - # -c upper-constraints.txt.master - # jinja2 - # mako mdurl==0.1.2 - # via - # -c upper-constraints.txt.master - # markdown-it-py microversion-parse==2.1.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt msgpack==1.2.1 - # via - # -c upper-constraints.txt.master - # oslo-serialization netaddr==1.3.0 - # via - # -c upper-constraints.txt.master - # oslo-config - # oslo-utils - # oslo-versionedobjects - # wsme networkx==3.6.1 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # taskflow openstacksdk==4.18.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # osc-lib - # python-ironicclient - # python-openstackclient os-resource-classes==1.1.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt os-service-types==1.8.2 - # via - # -c upper-constraints.txt.master - # keystoneauth1 - # openstacksdk osc-lib==4.7.0 - # via - # -c upper-constraints.txt.master - # python-ironicclient - # python-manilaclient - # python-observabilityclient - # python-openstackclient oslo-cache==4.3.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # -r watcher-base/pythondeps.txt - # keystonemiddleware oslo-concurrency==7.6.1 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # oslo-service oslo-config==10.6.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # keystonemiddleware - # oslo-cache - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-policy - # oslo-reports - # oslo-service - # oslo-upgradecheck - # oslo-versionedobjects - # pycadf - # python-keystoneclient - # python-manilaclient oslo-context==6.5.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # keystonemiddleware - # oslo-log - # oslo-messaging - # oslo-middleware - # oslo-policy - # oslo-versionedobjects oslo-db==18.1.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # -r watcher-base/pythondeps.txt oslo-i18n==6.9.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # keystonemiddleware - # osc-lib - # oslo-cache - # oslo-concurrency - # oslo-config - # oslo-db - # oslo-log - # oslo-middleware - # oslo-policy - # oslo-reports - # oslo-service - # oslo-upgradecheck - # oslo-utils - # oslo-versionedobjects - # python-keystoneclient - # python-openstackclient oslo-log==8.3.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # keystonemiddleware - # oslo-cache - # oslo-messaging - # oslo-metrics - # oslo-service - # oslo-versionedobjects - # python-manilaclient oslo-messaging==18.2.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # oslo-versionedobjects oslo-metrics==0.16.0 - # via - # -c upper-constraints.txt.master - # oslo-messaging oslo-middleware==8.2.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # oslo-messaging oslo-policy==6.0.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # oslo-upgradecheck oslo-reports==3.9.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt oslo-serialization==5.11.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # keystonemiddleware - # oslo-log - # oslo-messaging - # oslo-policy - # oslo-reports - # pycadf - # python-keystoneclient - # python-manilaclient - # taskflow oslo-service==4.8.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # oslo-messaging - # oslo-service oslo-upgradecheck==2.8.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt oslo-utils==10.1.1 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # keystonemiddleware - # osc-lib - # oslo-cache - # oslo-concurrency - # oslo-db - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-policy - # oslo-reports - # oslo-serialization - # oslo-service - # oslo-upgradecheck - # oslo-versionedobjects - # python-ironicclient - # python-keystoneclient - # python-manilaclient - # taskflow oslo-versionedobjects==3.11.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt packaging==26.2 - # via - # -c upper-constraints.txt.master - # kombu - # oslo-utils - # python-keystoneclient - # wheel paste==3.10.1 - # via - # -c upper-constraints.txt.master - # oslo-service pastedeploy==3.1.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # oslo-service pbr==7.0.3 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # -r watcher-base/pythonbuilddeps.txt - # keystonemiddleware - # openstacksdk - # os-resource-classes - # os-service-types - # osc-lib - # oslo-concurrency - # oslo-context - # oslo-i18n - # oslo-log - # oslo-messaging - # oslo-metrics - # oslo-middleware - # oslo-reports - # oslo-utils - # python-ironicclient - # python-keystoneclient - # python-manilaclient - # taskflow pecan==1.8.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt platformdirs==4.10.0 - # via - # -c upper-constraints.txt.master - # openstacksdk - # python-ironicclient prettytable==3.18.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # automaton - # cliff - # oslo-upgradecheck - # python-manilaclient prometheus-client==0.25.0 - # via - # -c upper-constraints.txt.master - # oslo-metrics prompt-toolkit==3.0.52 - # via - # -c upper-constraints.txt.master - # cmd2 psutil==7.2.2 - # via - # -c upper-constraints.txt.master - # openstacksdk - # oslo-reports - # oslo-utils pycadf==4.1.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware pycparser==3.0 - # via - # -c upper-constraints.txt.master - # cffi pydot==4.0.1 - # via - # -c upper-constraints.txt.master - # taskflow pygments==2.20.0 - # via - # -c upper-constraints.txt.master - # rich pyjwt==2.13.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware pymemcache==4.0.0 - # via - # -c upper-constraints.txt.master - # oslo-cache pymysql==1.2.0 - # via - # -c upper-constraints.txt.master - # oslo-db pyparsing==3.3.2 - # via - # -c upper-constraints.txt.master - # oslo-utils - # pydot pyperclip==1.11.0 - # via - # -c upper-constraints.txt.master - # cmd2 python-binary-memcached==0.32.0 - # via - # -c upper-constraints.txt.master - # oslo-cache python-dateutil==2.9.0.post0 - # via - # -c upper-constraints.txt.master - # croniter - # gnocchiclient - # oslo-log python-ironicclient==6.2.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt python-keystoneclient==5.8.0 - # via - # -c upper-constraints.txt.master - # keystonemiddleware - # python-openstackclient python-manilaclient==6.2.0 - # via - # -c upper-constraints.txt.master - # python-openstackclient python-memcached==1.62 - # via - # -c upper-constraints.txt.master - # oslo-cache python-observabilityclient==1.3.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt python-openstackclient==10.2.1 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt pytz==2026.2 - # via - # -c upper-constraints.txt.master - # wsme pyyaml==6.0.3 - # via - # -c upper-constraints.txt.master - # cliff - # openstacksdk - # oslo-config - # oslo-messaging - # oslo-policy - # oslo-utils - # python-ironicclient - # python-observabilityclient redis==8.0.1 - # via - # -c upper-constraints.txt.master - # oslo-cache referencing==0.37.0 - # via - # -c upper-constraints.txt.master - # jsonschema - # jsonschema-specifications repoze-lru==0.8 - # via - # -c upper-constraints.txt.master - # routes requests==2.34.2 - # via - # -c upper-constraints.txt.master - # keystoneauth1 - # keystonemiddleware - # osc-lib - # oslo-config - # oslo-policy - # python-ironicclient - # python-keystoneclient - # python-manilaclient - # python-openstackclient rfc3986==2.0.0 - # via - # -c upper-constraints.txt.master - # oslo-config rich==15.0.0 - # via - # -c upper-constraints.txt.master - # cmd2 - # rich-argparse rich-argparse==1.8.0 - # via - # -c upper-constraints.txt.master - # cmd2 routes==2.5.1 - # via - # -c upper-constraints.txt.master - # oslo-service rpds-py==2026.5.1 - # via - # -c upper-constraints.txt.master - # jsonschema - # referencing setproctitle==1.3.7 - # via - # -c upper-constraints.txt.master - # cotyledon simplegeneric==0.8.1 - # via - # -c upper-constraints.txt.master - # wsme six==1.17.0 - # via - # -c upper-constraints.txt.master - # python-binary-memcached - # python-dateutil - # routes sqlalchemy==2.0.51 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # alembic - # oslo-db statsd==4.0.1 - # via - # -c upper-constraints.txt.master - # oslo-middleware stevedore==5.9.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # cliff - # dogpile-cache - # keystoneauth1 - # osc-lib - # oslo-config - # oslo-db - # oslo-messaging - # oslo-middleware - # oslo-policy - # python-ironicclient - # python-keystoneclient - # python-openstackclient - # taskflow taskflow==6.3.0 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt tenacity==9.1.4 - # via - # -c upper-constraints.txt.master - # taskflow testresources==2.1.2 - # via - # -c upper-constraints.txt.master - # oslo-db testscenarios==0.6.2 - # via - # -c upper-constraints.txt.master - # oslo-db typing-extensions==4.15.0 - # via - # -c upper-constraints.txt.master - # alembic - # automaton - # cotyledon - # keystoneauth1 - # os-service-types - # oslo-context - # oslo-middleware - # referencing - # sqlalchemy tzdata==2026.2 - # via - # -c upper-constraints.txt.master - # kombu tzlocal==5.4.3 - # via - # -c upper-constraints.txt.master - # apscheduler uhashring==2.4 - # via - # -c upper-constraints.txt.master - # python-binary-memcached ujson==5.13.0 - # via - # -c upper-constraints.txt.master - # gnocchiclient urllib3==2.7.0 - # via - # -c upper-constraints.txt.master - # requests vine==5.1.0 - # via - # -c upper-constraints.txt.master - # amqp - # kombu wcwidth==0.8.1 - # via - # -c upper-constraints.txt.master - # prettytable - # prompt-toolkit webob==1.8.10 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt - # keystonemiddleware - # microversion-parse - # oslo-messaging - # oslo-middleware - # oslo-service - # pecan - # wsme wheel==0.47.0 - # via -r watcher-base/pythonbuilddeps.txt wrapt==2.2.2 - # via - # -c upper-constraints.txt.master - # debtcollector wsme==0.12.1 - # via - # -c upper-constraints.txt.master - # -r src/watcher/requirements.txt yappi==1.7.6 - # via - # -c upper-constraints.txt.master - # oslo-service zipp==4.1.0 - # via - # -c upper-constraints.txt.master - # importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/docs/TESTING.md b/docs/TESTING.md index d76bb1a3..4458ba4c 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -19,5 +19,15 @@ The linter environment checks tracked Containerfiles and shell content through pre-commit. Use the narrowest applicable command first, then run both environments before proposing changes to build or source-maintenance behavior. +Regenerate dependency locks from committed source pins with Python 3.12: + +```console +uvx --python 3.12 tox -e update-lockfiles +``` + +The environment rejects other Python minor versions because environment +markers can resolve a different package set. A clean regeneration must leave no +tracked diff under `containers/`. + Additional change-specific validation is described in the [developer guide](developer-guide.md#ci-path-filtering). diff --git a/docs/developer-guide.md b/docs/developer-guide.md index df913c69..eecd7cd6 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -253,12 +253,20 @@ Auto-cloned repos in `src/` are cleaned up automatically on exit. Pre-existing checkouts in `src/` are used as-is and not removed. To regenerate lockfiles without updating pinned hashes (steps 1--3 are -skipped; repos are cloned at the existing pinned hashes instead): +skipped; repos are cloned at the existing pinned hashes instead), use the +canonical Python 3.12 environment: ```bash -STREAM=master SKIP_HASH_UPDATE=1 ./build.sh update-sources +STREAM=master uvx --python 3.12 tox -e update-lockfiles -- ``` +Python environment markers are evaluated by the generator interpreter, so +other Python minor versions can produce a different dependency set. The tox +environment rejects non-3.12 interpreters and pins the generator tool versions. +Generated lock headers, resolver annotations, and package-index directives are +removed because they describe the generation environment rather than the +resolved dependency set. + ### Building images ```bash diff --git a/tox.ini b/tox.ini index 2a9ebb16..988d08a6 100644 --- a/tox.ini +++ b/tox.ini @@ -24,11 +24,11 @@ passenv = BUILD_LOGS_DIR UPDATE_LOCKFILES_TARGETS # pin to pip==26.1.2 as 26.2 has broken pip-tools -# pin pip-tools<7.6.1: pybuild-deps 0.5.0 passes generate_hashes to +# pin pip-tools==7.6.0: pybuild-deps 0.5.0 passes generate_hashes to # OutputWriter which was removed in pip-tools 7.6.1 deps = - pip-tools<7.6.1 - pybuild-deps + pip-tools==7.6.0 + pybuild-deps==0.5.0 pip==26.1.2 allowlist_externals = bash @@ -48,15 +48,24 @@ commands = pre-commit run -a --show-diff-on-failure [testenv:update-sources] +description = Refresh source pins and generated lockfiles +setenv = + XDG_CACHE_HOME = {envtmpdir}/cache +commands_pre = + python -c "import sys; assert sys.version_info[:2] == (3, 12), 'dependency generation requires Python 3.12'" commands = - bash -c "rm -f ~/.cache/pybuild-deps/find-build-deps" + bash -c "rm -f {env:XDG_CACHE_HOME}/pybuild-deps/find-build-deps" bash {toxinidir}/build.sh update-sources {posargs:all} [testenv:update-lockfiles] +description = Refresh generated lockfiles without advancing source pins setenv = + XDG_CACHE_HOME = {envtmpdir}/cache SKIP_HASH_UPDATE = 1 +commands_pre = + {[testenv:update-sources]commands_pre} commands = - bash -c "rm -f ~/.cache/pybuild-deps/find-build-deps" + bash -c "rm -f {env:XDG_CACHE_HOME}/pybuild-deps/find-build-deps" bash {toxinidir}/build.sh update-sources {posargs:all} [testenv:build] From 40b6f2bb645113517fd47f5b153f5ce8dbdeb185 Mon Sep 17 00:00:00 2001 From: Sean Mooney Date: Wed, 12 Aug 2026 17:22:48 +0100 Subject: [PATCH 2/2] Make source updates atomic Source maintenance currently resolves and applies repositories one at a time. A late failure can therefore leave tracked source pins partially updated, and a moving branch can provide different content during one invocation. Preflight every selected source record and freeze each effective commit before tracked mutation. Retain the fetched objects for the complete run, record their authority in a deterministic manifest, and install source updates atomically. Replace the shell test harness with the existing stdlib unittest coverage for failure isolation, stream safety, checkout ownership, and multi-target scope. Assisted-By: Pi gpt-5.6-sol Signed-off-by: Sean Mooney (cherry picked from commit 0a14e6ae897ac39a0e065ad27e0ebc263231d0ce) --- .github/workflows/test-update-sources.yml | 8 + build.sh | 389 +++++++++++----- docs/TESTING.md | 12 +- docs/developer-guide.md | 21 +- tests/test_update_sources.py | 533 ++++++++++++++++++++++ tests/test_update_sources.sh | 529 --------------------- tox.ini | 9 +- 7 files changed, 836 insertions(+), 665 deletions(-) create mode 100644 tests/test_update_sources.py delete mode 100755 tests/test_update_sources.sh diff --git a/.github/workflows/test-update-sources.yml b/.github/workflows/test-update-sources.yml index e93daf23..75532ff9 100644 --- a/.github/workflows/test-update-sources.yml +++ b/.github/workflows/test-update-sources.yml @@ -33,3 +33,11 @@ jobs: - name: Require reproducible generated files run: git diff --exit-code -- containers/ + + - name: Preserve frozen source references + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: frozen-source-refs + path: .tmp/source-maintenance/frozen-source-refs.master.tsv + if-no-files-found: ignore diff --git a/build.sh b/build.sh index a3f934a0..9938efd9 100755 --- a/build.sh +++ b/build.sh @@ -103,6 +103,8 @@ DEFAULT_STREAM="${DEFAULT_STREAM:-master}" SKIP_HASH_UPDATE="${SKIP_HASH_UPDATE:-}" PIP_NO_BINARY="${PIP_NO_BINARY:-}" PARALLEL="${PARALLEL:-$(nproc)}" +SOURCE_REFS_STREAM="${STREAM//\//%2F}" +SOURCE_REFS_MANIFEST="${REPO_ROOT}/.tmp/source-maintenance/frozen-source-refs.${SOURCE_REFS_STREAM}.tsv" # Discover all buildable images from the directory structure. discover_images() { @@ -174,14 +176,18 @@ image_tag_args() { echo "${args}" } -# Track which repos were auto-cloned so we can clean up +# Track temporary source and frozen-ref repositories so they can be cleaned. declare -A _AUTO_CLONED=() +declare -A _FROZEN_REPOSITORIES=() -# Remove auto-cloned sources on exit cleanup_auto() { - for src_dir in "${!_AUTO_CLONED[@]}"; do - echo "--- Removing auto-cloned source: ${src_dir} ---" - rm -rf "${src_dir}" + local path + for path in "${!_AUTO_CLONED[@]}"; do + echo "--- Removing auto-cloned source: ${path} ---" + rm -rf "${path}" + done + for path in "${_FROZEN_REPOSITORIES[@]}"; do + rm -rf "${path}" done } trap cleanup_auto EXIT @@ -441,101 +447,280 @@ resolve_targets() { echo "${resolved[@]}" } -# Clone a repo at a branch tip (or tag) and store the resolved commit hash -# in _CLONE_RESULT. Must NOT be called via command substitution ($(...)) -# because _AUTO_CLONED assignments would be lost in the subshell. -# If the destination already exists, use it as-is (same policy as clone_at_hash). -# Args: -clone_at_branch() { - local dest="$1" - local url="$2" - local branch="$3" +# Resolve an expression without losing failures in command substitutions. +declare -a _RESOLVED_TARGETS=() +resolve_targets_array() { + local output + if ! output=$(resolve_targets "$@"); then + return 1 + fi + _RESOLVED_TARGETS=(${output}) +} - if [[ -d "${dest}" ]]; then - echo "--- Using existing source: ${dest} ---" - else - mkdir -p "$(dirname "${dest}")" - echo "--- Cloning ${url} (${branch}) into ${dest} ---" - if ! git clone --branch "${branch}" "${url}" "${dest}" 2>/dev/null; then - git clone "${url}" "${dest}" 2>/dev/null - git -C "${dest}" checkout "${branch}" +# Collect selected source manifests in deterministic target order. The parallel +# arrays describe the manifest, source checkout directory, and project context. +declare -a _SOURCE_FILES=() +declare -a _SOURCE_DIRS=() +declare -a _SOURCE_PROJECT_DIRS=() +collect_source_scopes() { + local -a targets + local img project sources_file + declare -A seen=() + + resolve_targets_array "$@" || return 1 + targets=("${_RESOLVED_TARGETS[@]}") + _SOURCE_FILES=() + _SOURCE_DIRS=() + _SOURCE_PROJECT_DIRS=() + + for img in "${targets[@]}"; do + project="$(project_name "${img}")" + if [[ -z "${project}" ]]; then + [[ "${img}" == "base" ]] || continue + sources_file="${CONTAINERS_DIR}/base/sources.txt" + if [[ -f "${sources_file}" && -z "${seen[${sources_file}]:-}" ]]; then + seen["${sources_file}"]=1 + _SOURCE_FILES+=("${sources_file}") + _SOURCE_DIRS+=("${CONTAINERS_DIR}/base/src") + _SOURCE_PROJECT_DIRS+=("${CONTAINERS_DIR}/base") + fi + continue + fi + + sources_file="${CONTAINERS_DIR}/${project}/sources.txt" + if [[ -f "${sources_file}" && -z "${seen[${sources_file}]:-}" ]]; then + seen["${sources_file}"]=1 + _SOURCE_FILES+=("${sources_file}") + _SOURCE_DIRS+=("${CONTAINERS_DIR}/${project}/src") + _SOURCE_PROJECT_DIRS+=("${CONTAINERS_DIR}/${project}") fi - _AUTO_CLONED["${dest}"]=1 + + sources_file="${CONTAINERS_DIR}/${img}/sources.txt" + if [[ -f "${sources_file}" && -z "${seen[${sources_file}]:-}" ]]; then + seen["${sources_file}"]=1 + _SOURCE_FILES+=("${sources_file}") + _SOURCE_DIRS+=("${CONTAINERS_DIR}/${img}/src") + _SOURCE_PROJECT_DIRS+=("${CONTAINERS_DIR}/${project}") + fi + done +} + +source_record_key() { + printf '%s\x1f%s\x1f%s\x1f%s' "$1" "$2" "$3" "$4" +} + +source_ref_key() { + printf '%s\x1f%s' "$1" "$2" +} + +declare -A _FROZEN_COMMITS=() +declare -A _FROZEN_AUTHORITIES=() +declare -A _FROZEN_REF_COMMITS=() + +# Fetch one declared ref into a temporary bare repository and retain its exact +# commit for the rest of this process. This prevents a moving branch from +# changing inputs after preflight. +freeze_remote_ref() { + local url="$1" + local ref="$2" + local ref_key repository + ref_key="$(source_ref_key "${url}" "${ref}")" + if [[ -n "${_FROZEN_REF_COMMITS[${ref_key}]:-}" ]]; then + _FREEZE_RESULT="${_FROZEN_REF_COMMITS[${ref_key}]}" + return fi - _CLONE_RESULT=$(git -C "${dest}" rev-parse HEAD) + repository=$(mktemp -d) + git -C "${repository}" init --quiet --bare + if ! git -C "${repository}" fetch --quiet "${url}" "${ref}"; then + echo "ERROR: Could not freeze ref '${ref}' for ${url}" >&2 + rm -rf "${repository}" + return 1 + fi + if ! _FREEZE_RESULT=$(git -C "${repository}" rev-parse --verify 'FETCH_HEAD^{commit}'); then + echo "ERROR: Ref '${ref}' for ${url} does not resolve to a commit" >&2 + rm -rf "${repository}" + return 1 + fi + _FROZEN_REF_COMMITS["${ref_key}"]="${_FREEZE_RESULT}" + _FROZEN_REPOSITORIES["${ref_key}"]="${repository}" } -# Update pinned hashes in a single sources.txt file for the given stream. -# Clones source repos at the branch tip to resolve hashes, and extracts -# upper-constraints.txt when encountered. -# Args: +validate_stream_name() { + local stream="$1" + local component + local -a components + if [[ ! "${stream}" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]*$ ]] || \ + [[ "${stream}" == */ || "${stream}" == *//* ]]; then + echo "ERROR: Unsafe stream name '${stream}'" >&2 + return 1 + fi + IFS='/' read -ra components <<< "${stream}" + for component in "${components[@]}"; do + if [[ "${component}" == "." || "${component}" == ".." ]]; then + echo "ERROR: Unsafe stream name '${stream}'" >&2 + return 1 + fi + done +} + +# Resolve and record every selected source before the first tracked mutation. +freeze_source_refs() { + local stream="${!#}" + local targets_args=("${@:1:$#-1}") + local manifest_dir manifest_tmp + local index line entry_stream name url branch pinned_hash extra + local sources_file src_dir relative_file record_key authority frozen + + validate_stream_name "${stream}" || return 1 + collect_source_scopes "${targets_args[@]}" || return 1 + _FROZEN_COMMITS=() + _FROZEN_AUTHORITIES=() + _FROZEN_REF_COMMITS=() + _FROZEN_REPOSITORIES=() + + manifest_dir="$(dirname "${SOURCE_REFS_MANIFEST}")" + if [[ "${manifest_dir}" != "${REPO_ROOT}/.tmp/source-maintenance" ]]; then + echo "ERROR: Frozen source manifest escaped repository temporary state" >&2 + return 1 + fi + mkdir -p "${manifest_dir}" + rm -f "${SOURCE_REFS_MANIFEST}" + manifest_tmp=$(mktemp "${manifest_dir}/.frozen-source-refs.XXXXXX") + printf 'source_file\tstream\tname\turl\tdeclared_ref\tcommitted_pin\tfrozen_commit\tauthority\n' > "${manifest_tmp}" + + for index in "${!_SOURCE_FILES[@]}"; do + sources_file="${_SOURCE_FILES[${index}]}" + src_dir="${_SOURCE_DIRS[${index}]}" + relative_file="${sources_file#"${REPO_ROOT}/"}" + while IFS= read -r line; do + [[ -z "${line}" || "${line}" == \#* ]] && continue + read -r entry_stream name url branch pinned_hash extra <<< "${line}" + [[ "${entry_stream}" == "${stream}" ]] || continue + if [[ -z "${name}" || -z "${url}" || -z "${branch}" || -z "${pinned_hash}" || -n "${extra:-}" ]]; then + echo "ERROR: Malformed source record in ${relative_file}: ${line}" >&2 + rm -f "${manifest_tmp}" + return 1 + fi + + record_key="$(source_record_key "${sources_file}" "${name}" "${url}" "${branch}")" + if [[ "${name}" != "upper-constraints" && -d "${src_dir}/${name}" ]]; then + local checkout_root + checkout_root=$(git -C "${src_dir}/${name}" rev-parse --show-toplevel 2>/dev/null || true) + if [[ -z "${checkout_root}" || "$(realpath -e "${checkout_root}")" != "$(realpath -e "${src_dir}/${name}")" ]] || \ + ! frozen=$(git -C "${src_dir}/${name}" rev-parse --verify 'HEAD^{commit}' 2>/dev/null); then + echo "ERROR: Pre-existing source is not a Git checkout: ${src_dir}/${name}" >&2 + rm -f "${manifest_tmp}" + return 1 + fi + authority="pre-existing-checkout" + else + if [[ -n "${SKIP_HASH_UPDATE}" ]]; then + freeze_remote_ref "${url}" "${pinned_hash}" || { + rm -f "${manifest_tmp}" + return 1 + } + authority="committed-pin" + else + freeze_remote_ref "${url}" "${branch}" || { + rm -f "${manifest_tmp}" + return 1 + } + authority="declared-ref" + fi + frozen="${_FREEZE_RESULT}" + fi + + _FROZEN_COMMITS["${record_key}"]="${frozen}" + _FROZEN_AUTHORITIES["${record_key}"]="${authority}" + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "${relative_file}" "${entry_stream}" "${name}" "${url}" \ + "${branch}" "${pinned_hash}" "${frozen}" "${authority}" \ + >> "${manifest_tmp}" + done < "${sources_file}" + done + + mv "${manifest_tmp}" "${SOURCE_REFS_MANIFEST}" + echo "--- Frozen source references: ${SOURCE_REFS_MANIFEST} ---" +} + +# Materialize one source declaration from its frozen preflight repository and +# update its maintained pin only in advancement mode. update_sources_file() { local sources_file="$1" local stream="$2" local src_dir="$3" local project_dir="$4" - - if [[ ! -f "${sources_file}" ]]; then - return - fi - - local tmp_file - tmp_file=$(mktemp) + local tmp_file line entry_stream name url branch pinned_hash extra + local record_key ref_key frozen authority repository output_tmp local updated=0 + tmp_file=$(mktemp) while IFS= read -r line; do - # Preserve comments and blank lines if [[ -z "${line}" || "${line}" == \#* ]]; then echo "${line}" >> "${tmp_file}" continue fi - read -r entry_stream name url branch pinned_hash <<< "${line}" - - # Only update entries for the requested stream + read -r entry_stream name url branch pinned_hash extra <<< "${line}" if [[ "${entry_stream}" != "${stream}" ]]; then echo "${line}" >> "${tmp_file}" continue fi - local new_hash - if [[ "${name}" == "upper-constraints" ]]; then - # Clone without checkout, resolve hash from branch, extract file - local uc_tmp - uc_tmp=$(mktemp -d) - git clone --no-checkout "${url}" "${uc_tmp}" 2>/dev/null - new_hash=$(git -C "${uc_tmp}" rev-parse --verify "origin/${branch}" 2>/dev/null \ - || git -C "${uc_tmp}" rev-parse --verify "${branch}" 2>/dev/null) - git -C "${uc_tmp}" checkout "${new_hash}" -- upper-constraints.txt - cp "${uc_tmp}/upper-constraints.txt" "${project_dir}/${UPSTREAM_CONSTRAINTS}.${stream}" - rm -rf "${uc_tmp}" - elif [[ -d "${src_dir}/${name}" ]]; then - # Pre-existing checkout — use it for pip-compile but don't update the hash + record_key="$(source_record_key "${sources_file}" "${name}" "${url}" "${branch}")" + frozen="${_FROZEN_COMMITS[${record_key}]:-}" + authority="${_FROZEN_AUTHORITIES[${record_key}]:-}" + if [[ -z "${frozen}" || -z "${authority}" ]]; then + echo "ERROR: Missing frozen source record for ${name} in ${sources_file}" >&2 + rm -f "${tmp_file}" + return 1 + fi + + if [[ "${authority}" == "pre-existing-checkout" ]]; then echo " ${name}: skipped (pre-existing checkout at ${src_dir}/${name})" - new_hash="${pinned_hash}" else - clone_at_branch "${src_dir}/${name}" "${url}" "${branch}" - new_hash="${_CLONE_RESULT}" - fi + if [[ "${authority}" == "committed-pin" ]]; then + ref_key="$(source_ref_key "${url}" "${pinned_hash}")" + else + ref_key="$(source_ref_key "${url}" "${branch}")" + fi + repository="${_FROZEN_REPOSITORIES[${ref_key}]:-}" + if [[ -z "${repository}" || ! -d "${repository}" ]]; then + echo "ERROR: Missing frozen repository for ${name}" >&2 + rm -f "${tmp_file}" + return 1 + fi - if [[ -z "${new_hash}" ]]; then - echo "ERROR: Could not resolve ref '${branch}' for ${url}" >&2 - rm "${tmp_file}" - return 1 + if [[ "${name}" == "upper-constraints" ]]; then + output_tmp=$(mktemp "${project_dir}/.${UPSTREAM_CONSTRAINTS}.${stream}.XXXXXX") + if ! git -C "${repository}" show "${frozen}:upper-constraints.txt" > "${output_tmp}"; then + rm -f "${output_tmp}" "${tmp_file}" + return 1 + fi + mv "${output_tmp}" "${project_dir}/${UPSTREAM_CONSTRAINTS}.${stream}" + else + mkdir -p "${src_dir}" + echo "--- Materializing ${url} at ${frozen} into ${src_dir}/${name} ---" + git -C "${src_dir}" init --quiet "${name}" + _AUTO_CLONED["${src_dir}/${name}"]=1 + git -C "${src_dir}/${name}" fetch --quiet "${repository}" "${frozen}" + git -C "${src_dir}/${name}" checkout --quiet --detach FETCH_HEAD + fi fi - if [[ "${new_hash}" != "${pinned_hash}" ]]; then - echo " ${name}: ${pinned_hash:-} → ${new_hash} (${branch})" + if [[ -z "${SKIP_HASH_UPDATE}" && "${authority}" != "pre-existing-checkout" && "${frozen}" != "${pinned_hash}" ]]; then + echo " ${name}: ${pinned_hash} → ${frozen} (${branch})" + pinned_hash="${frozen}" updated=1 fi - echo "${entry_stream} ${name} ${url} ${branch} ${new_hash}" >> "${tmp_file}" + echo "${entry_stream} ${name} ${url} ${branch} ${pinned_hash}" >> "${tmp_file}" done < "${sources_file}" if [[ ${updated} -eq 1 ]]; then install -m 644 "${tmp_file}" "${sources_file}" else - echo " (no changes)" + echo " (no pin changes)" fi rm "${tmp_file}" } @@ -920,71 +1105,30 @@ ensure_sources_for_targets() { done } -# Update sources.txt files for targets in scope. -# Clones source repos at branch tips to resolve hashes, fetches -# upper-constraints.txt, and updates pinned hashes in sources.txt. +# Materialize all preflight-frozen sources and update maintained pins only when +# SKIP_HASH_UPDATE is unset. update_sources() { local stream="${!#}" - local targets_args=("${@:1:$#-1}") + local index sources_file if [[ -z "${stream}" ]]; then echo "ERROR: STREAM is required for update-sources." >&2 echo " Example: STREAM=master ./build.sh update-sources watcher" >&2 return 1 fi + if [[ ${#_SOURCE_FILES[@]} -eq 0 ]]; then + echo "ERROR: Source preflight did not collect any manifests" >&2 + return 1 + fi - local targets - targets=($(resolve_targets "${targets_args[@]}")) - - declare -A projects_seen - - for img in "${targets[@]}"; do - local project - project="$(project_name "${img}")" - - # Base container: flat layout, sources.txt directly in containers/base/ - if [[ -z "${project}" ]]; then - if [[ "${img}" == "base" ]] && [[ -z "${projects_seen[base]:-}" ]]; then - projects_seen["base"]=1 - local base_sources="${CONTAINERS_DIR}/base/sources.txt" - if [[ -f "${base_sources}" ]]; then - echo "--- Updating ${base_sources} (stream: ${stream}) ---" - if ! update_sources_file "${base_sources}" "${stream}" \ - "${CONTAINERS_DIR}/base/src" \ - "${CONTAINERS_DIR}/base"; then - echo "ERROR: Failed to update ${base_sources}" >&2 - return 1 - fi - fi - fi - continue - fi - - # Project-level sources.txt (only process once per project) - if [[ -z "${projects_seen[$project]:-}" ]]; then - projects_seen["${project}"]=1 - local project_sources="${CONTAINERS_DIR}/${project}/sources.txt" - if [[ -f "${project_sources}" ]]; then - echo "--- Updating ${project_sources} (stream: ${stream}) ---" - if ! update_sources_file "${project_sources}" "${stream}" \ - "${CONTAINERS_DIR}/${project}/src" \ - "${CONTAINERS_DIR}/${project}"; then - echo "ERROR: Failed to update ${project_sources}" >&2 - return 1 - fi - fi - fi - - # Image-level sources.txt - local image_sources="${CONTAINERS_DIR}/${img}/sources.txt" - if [[ -f "${image_sources}" ]]; then - echo "--- Updating ${image_sources} (stream: ${stream}) ---" - if ! update_sources_file "${image_sources}" "${stream}" \ - "${CONTAINERS_DIR}/${img}/src" \ - "${CONTAINERS_DIR}/${project}"; then - echo "ERROR: Failed to update ${image_sources}" >&2 - return 1 - fi + for index in "${!_SOURCE_FILES[@]}"; do + sources_file="${_SOURCE_FILES[${index}]}" + echo "--- Updating ${sources_file} (stream: ${stream}) ---" + if ! update_sources_file "${sources_file}" "${stream}" \ + "${_SOURCE_DIRS[${index}]}" \ + "${_SOURCE_PROJECT_DIRS[${index}]}"; then + echo "ERROR: Failed to update ${sources_file}" >&2 + return 1 fi done } @@ -1104,10 +1248,9 @@ case "${ACTION}" in update-sources) if [[ -n "${SKIP_HASH_UPDATE}" ]]; then echo "=== Skipping hash update (SKIP_HASH_UPDATE is set) ===" - ensure_sources_for_targets "${TARGETS[@]}" "${STREAM}" - else - update_sources "${TARGETS[@]}" "${STREAM}" fi + freeze_source_refs "${TARGETS[@]}" "${STREAM}" + update_sources "${TARGETS[@]}" "${STREAM}" # Generate lockfiles and metadata echo "" diff --git a/docs/TESTING.md b/docs/TESTING.md index 4458ba4c..9c50e7e2 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -1,13 +1,15 @@ # Testing -Run the repository's current test environment with: +Run the stdlib unittest suite through any supported alias: ```console +tox -e unit tox -e test +tox -e py3 ``` -This executes `tests/test_update_sources.sh`, which exercises source update and -lock-generation behavior with local temporary Git repositories. +The suite exercises source update and lock-generation behavior with local +temporary Git repositories. Run repository lint checks with: @@ -27,7 +29,9 @@ uvx --python 3.12 tox -e update-lockfiles The environment rejects other Python minor versions because environment markers can resolve a different package set. A clean regeneration must leave no -tracked diff under `containers/`. +tracked diff under `containers/`. Inspect the corresponding +`.tmp/source-maintenance/frozen-source-refs..tsv` manifest to confirm +that pinned regeneration used `committed-pin` authority throughout. Additional change-specific validation is described in the [developer guide](developer-guide.md#ci-path-filtering). diff --git a/docs/developer-guide.md b/docs/developer-guide.md index eecd7cd6..7926331e 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -241,16 +241,27 @@ STREAM=master ./build.sh update-sources ``` This will: -1. Clone each source repo at the branch tip to resolve the latest commit hash. -2. Update `sources.txt` with the new pinned hashes. -3. Fetch `upper-constraints.txt` from the requirements repo. +1. Resolve every selected branch or tag to one exact commit before mutation. +2. Update `sources.txt` with the frozen commits. +3. Fetch `upper-constraints.txt` from the same frozen input set. 4. Generate `rpms.in.yaml` from all `bindeps.txt` + `builddeps.txt` files. 5. Run `pip-compile` to generate `requirements.lock.`. 6. Run `pybuild-deps compile` to generate `buildrequirements.lock.`. 7. Create default-stream symlinks if `STREAM == DEFAULT_STREAM`. -Auto-cloned repos in `src/` are cleaned up automatically on exit. -Pre-existing checkouts in `src/` are used as-is and not removed. +The preflight record is written to +`.tmp/source-maintenance/frozen-source-refs..tsv`. It records each +source manifest, declared ref, committed pin, frozen commit, and authority. +Advancing runs use `declared-ref`; pinned runs use `committed-pin`; intentional +Git checkouts already under `src/` use `pre-existing-checkout`. A slash in a +stream name is encoded as `%2F`, and unsafe stream names are rejected before +filesystem mutation. + +All selected records must pass preflight before a tracked source manifest is +changed. The fetched repositories are retained for the complete run so a moving +branch cannot provide different content after preflight. Auto-created source +checkouts are cleaned up on exit. Pre-existing checkouts are used as-is and are +not removed. To regenerate lockfiles without updating pinned hashes (steps 1--3 are skipped; repos are cloned at the existing pinned hashes instead), use the diff --git a/tests/test_update_sources.py b/tests/test_update_sources.py new file mode 100644 index 00000000..2d57b45e --- /dev/null +++ b/tests/test_update_sources.py @@ -0,0 +1,533 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Tests for build.sh update-sources behavior.""" + +import csv +import os +import pathlib +import subprocess +import tempfile +import unittest + + +class UpdateSourcesTest(unittest.TestCase): + def setUp(self): + self.repo_root = pathlib.Path(__file__).resolve().parents[1] + tmp_root = self.repo_root / ".tmp" + tmp_root.mkdir(exist_ok=True) + self.temporary_directory = tempfile.TemporaryDirectory( + dir=tmp_root, prefix="update-sources-test." + ) + self.addCleanup(self.temporary_directory.cleanup) + self.test_root = pathlib.Path(self.temporary_directory.name) + upstream_root = self.test_root / "upstream" + upstream_root.mkdir() + + self.upstream_requirements = upstream_root / "requirements.git" + self.requirements_old, self.requirements_new = self.create_remote( + self.upstream_requirements, + [ + {"upper-constraints.txt": "six==1.17.0\n"}, + {"upper-constraints.txt": ("six==1.17.0\npbr==7.0.3\n")}, + ], + ) + self.upstream_service = upstream_root / "test-svc.git" + self.service_old, self.service_new = self.create_remote( + self.upstream_service, + [ + {"requirements.txt": "six\npbr\n"}, + {"requirements.txt": "six\npbr\n# v2\n"}, + ], + ) + (self.test_root / "build.sh").symlink_to(self.repo_root / "build.sh") + self.project_root = self.test_root / "containers" / "test-svc" + image_root = self.project_root / "test-svc" + (self.project_root / "src").mkdir(parents=True) + (image_root / "src").mkdir(parents=True) + self.write_sources( + "master upper-constraints " + f"{self.upstream_requirements} master {self.requirements_old}\n" + "master test-svc " + f"{self.upstream_service} master {self.service_old}\n" + ) + (image_root / "Containerfile").write_text( + "FROM scratch\n", encoding="utf-8" + ) + (image_root / "bindeps.txt").write_text("python3\n", encoding="utf-8") + (image_root / "builddeps.txt").write_text("gcc\n", encoding="utf-8") + (image_root / "pythondeps.txt").touch() + (image_root / "pythonbuilddeps.txt").touch() + + self.additional_services = {} + for name in ("test-svc2", "test-svc3"): + remote = upstream_root / f"{name}.git" + old, new = self.create_remote( + remote, + [ + {"requirements.txt": "six\npbr\n"}, + {"requirements.txt": "six\npbr\n# v2\n"}, + ], + ) + self.additional_services[name] = (remote, old, new) + self.create_project_fixture(name, remote, old) + + self.manifest_path = ( + self.test_root + / ".tmp/source-maintenance/frozen-source-refs.master.tsv" + ) + + def run_command(self, command, cwd=None): + result = subprocess.run( + command, + cwd=cwd, + check=False, + capture_output=True, + text=True, + ) + if result.returncode != 0: + self.fail( + f"command failed with {result.returncode}: {command}\n" + f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" + ) + return result.stdout.strip() + + def create_remote(self, destination, commits): + work = self.test_root / f"work-{destination.stem}" + self.run_command(["git", "init", "-b", "master", str(work)]) + self.run_command( + ["git", "config", "user.email", "test@example.com"], work + ) + self.run_command(["git", "config", "user.name", "Test"], work) + self.run_command(["git", "config", "commit.gpgsign", "false"], work) + hashes = [] + for index, files in enumerate(commits, start=1): + for name, content in files.items(): + (work / name).write_text(content, encoding="utf-8") + self.run_command(["git", "add", "-A"], work) + self.run_command(["git", "commit", "-m", f"v{index}"], work) + hashes.append(self.run_command(["git", "rev-parse", "HEAD"], work)) + self.run_command( + ["git", "clone", "--bare", str(work), str(destination)] + ) + return hashes[0], hashes[-1] + + def write_sources(self, content): + (self.project_root / "sources.txt").write_text( + content, encoding="utf-8" + ) + + def create_project_fixture(self, name, remote, pinned_hash): + project_root = self.test_root / "containers" / name + image_root = project_root / name + (project_root / "src").mkdir(parents=True) + (image_root / "src").mkdir(parents=True) + (project_root / "sources.txt").write_text( + "master upper-constraints " + f"{self.upstream_requirements} master {self.requirements_old}\n" + f"master {name} {remote} master {pinned_hash}\n", + encoding="utf-8", + ) + (image_root / "Containerfile").write_text( + "FROM scratch\n", encoding="utf-8" + ) + (image_root / "bindeps.txt").write_text("python3\n", encoding="utf-8") + (image_root / "builddeps.txt").write_text("gcc\n", encoding="utf-8") + (image_root / "pythondeps.txt").touch() + (image_root / "pythonbuilddeps.txt").touch() + + def run_update_result(self, *targets, **environment): + command_environment = os.environ.copy() + command_environment.update({"STREAM": "master"}) + command_environment.update(environment) + if not targets: + targets = ("test-svc",) + result = subprocess.run( + ["bash", "./build.sh", "update-sources", *targets], + cwd=self.test_root, + check=False, + capture_output=True, + text=True, + env=command_environment, + ) + (self.test_root / "build.log").write_text( + result.stdout + result.stderr, encoding="utf-8" + ) + return result + + def run_update(self, *targets, **environment): + result = self.run_update_result(*targets, **environment) + if result.returncode != 0: + self.fail( + f"update-sources failed with {result.returncode}:\n" + f"{result.stdout}{result.stderr}" + ) + return result.stdout + result.stderr + + def manifest_for_stream(self, stream): + filename = f"frozen-source-refs.{stream.replace('/', '%2F')}.tsv" + return self.test_root / ".tmp/source-maintenance" / filename + + def manifest_rows(self): + with self.manifest_path.open(encoding="utf-8", newline="") as stream: + return list(csv.DictReader(stream, delimiter="\t")) + + def source_field(self, name, field, project_root=None): + if project_root is None: + project_root = self.project_root + for line in ( + (project_root / "sources.txt") + .read_text(encoding="utf-8") + .splitlines() + ): + columns = line.split() + if columns[:2] == ["master", name]: + return columns[field - 1] + self.fail(f"source entry was not found: {name}") + + def test_updates_hashes_to_frozen_branch_tips(self): + self.run_update() + + self.assertEqual( + self.requirements_new, + self.source_field("upper-constraints", 5), + ) + self.assertEqual(self.service_new, self.source_field("test-svc", 5)) + rows = self.manifest_rows() + self.assertEqual( + ["upper-constraints", "test-svc"], [row["name"] for row in rows] + ) + self.assertEqual( + [self.requirements_new, self.service_new], + [row["frozen_commit"] for row in rows], + ) + self.assertEqual({"declared-ref"}, {row["authority"] for row in rows}) + + def test_fetches_upper_constraints(self): + self.run_update() + + constraints = ( + self.project_root / "upper-constraints.txt.master" + ).read_text(encoding="utf-8") + self.assertIn("six==1.17.0", constraints) + self.assertIn("pbr==7.0.3", constraints) + + def test_generates_rpms_in_yaml(self): + self.run_update() + + rpms = (self.project_root / "rpms.in.yaml").read_text(encoding="utf-8") + self.assertIn("python3", rpms) + self.assertIn("gcc", rpms) + + def test_generates_requirements_lock(self): + self.run_update() + + lock = (self.project_root / "requirements.lock.master").read_text( + encoding="utf-8" + ) + self.assertIn("six", lock) + + def test_generates_buildrequirements_lock(self): + self.run_update() + + build_lock = self.project_root / "buildrequirements.lock.master" + self.assertTrue(build_lock.is_file()) + self.assertNotIn("# via", build_lock.read_text(encoding="utf-8")) + + def test_creates_default_stream_symlinks(self): + self.run_update(DEFAULT_STREAM="master") + + expected = { + "upper-constraints.txt": "upper-constraints.txt.master", + "requirements.lock": "requirements.lock.master", + "buildrequirements.lock": "buildrequirements.lock.master", + } + for name, target in expected.items(): + link = self.project_root / name + self.assertTrue(link.is_symlink()) + self.assertEqual(target, os.readlink(link)) + + def test_skips_symlinks_for_non_default_stream(self): + self.run_update(DEFAULT_STREAM="other") + + for name in ( + "upper-constraints.txt", + "requirements.lock", + "buildrequirements.lock", + ): + self.assertFalse((self.project_root / name).is_symlink()) + + def test_skip_hash_update_preserves_and_records_committed_hashes(self): + self.run_update(SKIP_HASH_UPDATE="1") + + self.assertEqual( + self.requirements_old, + self.source_field("upper-constraints", 5), + ) + self.assertEqual(self.service_old, self.source_field("test-svc", 5)) + self.assertTrue( + (self.project_root / "requirements.lock.master").is_file() + ) + rows = self.manifest_rows() + self.assertEqual( + [self.requirements_old, self.service_old], + [row["frozen_commit"] for row in rows], + ) + self.assertEqual({"committed-pin"}, {row["authority"] for row in rows}) + + def test_skip_hash_update_uses_pinned_constraints(self): + self.run_update(SKIP_HASH_UPDATE="1") + + constraints = ( + self.project_root / "upper-constraints.txt.master" + ).read_text(encoding="utf-8") + self.assertIn("six==1.17.0", constraints) + self.assertNotIn("pbr", constraints) + + def test_hash_in_branch_field_selects_constraints_commit(self): + self.write_sources( + "master upper-constraints " + f"{self.upstream_requirements} {self.requirements_old} " + f"{self.requirements_new}\n" + "master test-svc " + f"{self.upstream_service} master {self.service_old}\n" + ) + + self.run_update() + + self.assertEqual( + self.requirements_old, + self.source_field("upper-constraints", 5), + ) + constraints = ( + self.project_root / "upper-constraints.txt.master" + ).read_text(encoding="utf-8") + self.assertIn("six==1.17.0", constraints) + self.assertNotIn("pbr", constraints) + + def test_hash_in_branch_field_selects_regular_repo_commit(self): + self.write_sources( + "master upper-constraints " + f"{self.upstream_requirements} master {self.requirements_old}\n" + "master test-svc " + f"{self.upstream_service} {self.service_old} " + f"{self.service_new}\n" + ) + + self.run_update() + + self.assertEqual(self.service_old, self.source_field("test-svc", 5)) + self.assertTrue( + (self.project_root / "requirements.lock.master").is_file() + ) + + def test_lockfile_excludes_rpm_python_packages(self): + image_root = self.project_root / "test-svc" + (image_root / "bindeps.txt").write_text( + "python3\npython3-six\n", encoding="utf-8" + ) + + output = self.run_update() + + lock = (self.project_root / "requirements.lock.master").read_text( + encoding="utf-8" + ) + self.assertNotIn("six==", lock) + self.assertIn("Filtering RPM-provided packages", output) + + def test_preexisting_checkout_is_preserved_and_recorded(self): + source = self.project_root / "src" / "test-svc" + self.run_command( + ["git", "clone", str(self.upstream_service), str(source)] + ) + (source / "MARKER").write_text("local-dev\n", encoding="utf-8") + + self.run_update() + + self.assertEqual( + "local-dev\n", + (source / "MARKER").read_text(encoding="utf-8"), + ) + self.assertEqual(self.service_old, self.source_field("test-svc", 5)) + service_row = self.manifest_rows()[1] + self.assertEqual("pre-existing-checkout", service_row["authority"]) + self.assertEqual(self.service_new, service_row["frozen_commit"]) + + def test_unversioned_preexisting_source_fails_before_mutation(self): + source = self.project_root / "src" / "test-svc" + source.mkdir() + (source / "requirements.txt").write_text("six\n", encoding="utf-8") + original_sources = (self.project_root / "sources.txt").read_bytes() + + result = self.run_update_result() + + self.assertNotEqual(0, result.returncode) + self.assertIn("is not a Git checkout", result.stderr) + self.assertEqual( + original_sources, (self.project_root / "sources.txt").read_bytes() + ) + self.assertFalse( + (self.project_root / "upper-constraints.txt.master").exists() + ) + self.assertFalse(self.manifest_path.exists()) + + def test_slash_stream_uses_safe_manifest_filename(self): + result = self.run_update_result(STREAM="stable/test") + + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + manifest = self.manifest_for_stream("stable/test") + self.assertTrue(manifest.is_file()) + self.assertEqual( + self.test_root / ".tmp/source-maintenance", manifest.parent + ) + self.assertFalse( + (self.test_root / ".tmp/source-maintenance/stable").exists() + ) + + def test_unsafe_stream_fails_before_filesystem_mutation(self): + original_sources = (self.project_root / "sources.txt").read_bytes() + + result = self.run_update_result(STREAM="../../escape") + + self.assertNotEqual(0, result.returncode) + self.assertIn("Unsafe stream name", result.stderr) + self.assertEqual( + original_sources, (self.project_root / "sources.txt").read_bytes() + ) + self.assertFalse((self.test_root / ".tmp").exists()) + + def test_unresolvable_ref_fails_before_mutation(self): + self.write_sources( + "master upper-constraints " + f"{self.upstream_requirements} master {self.requirements_old}\n" + "master test-svc " + f"{self.upstream_service} missing-branch {self.service_old}\n" + ) + original_sources = (self.project_root / "sources.txt").read_bytes() + + result = self.run_update_result() + + self.assertNotEqual(0, result.returncode) + self.assertIn("Could not freeze ref 'missing-branch'", result.stderr) + self.assertEqual( + original_sources, (self.project_root / "sources.txt").read_bytes() + ) + self.assertFalse( + (self.project_root / "upper-constraints.txt.master").exists() + ) + self.assertFalse(self.manifest_path.exists()) + + def test_failed_preflight_removes_stale_manifest(self): + self.run_update() + self.assertTrue(self.manifest_path.is_file()) + self.write_sources( + "master upper-constraints " + f"{self.upstream_requirements} master {self.requirements_new}\n" + "master test-svc " + f"{self.upstream_service} missing-branch {self.service_new}\n" + ) + + result = self.run_update_result() + + self.assertNotEqual(0, result.returncode) + self.assertFalse(self.manifest_path.exists()) + + def test_list_discovers_all_projects(self): + result = subprocess.run( + ["bash", "./build.sh", "list"], + cwd=self.test_root, + check=False, + capture_output=True, + text=True, + ) + + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + self.assertIn("test-svc/test-svc", result.stdout) + self.assertIn("test-svc2/test-svc2", result.stdout) + self.assertIn("test-svc3/test-svc3", result.stdout) + + def test_multiple_targets_update_only_selected_projects(self): + self.run_update("test-svc", "test-svc2", DEFAULT_STREAM="master") + + service2_new = self.additional_services["test-svc2"][2] + service3_old = self.additional_services["test-svc3"][1] + project2 = self.test_root / "containers/test-svc2" + project3 = self.test_root / "containers/test-svc3" + self.assertEqual(self.service_new, self.source_field("test-svc", 5)) + self.assertEqual( + service2_new, + self.source_field("test-svc2", 5, project2), + ) + self.assertEqual( + service3_old, + self.source_field("test-svc3", 5, project3), + ) + for project in (self.project_root, project2): + self.assertTrue( + (project / "upper-constraints.txt.master").is_file() + ) + self.assertTrue((project / "requirements.lock.master").is_file()) + self.assertTrue((project / "rpms.in.yaml").is_file()) + self.assertTrue((project / "requirements.lock").is_symlink()) + self.assertFalse((project3 / "upper-constraints.txt.master").exists()) + self.assertFalse((project3 / "requirements.lock.master").exists()) + self.assertFalse((project3 / "rpms.in.yaml").exists()) + + def test_single_target_does_not_affect_other_projects(self): + self.run_update("test-svc") + + project2 = self.test_root / "containers/test-svc2" + self.assertEqual( + self.additional_services["test-svc2"][1], + self.source_field("test-svc2", 5, project2), + ) + self.assertFalse((project2 / "requirements.lock.master").exists()) + + def test_all_updates_every_project(self): + self.run_update("all") + + self.assertEqual(self.service_new, self.source_field("test-svc", 5)) + for name in ("test-svc2", "test-svc3"): + project = self.test_root / "containers" / name + self.assertEqual( + self.additional_services[name][2], + self.source_field(name, 5, project), + ) + self.assertTrue((project / "requirements.lock.master").is_file()) + + def test_unknown_target_fails_before_mutation(self): + result = self.run_update_result("nonexistent") + + self.assertNotEqual(0, result.returncode) + self.assertIn("ERROR: Unknown image or project", result.stderr) + self.assertFalse(self.manifest_path.exists()) + + def test_duplicate_ref_records_retain_deterministic_rows(self): + image_sources = self.project_root / "test-svc" / "sources.txt" + image_sources.write_text( + "master helper " + f"{self.upstream_service} master {self.service_old}\n", + encoding="utf-8", + ) + + self.run_update() + + rows = self.manifest_rows() + self.assertEqual( + ["upper-constraints", "test-svc", "helper"], + [row["name"] for row in rows], + ) + self.assertEqual(rows[1]["frozen_commit"], rows[2]["frozen_commit"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_update_sources.sh b/tests/test_update_sources.sh deleted file mode 100755 index 9a3ed9b6..00000000 --- a/tests/test_update_sources.sh +++ /dev/null @@ -1,529 +0,0 @@ -#!/usr/bin/env bash -# Tests for build.sh update-sources functionality. -# -# Uses local bare git repos as fake remotes so tests run offline and fast. -# pip-compile and pybuild-deps must be on PATH for lockfile tests. -# -# Usage: -# PATH=".tox/update-sources/bin:$PATH" bash tests/test_update_sources.sh -# tox -e test -# -set -uo pipefail - -# ── Test runner ────────────────────────────────────────────────────────── - -_PASS=0 -_FAIL=0 -_SKIP=0 - -assert() { - local desc="$1" - shift - if "$@"; then - return 0 - fi - echo " ASSERTION FAILED: ${desc}" - echo " command: $*" - return 1 -} - -assert_file_exists() { assert "file exists: $1" test -f "$1"; } -assert_symlink() { assert "symlink exists: $1" test -L "$1"; } -assert_no_symlink() { assert "no symlink: $1" test ! -L "$1"; } -assert_grep() { assert "grep '$1' in $2" grep -q "$1" "$2"; } -assert_no_grep() { - if grep -q "$1" "$2" 2>/dev/null; then - echo " ASSERTION FAILED: '$1' should not appear in $2" - return 1 - fi -} - -assert_link_target() { - local link="$1" expected="$2" - local actual - actual="$(readlink "$1")" - assert "symlink $1 -> $2 (actual: ${actual})" test "${actual}" = "${expected}" -} - -assert_field() { - local file="$1" stream="$2" name="$3" field="$4" expected="$5" - local actual - actual=$(awk -v s="${stream}" -v n="${name}" '$1==s && $2==n {print $'${field}'}' "${file}") - assert "sources.txt ${name} field ${field} == ${expected} (actual: ${actual})" \ - test "${actual}" = "${expected}" -} - -run_test() { - local name="$1" - - _setup_fixture - - local rc=0 - # Run in subshell with set -e so first failed assertion stops the test - ( set -e; "${name}" ) || rc=$? - - if [[ ${rc} -eq 0 ]]; then - echo " PASS ${name}" - ((_PASS++)) - elif [[ ${rc} -eq 99 ]]; then - echo " SKIP ${name}" - ((_SKIP++)) - else - echo " FAIL ${name}" - ((_FAIL++)) - if [[ -f "${TEST_DIR}/build.log" ]]; then - echo " --- build.log (last 20 lines) ---" - tail -20 "${TEST_DIR}/build.log" | sed 's/^/ /' - echo " ---" - fi - fi - - _teardown_fixture -} - -skip_test() { - echo " skipping: $1" - return 99 -} - -# ── Fixture ────────────────────────────────────────────────────────────── - -SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -TEST_DIR="" -UPSTREAM_REQ="" -UPSTREAM_SVC="" -UPSTREAM_SVC2="" -UPSTREAM_SVC3="" -REQ_HASH_OLD="" -REQ_HASH_NEW="" -SVC_HASH_OLD="" -SVC_HASH_NEW="" -SVC2_HASH_OLD="" -SVC2_HASH_NEW="" -SVC3_HASH_OLD="" -SVC3_HASH_NEW="" - -_init_work_repo() { - local dir="$1" - git init -b master "${dir}" >/dev/null 2>&1 - git -C "${dir}" config user.email "test@test.com" - git -C "${dir}" config user.name "Test" -} - -_setup_fixture() { - TEST_DIR="$(mktemp -d)" - local work - - # ── Upstream requirements repo (2 commits) ── - UPSTREAM_REQ="${TEST_DIR}/upstream/requirements.git" - mkdir -p "${TEST_DIR}/upstream" - work="$(mktemp -d)" - _init_work_repo "${work}" - - echo "six==1.17.0" > "${work}/upper-constraints.txt" - git -C "${work}" add -A >/dev/null && git -C "${work}" commit -m "v1" >/dev/null 2>&1 - - printf 'six==1.17.0\npbr==7.0.3\n' > "${work}/upper-constraints.txt" - git -C "${work}" add -A >/dev/null && git -C "${work}" commit -m "v2" >/dev/null 2>&1 - - git clone --bare "${work}" "${UPSTREAM_REQ}" >/dev/null 2>&1 - rm -rf "${work}" - - REQ_HASH_OLD="$(git -C "${UPSTREAM_REQ}" rev-parse master~1)" - REQ_HASH_NEW="$(git -C "${UPSTREAM_REQ}" rev-parse master)" - - # ── Upstream service repo (2 commits) ── - UPSTREAM_SVC="${TEST_DIR}/upstream/test-svc.git" - work="$(mktemp -d)" - _init_work_repo "${work}" - - echo "six" > "${work}/requirements.txt" - git -C "${work}" add -A >/dev/null && git -C "${work}" commit -m "v1" >/dev/null 2>&1 - - printf 'six\npbr\n' > "${work}/requirements.txt" - git -C "${work}" add -A >/dev/null && git -C "${work}" commit -m "v2" >/dev/null 2>&1 - - git clone --bare "${work}" "${UPSTREAM_SVC}" >/dev/null 2>&1 - rm -rf "${work}" - - SVC_HASH_OLD="$(git -C "${UPSTREAM_SVC}" rev-parse master~1)" - SVC_HASH_NEW="$(git -C "${UPSTREAM_SVC}" rev-parse master)" - - # ── Upstream service 2 repo (2 commits) ── - UPSTREAM_SVC2="${TEST_DIR}/upstream/test-svc2.git" - work="$(mktemp -d)" - _init_work_repo "${work}" - - echo "six" > "${work}/requirements.txt" - git -C "${work}" add -A >/dev/null && git -C "${work}" commit -m "v1" >/dev/null 2>&1 - - printf 'six\npbr\n' > "${work}/requirements.txt" - git -C "${work}" add -A >/dev/null && git -C "${work}" commit -m "v2" >/dev/null 2>&1 - - git clone --bare "${work}" "${UPSTREAM_SVC2}" >/dev/null 2>&1 - rm -rf "${work}" - - SVC2_HASH_OLD="$(git -C "${UPSTREAM_SVC2}" rev-parse master~1)" - SVC2_HASH_NEW="$(git -C "${UPSTREAM_SVC2}" rev-parse master)" - - # ── Upstream service 3 repo (2 commits) ── - UPSTREAM_SVC3="${TEST_DIR}/upstream/test-svc3.git" - work="$(mktemp -d)" - _init_work_repo "${work}" - - echo "six" > "${work}/requirements.txt" - git -C "${work}" add -A >/dev/null && git -C "${work}" commit -m "v1" >/dev/null 2>&1 - - printf 'six\npbr\n' > "${work}/requirements.txt" - git -C "${work}" add -A >/dev/null && git -C "${work}" commit -m "v2" >/dev/null 2>&1 - - git clone --bare "${work}" "${UPSTREAM_SVC3}" >/dev/null 2>&1 - rm -rf "${work}" - - SVC3_HASH_OLD="$(git -C "${UPSTREAM_SVC3}" rev-parse master~1)" - SVC3_HASH_NEW="$(git -C "${UPSTREAM_SVC3}" rev-parse master)" - - # ── Symlink build.sh ── - ln -s "${SCRIPT_DIR}/build.sh" "${TEST_DIR}/build.sh" - - # ── Containers tree ── - mkdir -p "${TEST_DIR}/containers/test-svc/src" - mkdir -p "${TEST_DIR}/containers/test-svc/test-svc/src" - - cat > "${TEST_DIR}/containers/test-svc/sources.txt" < "${TEST_DIR}/containers/test-svc/test-svc/Containerfile" - echo "python3" > "${TEST_DIR}/containers/test-svc/test-svc/bindeps.txt" - echo "gcc" > "${TEST_DIR}/containers/test-svc/test-svc/builddeps.txt" - touch "${TEST_DIR}/containers/test-svc/test-svc/pythondeps.txt" - touch "${TEST_DIR}/containers/test-svc/test-svc/pythonbuilddeps.txt" - - # ── Second project containers tree ── - mkdir -p "${TEST_DIR}/containers/test-svc2/src" - mkdir -p "${TEST_DIR}/containers/test-svc2/test-svc2/src" - - cat > "${TEST_DIR}/containers/test-svc2/sources.txt" < "${TEST_DIR}/containers/test-svc2/test-svc2/Containerfile" - echo "python3" > "${TEST_DIR}/containers/test-svc2/test-svc2/bindeps.txt" - echo "gcc" > "${TEST_DIR}/containers/test-svc2/test-svc2/builddeps.txt" - touch "${TEST_DIR}/containers/test-svc2/test-svc2/pythondeps.txt" - touch "${TEST_DIR}/containers/test-svc2/test-svc2/pythonbuilddeps.txt" - - # ── Third project containers tree ── - mkdir -p "${TEST_DIR}/containers/test-svc3/src" - mkdir -p "${TEST_DIR}/containers/test-svc3/test-svc3/src" - - cat > "${TEST_DIR}/containers/test-svc3/sources.txt" < "${TEST_DIR}/containers/test-svc3/test-svc3/Containerfile" - echo "python3" > "${TEST_DIR}/containers/test-svc3/test-svc3/bindeps.txt" - echo "gcc" > "${TEST_DIR}/containers/test-svc3/test-svc3/builddeps.txt" - touch "${TEST_DIR}/containers/test-svc3/test-svc3/pythondeps.txt" - touch "${TEST_DIR}/containers/test-svc3/test-svc3/pythonbuilddeps.txt" -} - -_teardown_fixture() { - [[ -n "${TEST_DIR}" ]] && rm -rf "${TEST_DIR}" -} - -# Helper: run build.sh inside TEST_DIR with env vars passed as arguments. -# Usage: _run_build STREAM=master [SKIP_HASH_UPDATE=1 ...] -_run_build() { - (cd "${TEST_DIR}" && env "$@" ./build.sh update-sources test-svc) >"${TEST_DIR}/build.log" 2>&1 -} - -# Helper: run build.sh with arbitrary action and targets. -# Usage: _run_cmd -- [targets...] -_run_cmd() { - local env_args=() - while [[ $# -gt 0 && "$1" != "--" ]]; do - env_args+=("$1") - shift - done - [[ "$1" == "--" ]] && shift - (cd "${TEST_DIR}" && env "${env_args[@]}" ./build.sh "$@") >"${TEST_DIR}/build.log" 2>&1 -} - -# ── Tests ──────────────────────────────────────────────────────────────── - -test_updates_hashes_to_branch_tip() { - _run_build STREAM=master - - local src="${TEST_DIR}/containers/test-svc/sources.txt" - assert_field "${src}" master upper-constraints 5 "${REQ_HASH_NEW}" - assert_field "${src}" master test-svc 5 "${SVC_HASH_NEW}" -} - -test_fetches_upper_constraints() { - _run_build STREAM=master - - local uc="${TEST_DIR}/containers/test-svc/upper-constraints.txt.master" - assert_file_exists "${uc}" - assert_grep "six==1.17.0" "${uc}" - assert_grep "pbr==7.0.3" "${uc}" -} - -test_generates_rpms_in_yaml() { - _run_build STREAM=master - - local rpms="${TEST_DIR}/containers/test-svc/rpms.in.yaml" - assert_file_exists "${rpms}" - assert_grep "python3" "${rpms}" - assert_grep "gcc" "${rpms}" -} - -test_generates_requirements_lock() { - command -v pip-compile >/dev/null 2>&1 || skip_test "pip-compile not on PATH" - - _run_build STREAM=master - - local lock="${TEST_DIR}/containers/test-svc/requirements.lock.master" - assert_file_exists "${lock}" - assert_grep "six" "${lock}" -} - -test_generates_buildrequirements_lock() { - command -v pip-compile >/dev/null 2>&1 || skip_test "pip-compile not on PATH" - command -v pybuild-deps >/dev/null 2>&1 || skip_test "pybuild-deps not on PATH" - - _run_build STREAM=master - - assert_file_exists "${TEST_DIR}/containers/test-svc/buildrequirements.lock.master" -} - -test_creates_default_stream_symlinks() { - command -v pip-compile >/dev/null 2>&1 || skip_test "pip-compile not on PATH" - command -v pybuild-deps >/dev/null 2>&1 || skip_test "pybuild-deps not on PATH" - - _run_build STREAM=master DEFAULT_STREAM=master - - local d="${TEST_DIR}/containers/test-svc" - assert_symlink "${d}/upper-constraints.txt" - assert_symlink "${d}/requirements.lock" - assert_symlink "${d}/buildrequirements.lock" - assert_link_target "${d}/requirements.lock" "requirements.lock.master" - assert_link_target "${d}/buildrequirements.lock" "buildrequirements.lock.master" - assert_link_target "${d}/upper-constraints.txt" "upper-constraints.txt.master" -} - -test_skips_symlinks_for_non_default_stream() { - command -v pip-compile >/dev/null 2>&1 || skip_test "pip-compile not on PATH" - command -v pybuild-deps >/dev/null 2>&1 || skip_test "pybuild-deps not on PATH" - - _run_build STREAM=master DEFAULT_STREAM=other - - local d="${TEST_DIR}/containers/test-svc" - assert_no_symlink "${d}/requirements.lock" - assert_no_symlink "${d}/buildrequirements.lock" - assert_no_symlink "${d}/upper-constraints.txt" -} - -test_skip_hash_update_preserves_hashes() { - command -v pip-compile >/dev/null 2>&1 || skip_test "pip-compile not on PATH" - - _run_build STREAM=master SKIP_HASH_UPDATE=1 - - local src="${TEST_DIR}/containers/test-svc/sources.txt" - assert_field "${src}" master upper-constraints 5 "${REQ_HASH_OLD}" - assert_field "${src}" master test-svc 5 "${SVC_HASH_OLD}" - - assert_file_exists "${TEST_DIR}/containers/test-svc/requirements.lock.master" -} - -test_skip_hash_update_fetches_constraints_at_pinned_hash() { - _run_build STREAM=master SKIP_HASH_UPDATE=1 - - local uc="${TEST_DIR}/containers/test-svc/upper-constraints.txt.master" - assert_file_exists "${uc}" - assert_grep "six==1.17.0" "${uc}" - assert_no_grep "pbr" "${uc}" -} - -test_hash_in_branch_field_upper_constraints() { - cat > "${TEST_DIR}/containers/test-svc/sources.txt" </dev/null 2>&1 || skip_test "pip-compile not on PATH" - - cat > "${TEST_DIR}/containers/test-svc/sources.txt" </dev/null 2>&1 || skip_test "pip-compile not on PATH" - - printf 'python3\npython3-six\n' > "${TEST_DIR}/containers/test-svc/test-svc/bindeps.txt" - - _run_build STREAM=master - - local lock="${TEST_DIR}/containers/test-svc/requirements.lock.master" - assert_file_exists "${lock}" - assert_no_grep "^six==" "${lock}" - assert_grep "Filtering RPM-provided packages" "${TEST_DIR}/build.log" -} - -test_preexisting_checkout_is_preserved() { - local src_dir="${TEST_DIR}/containers/test-svc/src/test-svc" - mkdir -p "${src_dir}" - echo "local-dev" > "${src_dir}/MARKER" - echo "six" > "${src_dir}/requirements.txt" - - _run_build STREAM=master - - assert_file_exists "${src_dir}/MARKER" - assert_grep "local-dev" "${src_dir}/MARKER" - assert_field "${TEST_DIR}/containers/test-svc/sources.txt" master test-svc 5 "${SVC_HASH_OLD}" -} - -# ── Multi-target tests ────────────────────────────────────────────────── - -test_list_discovers_all_projects() { - _run_cmd STREAM=master -- list - assert_grep "test-svc/test-svc" "${TEST_DIR}/build.log" - assert_grep "test-svc2/test-svc2" "${TEST_DIR}/build.log" -} - -test_update_sources_multiple_targets() { - _run_cmd STREAM=master -- update-sources test-svc test-svc2 - - local src1="${TEST_DIR}/containers/test-svc/sources.txt" - local src2="${TEST_DIR}/containers/test-svc2/sources.txt" - local src3="${TEST_DIR}/containers/test-svc3/sources.txt" - assert_field "${src1}" master test-svc 5 "${SVC_HASH_NEW}" - assert_field "${src2}" master test-svc2 5 "${SVC2_HASH_NEW}" - assert_field "${src3}" master test-svc3 5 "${SVC3_HASH_OLD}" -} - -test_update_sources_multiple_targets_fetches_constraints() { - _run_cmd STREAM=master -- update-sources test-svc test-svc2 - - assert_file_exists "${TEST_DIR}/containers/test-svc/upper-constraints.txt.master" - assert_file_exists "${TEST_DIR}/containers/test-svc2/upper-constraints.txt.master" - assert "no constraints for test-svc3" test ! -f "${TEST_DIR}/containers/test-svc3/upper-constraints.txt.master" -} - -test_update_sources_multiple_targets_generates_lockfiles() { - command -v pip-compile >/dev/null 2>&1 || skip_test "pip-compile not on PATH" - - _run_cmd STREAM=master -- update-sources test-svc test-svc2 - - assert_file_exists "${TEST_DIR}/containers/test-svc/requirements.lock.master" - assert_file_exists "${TEST_DIR}/containers/test-svc2/requirements.lock.master" - assert "no lockfile for test-svc3" test ! -f "${TEST_DIR}/containers/test-svc3/requirements.lock.master" -} - -test_update_sources_multiple_targets_generates_rpms_in() { - _run_cmd STREAM=master -- update-sources test-svc test-svc2 - - assert_file_exists "${TEST_DIR}/containers/test-svc/rpms.in.yaml" - assert_file_exists "${TEST_DIR}/containers/test-svc2/rpms.in.yaml" - assert "no rpms.in.yaml for test-svc3" test ! -f "${TEST_DIR}/containers/test-svc3/rpms.in.yaml" -} - -test_update_sources_single_target_does_not_affect_other() { - _run_cmd STREAM=master -- update-sources test-svc - - local src2="${TEST_DIR}/containers/test-svc2/sources.txt" - assert_field "${src2}" master test-svc2 5 "${SVC2_HASH_OLD}" -} - -test_update_sources_all_updates_everything() { - _run_cmd STREAM=master -- update-sources all - - local src1="${TEST_DIR}/containers/test-svc/sources.txt" - local src2="${TEST_DIR}/containers/test-svc2/sources.txt" - local src3="${TEST_DIR}/containers/test-svc3/sources.txt" - assert_field "${src1}" master test-svc 5 "${SVC_HASH_NEW}" - assert_field "${src2}" master test-svc2 5 "${SVC2_HASH_NEW}" - assert_field "${src3}" master test-svc3 5 "${SVC3_HASH_NEW}" -} - -test_update_sources_unknown_target_fails() { - if _run_cmd STREAM=master -- update-sources nonexistent 2>/dev/null; then - echo " ASSERTION FAILED: expected failure for unknown target" - return 1 - fi - assert_grep "ERROR: Unknown image or project" "${TEST_DIR}/build.log" -} - -test_update_sources_multiple_targets_symlinks() { - command -v pip-compile >/dev/null 2>&1 || skip_test "pip-compile not on PATH" - command -v pybuild-deps >/dev/null 2>&1 || skip_test "pybuild-deps not on PATH" - - _run_cmd STREAM=master DEFAULT_STREAM=master -- update-sources test-svc test-svc2 - - for proj in test-svc test-svc2; do - local d="${TEST_DIR}/containers/${proj}" - assert_symlink "${d}/requirements.lock" - assert_link_target "${d}/requirements.lock" "requirements.lock.master" - done -} - -# ── Run all tests ──────────────────────────────────────────────────────── - -echo "=== update-sources tests ===" -echo "" - -TESTS=( - test_updates_hashes_to_branch_tip - test_fetches_upper_constraints - test_generates_rpms_in_yaml - test_generates_requirements_lock - test_generates_buildrequirements_lock - test_creates_default_stream_symlinks - test_skips_symlinks_for_non_default_stream - test_skip_hash_update_preserves_hashes - test_skip_hash_update_fetches_constraints_at_pinned_hash - test_hash_in_branch_field_upper_constraints - test_hash_in_branch_field_regular_repo - test_lockfile_excludes_rpm_python_packages - test_preexisting_checkout_is_preserved - test_list_discovers_all_projects - test_update_sources_multiple_targets - test_update_sources_multiple_targets_fetches_constraints - test_update_sources_multiple_targets_generates_lockfiles - test_update_sources_multiple_targets_generates_rpms_in - test_update_sources_single_target_does_not_affect_other - test_update_sources_all_updates_everything - test_update_sources_unknown_target_fails - test_update_sources_multiple_targets_symlinks -) - -for t in "${TESTS[@]}"; do - run_test "${t}" -done - -echo "" -echo "=== ${_PASS} passed, ${_FAIL} failed, ${_SKIP} skipped ===" - -[[ ${_FAIL} -eq 0 ]] diff --git a/tox.ini b/tox.ini index 988d08a6..bec1fbe4 100644 --- a/tox.ini +++ b/tox.ini @@ -33,6 +33,11 @@ deps = allowlist_externals = bash +[testenv:{unit,test,py3}] +description = Run all non-Ansible tests through stdlib unittest +commands = + python -m unittest discover -s {toxinidir}/tests -p 'test_*.py' -v {posargs} + [testenv:linters] description = Run formatting and style checks through pre-commit deps = @@ -72,10 +77,6 @@ commands = commands = bash {toxinidir}/build.sh build {posargs:all} -[testenv:test] -commands = - bash {toxinidir}/tests/test_update_sources.sh - [testenv:custom] commands = bash {toxinidir}/build.sh {posargs}