Skip to content

refactor: support Ansible 2.19#261

Merged
richm merged 1 commit into
linux-system-roles:mainfrom
richm:fix-ansible-2.19
Jul 2, 2025
Merged

refactor: support Ansible 2.19#261
richm merged 1 commit into
linux-system-roles:mainfrom
richm:fix-ansible-2.19

Conversation

@richm

@richm richm commented Jul 1, 2025

Copy link
Copy Markdown
Collaborator

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

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

Summary by Sourcery

Refactor kernel settings role to support Ansible 2.19 by replacing in-place list/dict mutations with Jinja filters and set_fact loops, adding explicit boolean and length checks, and updating templates and tests accordingly.

Enhancements:

  • Refactor tasks/main.yml to initialize and build new sysctl and sysfs dictionaries via set_fact loops and combine filter instead of in-place mutations
  • Introduce explicit is not none and | length > 0 checks for boolean expressions in variable assignments and when conditions
  • Simplify kernel_settings.j2 by removing imperative dict mutations, adding a write_section macro, and conditionally rendering sections based on value presence

Tests:

  • Update assert_kernel_settings_conf_files.yml to require is not none and | length > 0 in when conditions
  • Rename ansible_managed lookup variable to __ansible_managed in check_header.yml

@sourcery-ai

sourcery-ai Bot commented Jul 1, 2025

Copy link
Copy Markdown

Reviewer's Guide

This PR refactors playbook tasks and templates to support Ansible 2.19’s immutable Jinja data structures and stricter boolean evaluations by replacing in-template mutations with set_fact/combine loops, introducing a Jinja macro for sections, and adding explicit length checks in conditionals.

Entity relationship diagram for kernel settings data structures

