Skip to content

Conversation

@richm
Copy link
Contributor

@richm richm commented Jul 8, 2025

Ansible 2.19 introduces some big changes
https://docs.ansible.com/ansible/devel/porting_guides/porting_guide_core_2.19.html

One big change is that data structures are no longer mutable by the use of python
methods such as __setitem__, setdefault, update, etc. in Jinja constructs.
Instead, items must use filters or other Jinja operations.

One common idiom is to mutate each element in a list. Since we cannot do this
"in-place" anymore, a common way to do this is:

- name: Construct a new list from an existing list and mutate each element
  set_fact:
    __new_list: "{{ __new_list | d([]) + [mutated_item] }}"
  loop: "{{ old_list }}"
  mutated_item: "{{ some value based on item from old list }}"

- name: Reset original old list
  set_fact:
    old_list: "{{ __new_list }}"

Similarly with dict items:

- name: Construct a new dict from an existing dict and mutate each element
  set_fact:
    __new_dict: "{{ __new_dict | d({}) | combine(mutated_item) }}"
  loop: "{{ old_dict | dict2items }}"
  mutated_item: "{{ {item.key: mutation of item.value} }}"

- name: Reset original old dict
  set_fact:
    old_dict: "{{ __new_dict }}"

Another big change is that a boolean expression in a when or similar construct
must be converted to a boolean - we cannot rely on the implicit evaluation in
a boolean context. For example, if var is some iterable, like a dict, list,
or string, you used to be able to evaluate an empty value in a boolean context:

when: var  # do this only if var is not empty

You now have to explicitly test for empty using length:

when: var | length > 0  # do this only if var is not empty

Similarly for int values - you cannot rely on 0 being evaluated as false
and non-zero true - you must explicitly compare the values with == or !=

These are the biggest changes. See the porting guide for others.

Signed-off-by: Rich Megginson rmeggins@redhat.com

Summary by Sourcery

Adapt role tasks to comply with Ansible 2.19’s immutable data structures by replacing in-place Jinja mutations with filter-based constructions and update tests to pass new ansible-lint checks

Enhancements:

  • Refactor manage_user_info and get_update_user_info tasks to use filter-based list and dict construction with combine for Ansible 2.19 immutability
  • Add explicit reset of __systemd_dict_list before building the new list

Tests:

  • Rename duplicate task names in tests_basic.yml and tests_user_units.yml to satisfy ansible-lint uniqueness rules

Ansible 2.19 introduces some big changes
https://docs.ansible.com/ansible/devel/porting_guides/porting_guide_core_2.19.html

One big change is that data structures are no longer mutable by the use of python
methods such as `__setitem__`, `setdefault`, `update`, etc.  in Jinja constructs.
Instead, items must use filters or other Jinja operations.

One common idiom is to mutate each element in a list.  Since we cannot do this
"in-place" anymore, a common way to do this is:

```yaml
- name: Construct a new list from an existing list and mutate each element
  set_fact:
    __new_list: "{{ __new_list | d([]) + [mutated_item] }}"
  loop: "{{ old_list }}"
  mutated_item: "{{ some value based on item from old list }}"

- name: Reset original old list
  set_fact:
    old_list: "{{ __new_list }}"
```

Similarly with `dict` items:

```yaml
- name: Construct a new dict from an existing dict and mutate each element
  set_fact:
    __new_dict: "{{ __new_dict | d({}) | combine(mutated_item) }}"
  loop: "{{ old_dict | dict2items }}"
  mutated_item: "{{ {item.key: mutation of item.value} }}"

- name: Reset original old dict
  set_fact:
    old_dict: "{{ __new_dict }}"
```

Another big change is that a boolean expression in a `when` or similar construct
must be converted to a boolean - we cannot rely on the implicit evaluation in
a boolean context.  For example, if `var` is some iterable, like a `dict`, `list`,
or `string`, you used to be able to evaluate an empty value in a boolean context:

```yaml
when: var  # do this only if var is not empty
```

You now have to explicitly test for empty using `length`:

```yaml
when: var | length > 0  # do this only if var is not empty
```

Similarly for `int` values - you cannot rely on `0` being evaluated as false
and non-zero true - you must explicitly compare the values with `==` or `!=`

