Skip to content

fix(ansible): keep autoware_data owned by the user - #7260

Merged
xmfcx merged 5 commits into
autowarefoundation:mainfrom
xmfcx:fix/ansible-autoware-data-ownership
Aug 7, 2026
Merged

xmfcx merged 5 commits into
autowarefoundation:mainfrom
xmfcx:fix/ansible-autoware-data-ownership

Conversation

@xmfcx

@xmfcx xmfcx commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Description

~/autoware_data sits in the home directory of the user because every consumer reads it as that user:

  • The ansible roles that download into it
  • The ROS nodes that write TensorRT engine files next to the models
  • The containers under docker/, which mount it for the unprivileged aw user

The directory was never meant to hold a path that root owns. A path that root owns stops all three consumers above.

Two things in this repository create such a path.

The download tasks used become: true, so many installs hold model files and one model directory that root owns. Older Dockerfiles also installed as root.

The compose files under docker/ used the short mount syntax. That syntax creates a missing host path, and the Docker daemon creates it as root. A user who starts a demo before the ansible run therefore gets the same problem on a clean machine.

The container still starts in that state. Autoware then reads an empty maps directory and an empty ml_models directory. The run fails later with a message about the data, not about the mount. The user also cannot remove the new directories without sudo.

This PR removes both causes, and corrects the installs that already have the problem.

What changes

A new role, autoware_data_ownership. It gives ~/autoware_data and every path under it to the user that runs the playbook. It does nothing when the directory does not exist, and nothing when the user already owns every path. Only the correction needs sudo, so a clean install needs no password.