erDiagram
    KERNEL_SETTINGS_PROFILE_CONTENTS ||--o{ SYSCTL : contains
    KERNEL_SETTINGS_PROFILE_CONTENTS ||--o{ SYSFS : contains
    KERNEL_SETTINGS_PROFILE_CONTENTS ||--o{ SYSTEMD : contains
    KERNEL_SETTINGS_PROFILE_CONTENTS ||--o{ VM : contains
    SYSCTL {
      string name
      value value
    }
    SYSFS {
      string name
      value value
    }
    SYSTEMD {
      cpu_affinity value
    }
    VM {
      transparent_hugepages value
      transparent_hugepage_defrag value
    }
Loading

Class diagram for Jinja data structure handling before and after Ansible 2.19

classDiagram
    class JinjaTemplateOld {
      +__settings: dict
      +__setitem__()
      +setdefault()
      +update()
      +Mutates dicts/lists in-place
    }
    class JinjaTemplateNew {
      +__kernel_settings_new_sysctl: dict
      +__kernel_settings_new_sysfs: dict
      +combine()
      +No in-place mutation
      +Uses set_fact and loops
    }
    JinjaTemplateOld <|-- JinjaTemplateNew : Refactored
Loading

Class diagram for kernel settings template macro and variables

classDiagram
    class KernelSettingsTemplate {
      +write_section(section_name, settings)
      +__sysctl_has_values: bool
      +__sysfs_has_values: bool
      +__systemd_new: value
      +__trans_huge_new: value
      +__trans_defrag_new: value
    }
    class Macro_write_section {
      +section_name: str
      +settings: dict
      +Outputs INI section
    }
    KernelSettingsTemplate o-- Macro_write_section : uses
Loading

Flow diagram for sysctl/sysfs dict construction with set_fact and combine

flowchart TD
    A[Start: Load old sysctl/sysfs] --> B[Initialize new dict with old values]
    B --> C[Loop over new items]
    C --> D[Combine mutated item into new dict]
    D --> E[Repeat for all items]
    E --> F[Set new dict as final sysctl/sysfs]
    F --> G[Apply kernel settings template]
Loading

File-Level Changes

Change Details Files
Replace in-place dict/list mutations with set_fact loops using combine filter
  • Initialize new state facts from old profiles
  • Loop over sysctl/sysfs items to combine mutated entries
  • Compute new values with explicit absent/present logic
tasks/main.yml
Refactor kernel_settings.j2 to remove Python mutators and use a Jinja macro
  • Remove setdefault/setitem loops for building __settings
  • Add write_section macro for each section
  • Conditionally render sections based on new fact checks
templates/kernel_settings.j2
Enforce explicit boolean tests in when/assert clauses
  • Change var checks to use `
d() is not none</li><li>Add

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

@sourcery-ai sourcery-ai Bot 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.

Hey @richm - I've reviewed your changes - here's some feedback:

  • Consolidate the nearly identical set_fact loops for sysctl and sysfs into a shared include or iterate over both sections in a single loop to reduce duplication.
  • In the Jinja2 template macro, values are rendered without quoting—consider wrapping {{ val }} in quotes or using the quote filter to avoid issues when values contain spaces or special characters.
  • Rather than checking var | length > 0, you could use var | bool or var | default([]) | bool to make empty‐value tests more concise and intention-revealing.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consolidate the nearly identical set_fact loops for sysctl and sysfs into a shared include or iterate over both sections in a single loop to reduce duplication.
- In the Jinja2 template macro, values are rendered without quoting—consider wrapping `{{ val }}` in quotes or using the `quote` filter to avoid issues when values contain spaces or special characters.
- Rather than checking `var | length > 0`, you could use `var | bool` or `var | default([]) | bool` to make empty‐value tests more concise and intention-revealing.

## Individual Comments

### Comment 1
<location> `templates/kernel_settings.j2:3` </location>
<code_context>
-{%     if section[key] != __kernel_settings_state_absent %}
-{%       if not seen_sections.__contains__(section_name) %}
-{%         set _ = seen_sections.__setitem__(section_name, true) %}
+{% macro write_section(section_name, settings) %}
 [{{ section_name }}]
-{%       endif %}
-{{ key }} = {{ section[key] }}
+{%   for key, val in settings.items() %}
+{%     if val != {"state": "absent"} %}
+{{ key }} = {{ val }}
 {%     endif %}
 {%   endfor %}
-{% endfor %}
+{% endmacro %}
+[main]
+summary = kernel settings
</code_context>

<issue_to_address>
The macro write_section does not filter out keys with values that are empty strings or None.

Previously, keys with None or empty string values were omitted from the output. The current macro may now include these values. If this is unintended, please reintroduce the filtering for None and empty strings.
</issue_to_address>

### Comment 2
<location> `templates/kernel_settings.j2:6` </location>
<code_context>
-{%       endif %}
-{{ key }} = {{ section[key] }}
+{%   for key, val in settings.items() %}
+{%     if val != {"state": "absent"} %}
+{{ key }} = {{ val }}
 {%     endif %}
</code_context>

<issue_to_address>
The check for val != {"state": "absent"} assumes no other dict values will be present.

If other dict values are possible, this check may behave unexpectedly. Make the check more robust or document the assumption clearly.
</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.

Comment thread templates/kernel_settings.j2
Comment thread templates/kernel_settings.j2
@richm

richm commented Jul 1, 2025

Copy link
Copy Markdown
Collaborator Author

[citest]

1 similar comment
@richm

richm commented Jul 1, 2025

Copy link
Copy Markdown
Collaborator Author

[citest]

Comment thread tasks/main.yml Outdated
@richm richm force-pushed the fix-ansible-2.19 branch from 52bc792 to f11fb0f Compare July 1, 2025 20:44
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
```

Signed-off-by: Rich Megginson <rmeggins@redhat.com>
@richm richm force-pushed the fix-ansible-2.19 branch from f11fb0f to a984315 Compare July 1, 2025 22:44
@richm

richm commented Jul 1, 2025

Copy link
Copy Markdown
Collaborator Author

[citest]

1 similar comment
@richm

richm commented Jul 2, 2025

Copy link
Copy Markdown
Collaborator Author

[citest]

@richm

richm commented Jul 2, 2025

Copy link
Copy Markdown
Collaborator Author

[citest]

1 similar comment
@richm

richm commented Jul 2, 2025

Copy link
Copy Markdown
Collaborator Author

[citest]

@richm richm merged commit 12840f2 into linux-system-roles:main Jul 2, 2025
43 of 44 checks passed
@richm richm deleted the fix-ansible-2.19 branch July 2, 2025 21:04
@codecov

codecov Bot commented Jul 2, 2025

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 0.00%. Comparing base (1659d9b) to head (a984315).
Report is 62 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##             main   #261       +/-   ##
=========================================
- Coverage   72.66%      0   -72.67%     
=========================================
  Files           1      0        -1     
  Lines         450      0      -450     
=========================================
- Hits          327      0      -327     
+ Misses        123      0      -123     
Flag Coverage Δ
sanity ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.

2 participants