These are the biggest changes.  See the porting guide for others.

Signed-off-by: Rich Megginson <rmeggins@redhat.com>
@richm richm requested a review from spetrosi as a code owner July 8, 2025 20:51
@sourcery-ai
Copy link

sourcery-ai bot commented Jul 8, 2025

Reviewer's Guide

Refactored in-place Jinja mutations to use Ansible 2.19–compliant filters and combine operations for list and dict construction, and updated test task names to satisfy the new ansible-lint requirements.

File-Level Changes

Change Details Files
Rewrite __systemd_dict_list construction without in-place Jinja mutations
  • Insert a reset step to initialize __systemd_dict_list to an empty list
  • Replace the long Jinja loop with a set_fact task building the list via __systemd_dict_list + [newitem
combine(...)]
  • Define scoped vars (newitem, newstate, newuser, etc.) for each element instead of calling Python methods
  • Replace dict mutation in __systemd_user_info with combine filter
    • Change set_fact to merge in a user_dict via __systemd_user_info
    combine({key: user_dict})
  • Introduce vars getent and user_dict to compute xdg_dir, units_dir, and group without setitem or update
  • Standardize test task names with numeric suffixes for ansible-lint
    • Append incremental suffixes (– 2, – 3, etc.) to UnitFileState retrieval task names
    • Ensure consistent naming format in tests_basic.yml and tests_user_units.yml
    tests/tests_basic.yml
    tests/tests_user_units.yml

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    @richm
    Copy link
    Contributor Author

    richm commented Jul 8, 2025

    [citest]

    Copy link

    @sourcery-ai sourcery-ai bot left a comment

    Choose a reason for hiding this comment

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

    Hey @richm - I've reviewed your changes and they look great!

    Prompt for AI Agents
    Please address the comments from this code review:
    ## Individual Comments
    
    ### Comment 1
    <location> `tasks/manage_user_info.yml:42` </location>
    <code_context>
    -          {%   set _ = rv.append(newitem) %}
    -          {% endfor %}
    -          {{ rv }}
    +        __systemd_dict_list: "{{ __systemd_dict_list +
    +          [newitem | combine(newstate, newuser, newgroup, newxdg_dir, newunits_dir, newmode, newdir_mode)] }}"
    +      loop: "{{ __systemd_list }}"
    </code_context>
    
    <issue_to_address>
    Appending to a fact in a loop can lead to race conditions or unexpected results in Ansible.
    
    Appending to a list with set_fact inside a loop can cause lost updates or only the last value being set. Use a map or accumulate values, then set_fact once after the loop.
    </issue_to_address>
    
    ### Comment 2
    <location> `tasks/manage_user_info.yml:53` </location>
    <code_context>
    +        newuser:
    +          user: "{{ item['user'] if item is mapping and 'user' in item
    +            else 'root' }}"
    +        newgroup:
    +          group: "{{ item['group'] if item is mapping and 'group' in item
    +            else __systemd_user_info[newuser['user']]['group'] }}"
    +        newxdg_dir:
    </code_context>
    
    <issue_to_address>
    Accessing __systemd_user_info[newuser['user']] assumes the user exists, which may not always be true.
    
    Handle the case where newuser['user'] is not in __systemd_user_info to prevent errors.
    </issue_to_address>

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    @richm richm merged commit 0fd8636 into linux-system-roles:main Jul 9, 2025
    35 checks passed
    @richm richm deleted the fix-ansible-2.19 branch July 9, 2025 15:51
    @codecov
    Copy link

    codecov bot commented Jul 9, 2025

    Codecov Report

    All modified and coverable lines are covered by tests ✅

    Please upload report for BASE (main@6edeb8d). Learn more about missing BASE report.

    Additional details and impacted files
    @@         Coverage Diff          @@
    ##             main   #96   +/-   ##
    ====================================
      Coverage        ?     0           
    ====================================
      Files           ?     0           
      Lines           ?     0           
      Branches        ?     0           
    ====================================
      Hits            ?     0           
      Misses          ?     0           
      Partials        ?     0           

    ☔ View full report in Codecov by Sentry.
    📢 Have feedback on the report? Share it here.

    🚀 New features to boost your workflow:
    • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
    • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    1 participant