Skip to content

feat(ansible): add version_lock role for reproducible dependency installation#6934

Merged
youtalk merged 14 commits into
autowarefoundation:mainfrom
youtalk:feat/ansible-lockfile
Jul 3, 2026
Merged

feat(ansible): add version_lock role for reproducible dependency installation#6934
youtalk merged 14 commits into
autowarefoundation:mainfrom
youtalk:feat/ansible-lockfile

Conversation

@youtalk

@youtalk youtalk commented Mar 24, 2026

Copy link
Copy Markdown
Member

Related to #6862 and the design discussion #6861.

Description

Adds an opt-in, reproducible dependency-install mode for install_dev_env.yaml. Locked mode pins the entire ROS dependency closure to a dated snapshots.ros.org snapshot, and pins the stable Ubuntu/pip packages via APT preferences. It is fully opt-in and leaves default behavior unchanged.

How it works

  • ROS side — dated snapshot. In locked mode the ros2 role configures the APT source as a deb822 .sources file pointing at http://snapshots.ros.org/{distro}/{date}/ubuntu, signed by a committed snapshot keyring (the older OSRF key AD19BAB3CBF125EA). This replaces the previous "skip the ros-apt-source block" behavior, so locked mode works on a fresh machine and the whole ROS dependency closure is frozen to the snapshot date — which also keeps the exact pinned ROS builds installable over time. (This is the direction agreed in the review thread and in Dependency Version Pinning for Reproducible Builds #6861.)
  • Ubuntu / pip side — APT preference pins. The version_lock role writes /etc/apt/preferences.d/autoware-lock with Pin-Priority: 1001 for the stable Ubuntu-archive packages (apt_pins). pip/pipx packages (e.g. gdown) live in a separate pip_pins section consumed directly by their role and are not rendered as APT pins.
  • Lockfile format (one file per distro/arch, ansible/vars/locked-versions-{distro}-{arch}.yaml): ros_snapshot_date, apt_pins, pip_pins, ros_overrides.
  • Freeze policy. A snapshot is frozen once taken; there is no scheduled regeneration. If an individual package must move ahead of the snapshot, add a one-line ros_overrides (ROS) or apt_pins (Ubuntu) entry in a small, focused PR.

Usage

# Default mode (existing behavior, unchanged)
./setup-dev-env.sh -y

# Locked mode (reproducible install)
ansible-playbook ansible/playbooks/install_dev_env.yaml -e use_locked_versions=true

# Regenerate a lockfile from a provisioned machine
ROS_DISTRO=jazzy ROS_SNAPSHOT_DATE=2026-04-13 ./ansible/scripts/generate_ansible_lockfile.sh

# Validate lockfiles
./ansible/scripts/validate_lockfiles.sh

Main changes

  • ansible/roles/version_lock/ — loads the lockfile, asserts ros_snapshot_date is present in locked mode, writes/cleans the APT preferences pin file.
  • ansible/roles/ros2/ — dated-snapshot source in locked mode + committed snapshot keyring (files/ros-snapshot-archive-keyring.gpg); default (rolling) path unchanged.
  • ansible/scripts/generate_ansible_lockfile.sh, ansible/scripts/validate_lockfiles.sh — generate / validate the nested lockfiles.
  • ansible/vars/locked-versions-{humble,jazzy}-{amd64,arm64}.yaml — all four filled (amd64 and arm64 regenerated on real runners).

How was this PR tested?

  • pre-commit run --all-files (ansible) and ansible-lint pass; ansible-playbook --syntax-check passes in both default and -e use_locked_versions=true modes.
  • ansible/scripts/validate_lockfiles.sh passes on the amd64 lockfiles.
  • Fork CI matrix (amd64 + arm64 × humble + jazzy):
  • A full clean-container locked install of install_dev_env completes locally and installs the exact snapshot build ros-jazzy-desktop=0.11.0-1noble.20260412.072512.

@github-actions

github-actions Bot commented Mar 24, 2026

Copy link
Copy Markdown

Thank you for contributing to the Autoware project!

🚧 If your pull request is in progress, switch it to draft mode.

Please ensure:

@youtalk youtalk force-pushed the feat/ansible-lockfile branch 2 times, most recently from 19f2255 to 8a10e7b Compare March 24, 2026 19:58
@youtalk youtalk self-assigned this Mar 25, 2026
@youtalk youtalk force-pushed the feat/ansible-lockfile branch from 3bf7650 to 966d6c9 Compare March 25, 2026 18:49
@youtalk youtalk changed the title feat(setup-dev-env): add --locked option for reproducible dependency installation feat(setup-dev-env.sh): add --locked option for reproducible dependency installation Mar 25, 2026
@youtalk youtalk force-pushed the feat/ansible-lockfile branch 3 times, most recently from 27886d3 to b21d086 Compare March 26, 2026 04:03
@youtalk youtalk added the run:health-check Run health-check label Mar 26, 2026
@youtalk youtalk marked this pull request as ready for review March 26, 2026 05:35
Copilot AI review requested due to automatic review settings March 26, 2026 05:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in “locked” installation path to the dev environment setup to support reproducible dependency versions via Ansible-managed APT pinning and a per-distro/arch lockfile.

Changes:

  • Add --locked flag to setup-dev-env.sh to enable locked mode and pass lockfile path into Ansible.
  • Introduce a version_lock role that loads a lockfile and writes APT preferences pins, and update existing roles to allow downgrades when locked mode is enabled.
  • Add lockfile templates plus scripts to generate/validate lockfiles.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
setup-dev-env.sh Adds --locked flag and passes use_locked_versions + lockfile_path to Ansible.
ansible/vars/locked-versions-jazzy-arm64.yaml Adds jazzy/arm64 lockfile template (empty versions).
ansible/vars/locked-versions-jazzy-amd64.yaml Adds jazzy/amd64 lockfile template (empty versions).
ansible/vars/locked-versions-humble-arm64.yaml Adds humble/arm64 lockfile template (empty versions).
ansible/vars/locked-versions-humble-amd64.yaml Adds humble/amd64 lockfile template (empty versions).
ansible/scripts/validate_lockfiles.sh Adds a script to validate lockfile YAML is a flat dict.
ansible/scripts/generate_ansible_lockfile.sh Adds a script to generate pinned versions from installed packages.
ansible/roles/version_lock/tasks/main.yaml Adds role tasks to load lockfile and manage /etc/apt/preferences.d/autoware-lock.
ansible/roles/version_lock/templates/autoware-lock.pref.j2 Adds APT preferences template for per-package version pins.
ansible/roles/ros2_dev_tools/tasks/main.yaml Allows downgrades when locked mode is enabled.
ansible/roles/ros2/tasks/main.yaml Skips dynamic ros-apt-source install in locked mode; allows downgrades for ROS metapackage install.
ansible/roles/rmw_implementation/tasks/main.yaml Allows downgrades when locked mode is enabled.
ansible/roles/geographiclib/tasks/main.yaml Allows downgrades when locked mode is enabled.
ansible/roles/gdown/tasks/main.yaml Pins gdown version via lockfile in locked mode; allows downgrades for pipx APT install.
ansible/roles/docker_engine/tasks/main.yaml Allows downgrades when locked mode is enabled.
ansible/roles/dev_tools/tasks/main.yaml Allows downgrades when locked mode is enabled.
ansible/roles/build_tools/tasks/main.yaml Allows downgrades when locked mode is enabled.
ansible/playbooks/universe.yaml Adds autoware.dev_env.version_lock role to the universe playbook.
.cspell.json Ignores ansible/vars/ in cspell.
.ansible-lint Excludes ansible/vars/ from ansible-lint.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ansible/roles/version_lock/templates/autoware-lock.pref.j2 Outdated
Comment thread ansible/roles/version_lock/tasks/main.yaml
Comment thread ansible/roles/gdown/tasks/main.yaml
Comment thread ansible/roles/ros2/tasks/main.yaml Outdated
Comment thread setup-dev-env.sh Outdated
Comment thread ansible/scripts/generate_ansible_lockfile.sh Outdated
Comment thread ansible/scripts/generate_ansible_lockfile.sh Outdated
Comment thread ansible/scripts/generate_ansible_lockfile.sh Outdated
@xmfcx

xmfcx commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Could you postpone this dependency pinning task after that issue is completed?

@youtalk youtalk added the type:build Tooling and infrastructure around building the Autoware. label Mar 26, 2026
@youtalk

youtalk commented Mar 26, 2026

Copy link
Copy Markdown
Member Author

@xmfcx Yes, I could.

youtalk added a commit to youtalk/autoware that referenced this pull request Jun 13, 2026
…allation

Forward-port of the lockfile mechanism from PR autowarefoundation#6934 onto the install_dev_env.yaml entry point established by autowarefoundation#7052/autowarefoundation#7053: version_lock role (APT preferences, Pin-Priority 1001) with lockfile_path auto-resolution, generator/validator scripts, empty lockfile templates (humble/jazzy x amd64/arm64), and role registration in install_dev_env.yaml. The setup-dev-env.sh --locked wrapper flag from the original PR is intentionally dropped; the supported entry point is --extra-vars use_locked_versions=true.

The lockfile package set is aligned with what install_dev_env.yaml actually installs: the Docker Engine packages from the original PR lockfiles are dropped (Docker is installed by the separate install_docker.yaml playbook), and the generator resolves the pipx-managed gdown version via pipx runpip as a fallback to pip3.

Refs: autowarefoundation#6862
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
youtalk added a commit to youtalk/autoware that referenced this pull request Jun 13, 2026
…allation

Forward-port of the lockfile mechanism from PR autowarefoundation#6934 onto the install_dev_env.yaml entry point established by autowarefoundation#7052/autowarefoundation#7053: version_lock role (APT preferences, Pin-Priority 1001) with lockfile_path auto-resolution, generator/validator scripts, empty lockfile templates (humble/jazzy x amd64/arm64), and role registration in install_dev_env.yaml. The setup-dev-env.sh --locked wrapper flag from the original PR is intentionally dropped; the supported entry point is --extra-vars use_locked_versions=true.

The lockfile package set is aligned with what install_dev_env.yaml actually installs: the Docker Engine packages from the original PR lockfiles are dropped (Docker is installed by the separate install_docker.yaml playbook), and the generator resolves the pipx-managed gdown version via pipx runpip as a fallback to pip3.

Refs: autowarefoundation#6862
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
youtalk added 4 commits June 12, 2026 22:13
Add allow_downgrade conditioned on use_locked_versions to all apt tasks
in roles managed by the lockfile. APT preferences handle version pinning,
so roles only need this single-line addition.

Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Wrap ros-apt-source download tasks in a block with when condition to
skip network access when using locked versions. The APT source is
already configured in locked environments.

Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
…allation

Forward-port of the lockfile mechanism from PR autowarefoundation#6934 onto the install_dev_env.yaml entry point established by autowarefoundation#7052/autowarefoundation#7053: version_lock role (APT preferences, Pin-Priority 1001) with lockfile_path auto-resolution, generator/validator scripts, empty lockfile templates (humble/jazzy x amd64/arm64), and role registration in install_dev_env.yaml. The setup-dev-env.sh --locked wrapper flag from the original PR is intentionally dropped; the supported entry point is --extra-vars use_locked_versions=true.

The lockfile package set is aligned with what install_dev_env.yaml actually installs: the Docker Engine packages from the original PR lockfiles are dropped (Docker is installed by the separate install_docker.yaml playbook), and the generator resolves the pipx-managed gdown version via pipx runpip as a fallback to pip3.

Refs: autowarefoundation#6862
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Generated in clean containers (ubuntu:22.04 / ubuntu:24.04) via generate_ansible_lockfile.sh. The remaining arm64 combos are left empty for the scheduled CI regeneration workflow (Phase C).

Refs: autowarefoundation#6862
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
@youtalk youtalk force-pushed the feat/ansible-lockfile branch from 1bce85b to 27722d4 Compare June 13, 2026 05:19
@youtalk youtalk changed the title feat(setup-dev-env.sh): add --locked option for reproducible dependency installation feat(ansible): add version_lock role for reproducible dependency installation Jun 13, 2026
@youtalk

youtalk commented Jun 13, 2026

Copy link
Copy Markdown
Member Author

@xmfcx I'm update this PR with the latest commit. Please review this.

@xmfcx xmfcx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this back up, @youtalk! The opt-in design is clean: default-off no-op, the self-cleaning pin file, and one centralized lockfile are all nice. 🙏

I tried use_locked_versions=true in a fresh container and ran into an issue with the ROS packages specifically. I think there's a cleaner mechanism that would make locked mode genuinely reproducible, so wanted to share what I found before approving.

What happens today

In locked mode the ros2 role skips the whole ros-apt-source block (ansible/roles/ros2/tasks/main.yaml:2), which is the only thing that configures packages.ros.org. On a fresh machine the ROS repo then never gets set up, and a later role aborts:

# fresh ubuntu:24.04, from the PR branch / repo root
docker run --rm -v "$PWD":/work:ro -w /work ubuntu:24.04 bash -c '
  apt-get update -qq && apt-get install -y -qq sudo git python3-pip pipx >/dev/null
  export PATH=$HOME/.local/bin:$PATH
  pipx install --include-deps "ansible==10.*" >/dev/null
  ansible-galaxy collection install -f -r ansible-galaxy-requirements.yaml >/dev/null
  ansible-playbook autoware.dev_env.install_dev_env \
    -e use_locked_versions=true --tags version_lock,ros2,ros2_dev_tools
'
# => fatal: ... "No package matching 'python3-colcon-mixin' is available"
# => /etc/apt/sources.list.d/ has no ROS source

Why pinning ROS packages against the rolling repo is tricky

I think the block was skipped because the "fetch latest ros-apt-source release" step is non-deterministic. The thing is, ros-apt-source is only the repo-config package. I extracted the .deb and it just installs this:

URIs: http://packages.ros.org/ros2/ubuntu   # the rolling repo
Suites: noble
Components: main

Its version (1.2.0) is the config package's own version, not a buildfarm date, so it doesn't influence which ROS package versions you get. And the rolling repo keeps only the newest build, so the date-stamped versions in the lockfile get pruned within weeks. The lockfile in this PR already shows it:

docker run --rm ros:jazzy-ros-base bash -c '
  apt-get update -qq
  apt-get install -y --dry-run ros-jazzy-desktop=0.11.0-1noble.20260412.072512'
# => E: Version '0.11.0-1noble.20260412.072512' for 'ros-jazzy-desktop' was not found

Suggested direction: dated ROS snapshots

ROS publishes dated snapshot repos at snapshots.ros.org, which are the ROS-native equivalent of snapshot.ubuntu.com. The snapshot nearest the lockfile stamp serves the exact pinned build:

docker run --rm ubuntu:24.04 bash -c '
  apt-get update -qq >/dev/null
  cat > /etc/apt/sources.list.d/ros2-snap.sources <<EOF
Types: deb
URIs: http://snapshots.ros.org/jazzy/2026-04-13/ubuntu
Suites: noble
Components: main
Trusted: yes
EOF
  apt-get update -qq
  apt-get install -y --dry-run ros-jazzy-desktop=0.11.0-1noble.20260412.072512 | grep "Inst ros-jazzy-desktop"'
# => Inst ros-jazzy-desktop (0.11.0-1noble.20260412.072512  jazzy/2026-04-13/ubuntu ...)  ✅

Pointing the ROS apt source at a dated snapshot instead of skipping it would:

  • configure the repo deterministically, so locked mode works on a fresh machine (no need to skip the block),
  • freeze the entire ROS dependency closure to that date, not just the handful of named packages, which is what actually makes a build reproducible,
  • make the lockfile's exact ROS versions installable, and keep them installable over time.

Two small things I noticed while testing: the snapshot is signed with an older key (AD19BAB3CBF125EA), so we'd ship that keyring rather than the rolling ros.key; and the lockfile would want to record the snapshot date (e.g. 2026-04-13), since it can't be recovered from the version string alone.

One option is to keep the APT-preference pins for the stable Ubuntu packages (ccache, git-lfs, etc.) where they work great, and use a dated snapshot for the ROS side. Totally happy to pair on this or help prototype the snapshot source if it's useful. Really glad this is moving again, just want to make sure locked mode holds up on a clean setup. 🙂

youtalk added 7 commits June 22, 2026 17:00
…ions

Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Move gdown from apt_pins to a new pip_pins section so that the gdown
role resolves locked_packages.pip_pins.gdown (fixing the empty-version
regression) and pip packages no longer appear as bogus APT pins.

Update the validator, generator, and README to handle pip_pins alongside
apt_pins. The APT-preferences template is unchanged (already excludes
pip_pins by iterating only apt_pins + ros_overrides).

Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
@youtalk

youtalk commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

Thanks @xmfcx — agreed, the dated snapshot is the right mechanism. It was actually the plan I'd noted in the design discussion (#6861, this comment): point locked mode at snapshots.ros.org/{distro}/{date}/ubuntu, the same approach OSRF's images use. I've reworked the PR accordingly:

  • The ros2 role, in locked mode, now writes a deb822 source pointing at the dated snapshot, signed by a committed snapshot keyring (the older AD19BAB3CBF125EA key) — the block is no longer skipped, so locked mode works on a fresh machine and freezes the entire ROS closure to the date.
  • The lockfile records ros_snapshot_date; APT-preference pins now cover only the stable Ubuntu packages (apt_pins), pip packages live in a separate pip_pins section, and ROS versions are left to the snapshot. No scheduled regeneration — a snapshot is frozen once taken, and individual packages get one-line update PRs if upstream ever forces a bump.

Verified on fork CI across amd64 + arm64 × humble + jazzy:

A full clean-container locked install also completes locally, installing the exact snapshot build ros-jazzy-desktop=0.11.0-1noble.20260412.072512. Thanks again for the snapshot pointer — happy to adjust anything.

@youtalk youtalk requested a review from xmfcx June 23, 2026 02:33
youtalk added 2 commits June 22, 2026 19:37
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
@youtalk

youtalk commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

@xmfcx @mitsudome-r @oguzkaganozt Please review this.

@youtalk

youtalk commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

@xmfcx @mitsudome-r @oguzkaganozt @isamu-takagi Please review this. I would like to move forward.

@xmfcx xmfcx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran an automated review and tested the high severity items and verified manually.

For AI generated PRs (especially for high impact ones) I'd suggest spawning some fresh context AI agents locally and ask for "adversarial review". And iterate until nothing sticks out.

The website-copilot reviews are quite bad in my opinion.

On claude /effort ultracode I prompted:

check out locally and adversarially review https://github.com/autowarefoundation/autoware/pull/6934 (dont post anything)

in my autoware workspace and it was pretty useful.

Comment thread ansible/roles/ros2/tasks/main.yaml
Comment thread ansible/roles/version_lock/templates/autoware-lock.pref.j2
Comment thread ansible/roles/dev_tools/tasks/main.yaml
Comment thread ansible/playbooks/install_dev_env.yaml Outdated
Address the adversarial review on the version_lock PR (findings F1, F3,
F4, F5). Each item was reproduced in a container before and after the
fix.

F1: the locked-mode block only added the dated snapshot source, so a
machine that already had the rolling packages.ros.org repo kept
installing the newer rolling build (both sit at apt priority 500). Add a
"Package: * / Pin: origin snapshots.ros.org / Pin-Priority: 1001" stanza
to autoware-lock.pref.j2 so the snapshot outranks the rolling repo.

F3: an APT pin to a version that exists in no configured source matches
nothing and apt silently installs another candidate. Add a post-install
task (version_lock/tasks/verify.yaml, run from the playbook post_tasks)
that compares the installed version of every apt_pins/ros_overrides
entry against the lockfile and fails listing all drift. Render
ros_overrides at Pin-Priority 1002 so an override beats the
snapshot-origin pin, and fix the README override example to a reachable
version.

F4: locked mode left already-installed packages on the wrong version.
Pass force in locked mode to the gdown pipx task (pipx is otherwise a
no-op on an existing venv) and switch git-lfs from state: present to
state: latest so the pin is honored.

F5: consumers dereferenced locked_packages with no default, so any run
that skipped version_lock (e.g. --tags ros2) crashed mid-play after
mutating system state. Tag the version_lock role [always] and add a
locked_packages-defined assert in the ros2 and gdown locked branches.

Signed-off-by: Yutaka Kondo <yutaka.kondo@youtalk.jp>
@youtalk youtalk force-pushed the feat/ansible-lockfile branch from d854b15 to 4775550 Compare July 2, 2026 22:22
@youtalk

youtalk commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

Thanks for the thorough adversarial review, @xmfcx — the container repros made each issue easy to confirm and fix. Addressed all four inline findings in 4775550, each reproduced before and after the fix:

  • F1 — snapshot-origin pin (Package: * / Pin: origin snapshots.ros.org / Pin-Priority: 1001) so the dated snapshot outranks a pre-existing rolling packages.ros.org repo.
  • F3 — a post-install verification task that fails on any pin-vs-installed drift; ros_overrides moved to Pin-Priority: 1002; README override example made reachable.
  • F4force for the gdown pipx task in locked mode, and git-lfs state: latest.
  • F5version_lock role tagged [always] plus locked_packages-defined asserts in the ros2/gdown locked branches.

I took your suggestion and drove the fixes with a fresh-context agent that re-ran each of your reproductions in throwaway containers. The one durable Ubuntu-side follow-up (dated snapshot.ubuntu.com sources) I've left for a separate PR since you flagged it as deferrable. Ready for another look.

@youtalk youtalk requested a review from xmfcx July 2, 2026 22:28

@xmfcx xmfcx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @youtalk for the patient iterations here and for the quick, well-verified turnaround on the review findings. I re-checked the branch: all four findings are addressed correctly, the lockfiles are consistent across distros and architectures, and the fork CI matrix is green. Approving.

The deferred follow-ups we discussed (Ubuntu-side dated snapshots, locked-mode CI coverage) can come in separate PRs whenever you get to them.

@youtalk youtalk enabled auto-merge (squash) July 3, 2026 00:29
@youtalk youtalk merged commit 13b4d03 into autowarefoundation:main Jul 3, 2026
25 checks passed
@youtalk youtalk deleted the feat/ansible-lockfile branch July 3, 2026 03:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run:health-check Run health-check type:build Tooling and infrastructure around building the Autoware.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants