# Task to enable dnf repos only if they are not enabled

**URL:** <https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905>\
**Category:** Get Help\
**Tags:** task, rhel\
**Created:** [December 5, 2024, 2:10am UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905 "2024-12-05T02:10:12Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![halloleo](https://avatars.discourse-cdn.com/v4/letter/h/ccd318/32.png) [@halloleo](https://forum.ansible.com/u/halloleo)\
**Post date:** [December 5, 2024, 2:10am UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/1 "2024-12-05T02:10:12Z")

</div>

I have a task like this:

```auto
    - name: Enable repos
      ansible.builtin.command:
        cmd: "dnf config-manager --enable {{ item }}"
      loop:
      - codeready-builder-for-rhel-9-x86_64-rpms
      - ansible-automation-platform-2.4-for-rhel-9-x86_64-rpms
      - rhel-9-for-x86_64-supplementary-rpms
      - ...
      become: true

```

This works well to enable the listed repos, however obviously this task _always_ is reported as changed, even when the repos are already enabled.

How can I rewrite the task so that it executes the command only if the respective repo is _not_ enabled already? (Then I would get the desired `ok` result instead of a `changed` result for already enabled repos.)

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [December 5, 2024, 4:21am UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/2 "2024-12-05T04:21:26Z")

</div>

I’m not sure if you have on rhel-9 the equivalent of `dnf5 repo list --json --disabled` as in the first task below, but if you do then you should be able to get this to work. (The commands below work on Fedora 41, although of course the repo IDs are different.)

```yaml
    - name: List disabled dnf repos
      ansible.builtin.command: dnf5 repo list --json --disabled
      register: repos_disabled

    - name: Enable only the repos of interest
      ansible.builtin.command:
        cmd: "dnf config-manager --enable {{ item }}"
      become: true
      vars:
        to_enable:
          - codeready-builder-for-rhel-9-x86_64-rpms
          - ansible-automation-platform-2.4-for-rhel-9-x86_64-rpms
          - rhel-9-for-x86_64-supplementary-rpms
      loop: '{{ repos_disabled.stdout
                | from_json
                | map(attribute="id")
                | intersect(to_enable) }}'

```

---

<div class="post-metadata">

**Author:** ![halloleo](https://avatars.discourse-cdn.com/v4/letter/h/ccd318/32.png) [@halloleo](https://forum.ansible.com/u/halloleo)\
**Post date:** [December 5, 2024, 6:22am UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/3 "2024-12-05T06:22:03Z")

</div>

Thanks for chiming in!

> [@utoddl](#):
>
> I’m not sure if you have on rhel-9 the equivalent of `dnf5 repo list --json --disabled`

`dnf5` is not available, but `dnf repolist --disabled` works, so I might cut the repo names out of this…

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [December 5, 2024, 1:37pm UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/4 "2024-12-05T13:37:30Z")

</div>

Then you have two problems: (1) parsing the results from `dnf repolist --disabled`, and (2) parsing the registered result from the “repolist” task in the “config-manager” task.

You could just write the IDs of those repos to stdout, which you would register, and the 2nd task could use `result.stdout_lines` which, conveniently enough, is a list version of `result.stdout`. In fact, in this case, you probably should.

However, parsing “stuff” and generating JSON from bash not hard, and the technique may be useful especially if you have more complicated datums than single strings to return. So here’s the “create the list as JSON even though `dnf repolist` doesn’t support `--json`” version.

```yaml
    - name: List disabled dnf repos
      args:
        executable: /bin/bash
      ansible.builtin.shell: |
        seen_header=no
        declare -a repos
        while read -r -a repo ; do
          if ["${repo[0]} ${repo[1]}" = "repo id" ] ; then
            seen_header=yes
            continue
          fi
          ["${seen_header}" = "yes"] || continue
          repos+=( "\"${repo[0]}\"" ) # double-quoted!
        done < <( dnf repolist --disabled )
        # Print JSON to stdout
        IFS=',';
        printf "[%s]\n" "${repos[*]}"
      become: true
      changed_when: false
      register: repos_disabled

    - name: Enable only the repos of interest
      ansible.builtin.command:
        cmd: "dnf config-manager --enable {{ item }}"
      become: true
      register: result
      changed_when: result.rc == 0
      vars:
        to_enable:
          - codeready-builder-for-rhel-9-x86_64-rpms
          - ansible-automation-platform-2.4-for-rhel-9-x86_64-rpms
          - rhel-9-for-x86_64-supplementary-rpms
      loop: '{{ repos_disabled.stdout
                | from_json
                | intersect(to_enable) }}'

```

A couple of things worth pointing out.

First, because JSON likes double-quoted strings, go ahead and add the enclosing quotes at the time the strings are added to the array. That leaves only the problem of getting commas between the entries when we get around to producing the JSON.

Second, `"${repos[*]}"` will separate all the strings in the `repos` array with the first character of `$IFS`, so set that to a comma. (It’s right at the end of the script; otherwise we’d set it back to something sane, like `$' \t\n'`.)

Third, because our JSON is just a list of strings, we don’t have to use  
“`| map(attribute="id")`” in the second task.

There you go - a much fancier hammer than this little nail requires, but people seem to have this _thing_ about “don’t use the shell” when they would be perfectly happy using the same logic if it were wrapped in Python and hidden away in a plugin or module. Go figure.

---

<div class="post-metadata">

**Author:** ![Denney-tech](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/denney-tech/32/1556_2.png) [@Denney-tech](https://forum.ansible.com/u/Denney-tech)\
**Post date:** [December 5, 2024, 2:35pm UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/5 "2024-12-05T14:35:31Z")

</div>

If you’re comfortable with using the community.general collection, there is a [dnf\_config\_manager](https://docs.ansible.com/ansible/latest/collections/community/general/dnf_config_manager_module.html) module specifically for enabling/disabling repositories. There’s also the [rhsm\_repository](https://docs.ansible.com/ansible/latest/collections/community/general/rhsm_repository_module.html) module for managing repositories through the subscription manager on entitled RHEL hosts. This might be preferred since you appear to be using RHEL 9, not CentOS 9 or other equivalent.

```yaml
    - name: Enable repos
      community.general.dnf_config_manager:
        name:
          - codeready-builder-for-rhel-9-x86_64-rpms
          - ansible-automation-platform-2.4-for-rhel-9-x86_64-rpms
          - rhel-9-for-x86_64-supplementary-rpms
        state: enabled
      when: ansible_distribution_major_version == 9

    - name: Enable repos
      community.general.rhsm_repository:
        name:
          - codeready-builder-for-rhel-9-x86_64-rpms
          - ansible-automation-platform-2.4-for-rhel-9-x86_64-rpms
          - rhel-9-for-x86_64-supplementary-rpms
        state: enabled
      when: ansible_distribution_major_version == 9

    # This module appears to support wildcards
    - name: Enable repos 
      community.general.rhsm_repository:
        name:
          - codeready-builder-for-rhel-*-x86_64-rpms
          - ansible-automation-platform-2.4-for-rhel-*-x86_64-rpms
          - rhel-*-for-x86_64-supplementary-rpms
        state: enabled

```

---

<div class="post-metadata">

**Author:** ![bvitnik](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bvitnik/32/4132_2.png) [@bvitnik](https://forum.ansible.com/u/bvitnik)\
**Post date:** [December 5, 2024, 2:50pm UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/6 "2024-12-05T14:50:04Z")

</div>

Here is another alternative.

Since repo files in `yum.repos.d` directory are just .ini files, you can also use `community.general.ini_file` module. It’s a bit low level but should work as expected. Here is an example:

```auto
- name: "Configure YUM repos"
  community.general.ini_file:
    path: "{{ item['file'] }}"
    section: "{{ item['name'] }}"
    option: "enabled"
    value: "1"
    no_extra_spaces: true
    create: false
  loop:
  - file: "/etc/yum.repos.d/redhat.repo"
    name: "codeready-builder-for-rhel-9-x86_64-rpms"
  - ...

```

The unfortunate thing is that in RHEL, `subscription-manager` likes to make a mess and overwrite any manual modification you have done to the repos.

---

<div class="post-metadata">

**Author:** ![halloleo](https://avatars.discourse-cdn.com/v4/letter/h/ccd318/32.png) [@halloleo](https://forum.ansible.com/u/halloleo)\
**Post date:** [December 9, 2024, 1:31am UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/7 "2024-12-09T01:31:59Z")

</div>

This approach (1stly constructing a JSON list via a shell script, then 2ndly using `intersect` to get the repos to be enabled and then 3rdly feed them to dnf) works for me. Thanks a lot.

A few syntactically changes to make it run for me:

```auto
    - name: Record disabled dnf repos
      ansible.builtin.shell: |
        seen_header=no
        declare -a repos
        while read -r -a repo ; do
          if ["${repo[0]} ${repo[1]}" = "repo id" ] ; then
            seen_header=yes
            continue
          fi
          ["${seen_header}" = "yes"] || continue
          repos+=( "\"${repo[0]}\"" ) # double-quoted!
        done < <( dnf repolist --disabled )
        # Print JSON to stdout
        IFS=',';
        printf "[%s]\n" "${repos[*]}"
      args:
        executable: /bin/bash # <-- this needs to be in the args section
      become: true
      changed_when: false
      register: repos_disabled

```

and

```auto
    - name: Enable necessary repos only if not yet enabled
      ansible.builtin.command:
        cmd: "dnf config-manager --enable {{ item }}"
      register: act_res
      changed_when: act_res.rc == 0
      vars:
        to_enable:
          - codeready-builder-for-rhel-9-x86_64-rpms
          - ansible-automation-platform-2.4-for-rhel-9-x86_64-rpms
          - rhel-9-for-x86_64-supplementary-rpms
      loop: '{{ repos_disabled.stdout
                | from_json
                | intersect(to_enable) }}'
      become: true # <-- this needs sudo

```

Thanks again! 🙂

---

<div class="post-metadata">

**Author:** ![halloleo](https://avatars.discourse-cdn.com/v4/letter/h/ccd318/32.png) [@halloleo](https://forum.ansible.com/u/halloleo)\
**Post date:** [December 9, 2024, 1:35am UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/8 "2024-12-09T01:35:40Z")

</div>

> [@Denney-tech](#):
>
> If you’re comfortable with using the community.general collection,

I think I would be comfortable with it, but the collection doesn;t seem to be installed. (I get a syntax error on `community.general.dnf_config_manager:`) And on an air-gapped machine I don’t want to add stuff if not absolutely necessary.

---

<div class="post-metadata">

**Author:** ![Denney-tech](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/denney-tech/32/1556_2.png) [@Denney-tech](https://forum.ansible.com/u/Denney-tech)\
**Post date:** [December 9, 2024, 4:56am UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/9 "2024-12-09T04:56:11Z")

</div>

That’s perfectly fine to keep dependencies to a minimum if need be. Depending on how you installed `ansible-core`, you most likely could install `ansible` as well, which is the “batteries included” version that includes the `community.general` collection among many others.

---

<div class="post-metadata">

**Author:** ![halloleo](https://avatars.discourse-cdn.com/v4/letter/h/ccd318/32.png) [@halloleo](https://forum.ansible.com/u/halloleo)\
**Post date:** [December 9, 2024, 5:18am UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/10 "2024-12-09T05:18:03Z")

</div>

> [@Denney-tech](#):
>
> Depending on how you installed `ansible-core`

I installed `ansible-core` via `dnf` from available local mirrors of the RHEL9 repos.

> [@Denney-tech](#):
>
> you most likely could install `ansible` as well, which is the “batteries included” version

I cannot see a plain `ansible` package. When I do a search via `dnf` I get:

```auto
ansible-automation-platform-common
ansible-automation-platform-installer
ansible-automation-platform-selinux
ansible-builder
ansible-collection-microsoft-sql
ansible-collection-redhat-rhel_mgmt
ansible-core
ansible-dev-tools
ansible-freeipa
ansible-freeipa-collection
ansible-freeipa-tests
ansible-lint
ansible-navigator
ansible-pcp
ansible-rulebook
ansible-runner
ansible-sign
ansible-test

```

I don’t think the communty stuff is in theer.

---

<div class="post-metadata">

**Author:** ![Denney-tech](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/denney-tech/32/1556_2.png) [@Denney-tech](https://forum.ansible.com/u/Denney-tech)\
**Post date:** [December 9, 2024, 5:58am UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/11 "2024-12-09T05:58:44Z")

</div>

For RHEL9 and other rpm based distros, the `ansible` rpm package is available from EPEL.

You could also install via pip if you have [pypi.org](http://pypi.org) or local mirrors available.

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [December 9, 2024, 3:27pm UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/12 "2024-12-09T15:27:49Z")

</div>

While I stand by my proposed two-task solution above as a valid answer to the very specific _original ask_ (with the corrections which you pointed out; thanks @halloleo), It’s always a good idea to step back, look at the larger picture, and question how we got into a situation that needed such an extraordinary solution in the first place.

For one thing, some package management modules allow temporarily enabling repositories during their operation, so it may not be necessary to enable them as a separate step anyway. It depends on why you want them enabled. If it’s just to install packages from them, you can skip enabling them first.

From a bigger picture perspective, even if you determine you don’t need other collections right now, it’s extremely unlikely that you never will. So you’d probably do yourself a favor to work out how to use the suggestions @Denney-tech is recommending _now_ in your air-gapped environment so you’ll have less to struggle through in a genuine crunch later.

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/ansible/original/1X/1882d9a01da5d8996a862359b0913cc568a39b84.svg) [@system](https://forum.ansible.com/u/system)\
**Post date:** [January 8, 2025, 3:27pm UTC](https://forum.ansible.com/t/task-to-enable-dnf-repos-only-if-they-are-not-enabled/38905/13 "2025-01-08T15:27:54Z")

</div>

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