The artifacts health check now also runs when this role changes. Its path filter listed ansible/roles/artifacts/** only, so a change to the new role alone got no coverage.

artifacts and demo_artifacts depend on the new role. A role dependency runs before the role that declares it, so the tree is user-owned before any task writes into it. The dependency inherits the tags of its parent, so it runs under --tags artifacts and under --tags demo_artifacts. Ansible runs it once per host even when both parents are selected, so --list-tasks prints it under both parents but only one run happens.

No task writes into ~/autoware_data as root any more. This removes become: true from 16 tasks in artifacts and from 3 tasks in demo_artifacts. The Install unzip task in demo_artifacts keeps become: true, because apt needs it.

One of the 16 is the task Create diffusion_planner/v3.1 directory. That task is the reason a directory that root owns exists at all. Every other directory task in the role was already correct.

Compose stops instead of creating a mount source. Every bind mount of ~/autoware_data under docker/ moves to the long syntax with create_host_path: false. Compose then stops with bind source path does not exist when a path is missing, and it no longer creates that path as root. This changes 17 mounts in 9 files.

No ansible role creates ~/autoware_data/scenarios, so that path was root-owned even after a complete install. The three devcontainers already create ~/autoware_data on the host with their initializeCommand, so that flow does not change.

This is a change of behaviour for a user who has not run the ansible roles. That user now reads a clear error, instead of a run that breaks later with an empty directory. The demo READMEs already list these paths as prerequisites. docker/examples/basic/README.md had no such list, and this PR adds one.

How the role decides to run

The role resolves ~/autoware_data with readlink -f. It then looks for the first path under the resolved directory that the user does not own. The correction runs only when that search prints a path.

find and chown both keep the default behaviour for a symbolic link. They act on the link itself, and they do not descend into it. Two results follow from this. A link that points out of ~/autoware_data keeps the owner of its target. The search and the correction also read and write one set of paths, so a corrected tree stops the search.

readlink -f resolves the directory when it sits on another disk behind a symbolic link. The role corrects the real directory in that case. When the path cannot be resolved, readlink -f prints nothing and the role does nothing.

How to verify manually

1. Check out the branch

cd ~/projects/autoware
gh pr checkout 7260
ansible-galaxy collection install -f -r ansible-galaxy-requirements.yaml

2. Look at the paths that root owns

find "$HOME/autoware_data" ! -user "$USER" -printf '%u:%g %y %p\n'

3. Run the playbook

ansible-playbook autoware.dev_env.install_dev_env --tags artifacts --ask-become-pass

On an install made before this PR, the task Take ownership of the Autoware data directory reports changed. On a clean install it reports skipped, and the run needs no password.

4. Look again

find "$HOME/autoware_data" ! -user "$USER" -printf '%u:%g %y %p\n'

The command must print nothing. A second run of the playbook skips the correction.

5. Check that Compose no longer creates a mount source

A throwaway HOME gives the compose file an empty tree to mount, so this check leaves the real ~/autoware_data alone.

HOME=$(mktemp -d) docker compose -f docker/examples/basic/dev-cpu.compose.yaml up

Compose must stop with bind source path does not exist, and it must create no directory under the temporary tree. Before this PR it created that path as root.

Compose pulls the image of the demo first when the machine does not hold it yet, which takes several GB.

Relation to the Hugging Face migration

Batch E carried a smaller form of this correction, scoped to the artifacts directory. This PR replaces it, so batch E drops those two tasks and rebases on this one. Batch E also deletes 15 of the 16 tasks that lose become: true here, so the rebase resolves in favor of the deletion.

AI usage

AI usage: written with Claude Code, from a review of batch E, then corrected twice from a review of this branch
Self-review:
image

Verification: the role and the compose change both ran on Ubuntu 24.04. The planning simulator demo drove to a goal with the compose file from this PR.

What ran, and what did not

Versions

  • ansible-core 2.17.14, the version that ansible/scripts/install-ansible.sh pins
  • GNU findutils 4.9.0 and GNU coreutils 9.4, in a container of ubuntu:24.04
  • Docker 29.7.2 and Compose v5.4.0

The role, on the host

Six inputs skip the correction, and none asks for a password:

  • An absent path
  • A regular file
  • A clean tree
  • A symbolic link to a directory
  • A tree that holds a link to a path outside it
  • A dangling symbolic link

The real ~/autoware_data of the host also skips. A run with --check also skips.

The role, end to end in a container

A container of ubuntu:24.04 with passwordless sudo ran the final role against a tree that root owns.

  • The first run reported changed=1, and made every path user-owned
  • The user then created a file in the directory that root owned before
  • That write is the one hf and the TensorRT engine writer need
  • After sudo -k, the second run reported changed=0 and skipped=1

The same container ran the role against two trees that hold a symbolic link.

  • In a tree that holds a link to a path outside it, the correction fixed the tree and the link
  • The directory outside the tree, and the file in it, keep root as the owner
  • When ~/autoware_data is itself a symbolic link, the correction fixed the resolved directory and its contents
  • A second run skips in both cases

Seven shell fixtures measured the guard and the correction against the GNU tools directly.

The compose files

docker compose config accepts all nine changed files. The resolved configuration shows 17 autoware_data mounts, each with create_host_path: false. Two of them keep read_only: true, which matches the two :ro mounts before the change.

Three tests ran against docker/examples/basic/dev-cpu.compose.yaml, with the image ghcr.io/autowarefoundation/autoware:universe-devel-jazzy. Each one used a throwaway HOME, so the real ~/autoware_data stayed out of the test.

  • With the mount source absent, Compose stopped with bind source path does not exist, and created nothing
  • With the mount entries put back into the short syntax, the container started. The daemon created autoware_data, autoware_data/maps and autoware_data/ml_models as root:root, and rm then refused to remove them
  • With the mount source present, the container ran, read a file through the mount, and reported id -u as 1000

The planning simulator demo

docker/examples/demos/planning-simulator/docker-compose.yaml ran with the image ghcr.io/autowarefoundation/autoware:universe-jazzy. Both autoware_data mounts in that file carry the long syntax from this PR.

  • Compose bound both sources, and the container started
  • Autoware read sample-map-planning through the mount, as the unprivileged aw user
  • A goal pose in rviz produced a route of four segments
  • The vehicle drove, and /api/routing/state then reported 3, which is ARRIVED
  • The vehicle stopped about 3 cm from the goal position

The tree of this host is already user-owned, so the correction skipped during this run.

Not checked

  • Ubuntu 22.04
  • A run of artifacts or demo_artifacts that downloads a real artifact
  • The CUDA image
  • The devcontainer files, which mount the whole ~/autoware_data instead of two directories under it

autoware_data sits in the home directory of the user because every consumer
reads it as that user: the ansible roles, the ROS nodes that write TensorRT
engine files next to the models, and the containers under docker/, which mount
it for the unprivileged aw user.

Older Dockerfiles installed as root, and the download tasks in this repository
used become: true, so many installs hold model files and one model directory
that root owns. A directory that root owns stops all three consumers.

Add the role autoware_data_ownership, which gives the tree to the user that
runs the playbook. It does nothing when the directory is absent, and nothing
when the user already owns every path, so only a repair needs sudo.

Make artifacts and demo_artifacts depend on it, so the tree is user-owned
before any task writes into it.

Remove become: true from every task that writes into autoware_data: 16 in
artifacts and 3 in demo_artifacts. One of the 16 is the task that created the
root-owned diffusion_planner/v3.1 directory. Install unzip keeps become: true,
because apt needs it.

Signed-off-by: Mete Fatih Cırıt <mfc@autoware.org>
@xmfcx xmfcx added the type:installation Issues or improvements related to the installation process of the software. label Aug 7, 2026
@xmfcx xmfcx self-assigned this Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thank you for contributing to the Autoware project!

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

Please ensure:

xmfcx added 3 commits August 7, 2026 19:01
The repair used ansible.builtin.file with recurse: true. That module
follows a symbolic link during the walk, so a link inside the tree let
the repair chown its target outside ~/autoware_data, as root. The guard
used ansible.builtin.find, which does not follow links, so it could not
see the paths the repair would write.

Resolve the directory with readlink -f, then let the guard and the repair
act on that one real path. Both keep the default link behaviour, so they
read and write the same set of paths, and a corrected tree stops the
guard.

The guard also no longer triggers on the skipped_paths of find, which is
set for a vanished file or a path that is not a directory, not only for a
directory that cannot be read.

Signed-off-by: Mete Fatih Cırıt <mfc@autoware.org>
A bind mount in the short syntax defaults to create_host_path: true. When
a path under ~/autoware_data is missing, the daemon creates it as root,
and the unprivileged aw user in the container cannot write it. The
container still starts, so the run fails later with a message about the
data rather than the mount, and the user cannot remove the new
directories without sudo.

No ansible role creates ~/autoware_data/scenarios, so that path was
root-owned even after a complete install.

Move all 17 autoware_data mounts to the long syntax with
create_host_path: false.

Signed-off-by: Mete Fatih Cırıt <mfc@autoware.org>
The path filter listed ansible/roles/artifacts/** only, so a change
confined to ansible/roles/autoware_data_ownership/** selected no files
and the health check did not run. That role runs as a dependency of
artifacts, so the same job exercises it.

Signed-off-by: Mete Fatih Cırıt <mfc@autoware.org>
@xmfcx xmfcx added the run:health-check Run health-check label Aug 7, 2026
@xmfcx
xmfcx marked this pull request as ready for review August 7, 2026 10:21

@mitsudome-r mitsudome-r left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Confirmed that the PR works on my local machine.

@xmfcx
xmfcx enabled auto-merge (squash) August 7, 2026 11:06
@xmfcx
xmfcx merged commit d70326c into autowarefoundation:main Aug 7, 2026
40 of 44 checks passed
@xmfcx
xmfcx deleted the fix/ansible-autoware-data-ownership branch August 8, 2026 14:08
xmfcx added a commit to xmfcx/autoware that referenced this pull request Aug 8, 2026
…Face

Batch E is the last batch of the artifact hosting migration. It removes
the 15 pinned get_url tasks and the 7 directory tasks around them. Five
hf download tasks take their place, and each one is pinned to a tag.

The same 15 files land at the same paths, so the launch files need no
change. The 10 onnx files match the removed pins through the LFS oid of
each tag. The 5 param.json files are too small for LFS, and a direct
download from each tag gives the same 5 hashes.

Every get_url task that this batch removes ran with become: true, so an
install made before the migration holds model files that root owns. hf
runs as the user that runs the playbook, and it cannot write into a
root-owned directory. The autoware_data_ownership role corrects such an
install, and the artifacts role already depends on it.

- Ownership role: autowarefoundation#7260

Signed-off-by: Mete Fatih Cırıt <mfc@autoware.org>
xmfcx added a commit that referenced this pull request Aug 9, 2026
…Face (#7258)

Batch E is the last batch of the artifact hosting migration. It removes
the 15 pinned get_url tasks and the 7 directory tasks around them. Five
hf download tasks take their place, and each one is pinned to a tag.

The same 15 files land at the same paths, so the launch files need no
change. The 10 onnx files match the removed pins through the LFS oid of
each tag. The 5 param.json files are too small for LFS, and a direct
download from each tag gives the same 5 hashes.

Every get_url task that this batch removes ran with become: true, so an
install made before the migration holds model files that root owns. hf
runs as the user that runs the playbook, and it cannot write into a
root-owned directory. The autoware_data_ownership role corrects such an
install, and the artifacts role already depends on it.

- Ownership role: #7260

Signed-off-by: Mete Fatih Cırıt <mfc@autoware.org>
TomcoGame pushed a commit to TomcoGame/Autoware-Assurance-Stack that referenced this pull request Oct 25, 2026
…n#7260)

Signed-off-by: Mete Fatih Cırıt <mfc@autoware.org>
TomcoGame pushed a commit to TomcoGame/Autoware-Assurance-Stack that referenced this pull request Oct 25, 2026
…Face (autowarefoundation#7258)

Batch E is the last batch of the artifact hosting migration. It removes
the 15 pinned get_url tasks and the 7 directory tasks around them. Five
hf download tasks take their place, and each one is pinned to a tag.

The same 15 files land at the same paths, so the launch files need no
change. The 10 onnx files match the removed pins through the LFS oid of
each tag. The 5 param.json files are too small for LFS, and a direct
download from each tag gives the same 5 hashes.

Every get_url task that this batch removes ran with become: true, so an
install made before the migration holds model files that root owns. hf
runs as the user that runs the playbook, and it cannot write into a
root-owned directory. The autoware_data_ownership role corrects such an
install, and the artifacts role already depends on it.

- Ownership role: autowarefoundation#7260

Signed-off-by: Mete Fatih Cırıt <mfc@autoware.org>
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:installation Issues or improvements related to the installation process of the software.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants