Search code examples
ansiblepackagedebianapt

Ansible apt-command failed with given virtual package


In my Ansible role I try to update the PHP version of all installed PHP packages. For this reason I read out all installed PHP packages into a list, manipulate the version number in every element in this list and after that I try to install all packages with the new PHP version out of this list.

my update-Debian.yml (task): (not complete yet)

---

- name: get list of installed php packages
  shell: "dpkg -l | grep php | awk '{print $2}'"
  register: update_php_installed_packages

- name: set_fact php current package lists
  set_fact:
    php_packages_installed: "{{ update_php_installed_packages.stdout_lines }}"

- name: set_fact php wanted package list
  set_fact:
    php_packages_wanted: "{{ php_packages_wanted | default([]) + [item.replace( update_php_current_php_version , update_php_wanted_php_version )] }}"
  loop: "{{ php_packages_installed }}"

- name: "Install new PHP{{ update_php_wanted_php_version }} Packages"
  apt:
    name: "{{ php_packages_wanted }}"
    state: present
    update_cache: true

- name: "Update-Alternatives from PHP{{ update_php_current_php_version }} to PHP{{ update_php_wanted_php_version }}"
  alternatives:
    name: php
    path: "/usr/bin/php{{ update_php_wanted_php_version }}"

- name: "Set Apache2 default PHP-Version to {{ update_php_wanted_php_version }}"
  command: "a2dismod php{{ update_php_current_php_version }}"
- command: "a2enmod php{{ update_php_wanted_php_version }}"
  notify: 
  - restart httpd
  - restart php-fpm

- name: "Install new PHP{{ update_php_wanted_php_version }} Packages"
  apt:
    name: "{{ php_packages_installed }}"
    state: absent

Unfortunately at least the package php7.4-json is a virtual package so the apt command runs into an error:

ASK [ansible-role-update-php : Install new PHP8.2 Packages]
***************************************************************
fatal: [nextcloud01]: FAILED! => {"cache_update_time": 1694096679, "cache_updated": true, "changed": false, "msg": "'/usr/bin/apt-get -y -o "Dpkg::Options::=--force-confdef" -o "Dpkg::Options::=--force-confold" install 'libapache2-mod-php8.2=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-apcu=5.1.22++-1+0~20230618.37+debian12~1.gbp7134d4' 'php8.2-bcmath=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-cli=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-common=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-curl=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-fpm=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-gd=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-gmp=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-igbinary=3.2.14-1+0~20230618.45+debian12~1.gbpcc1dca' 'php8.2-imagick=3.7.0-4+0~20230701.41+debian12~1.gbpbf7e27' 'php8.2-intl=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-json' 'php8.2-mbstring=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-mysql=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-opcache=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-pgsql=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-readline=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-redis=5.3.7++-1+0~20230619.51+debian12~1.gbp4ff337' 'php8.2-smbclient=1.1.1-1+0~20230612.24+debian12~1.gbp8be856' 'php8.2-xml=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-zip=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-cli=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-common=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-opcache=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c' 'php8.2-readline=8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c'' failed: E: Package 'php8.2-json' has no installation candidate\n", "rc": 100, "stderr": "E: Package 'php8.2-json' has no installation candidate\n", "stderr_lines": ["E: Package 'php8.2-json' has no installation candidate"], "stdout": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nPackage php8.2-json is a virtual package provided by:\n php8.2-phpdbg 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c\n php8.2-fpm 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c\n php8.2-cli 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c\n php8.2-cgi 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c\n libphp8.2-embed 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c\n libapache2-mod-php8.2 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c\n\n", "stdout_lines": ["Reading package lists...", "Building dependency tree...", "Reading state information...", "Package php8.2-json is a virtual package provided by:", " php8.2-phpdbg 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c", " php8.2-fpm 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c", " php8.2-cli 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c", " php8.2-cgi 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c", " libphp8.2-embed 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c", " libapache2-mod-php8.2 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c", ""]}

If I try to install these package directly on the machine via apt package manager I get the following error:

root@nc01/# apt install php8.2-json
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Package php8.2-json is a virtual package provided by:
  php8.2-phpdbg 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c
  php8.2-fpm 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c
  php8.2-cli 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c
  php8.2-cgi 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c
  libphp8.2-embed 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c
  libapache2-mod-php8.2 8.2.10-1+0~20230904.33+debian12~1.gbp2dc84c
You should explicitly select one to install.

E: Package 'php8.2-json' has no installation candidate

By hand, I would choose one of the suggested packages an that's it. In this case, there's already in my php-package-list at least one of the suggested packages. But how to solve this problem in Ansible?

I have no idea how to manage this problem. Maybe there is a better solution than my way.


Solution

  • Preamble: when designing a flow with Ansible, try the best you can to forget what you know about executing the said task in the command line and try to focus on using existing Ansible module.

    In your case, that means using the module package_facts rather than dpkg to get the list of installed packages, and then filter the list using Jinja filters, i.e. the select one.

    So:

    - package_facts:
    
    - set_fact: 
        php_packages_installed: >-
          {{ ansible_facts.packages.keys() | select('contains', 'php') }}
    

    Now, regarding the package you are looking for, this package exists in Debian 12, but without a PHP version, so the package is named php-json.

    For this, you could actually build a dictionary of known replacement, something like

    packages_replacements:
      php8.2-json: php-json
      ## you can further extends this dictionary if you find
      ## other packages that does not have a 1:1 replacement
      ## after swapping the PHP version
    

    And use the replace filter to replace those in your list of package.

    Something like:

    - set_fact:
        php_packages_wanted: >-
          {{
            php_packages_wanted
              | default(php_packages_installed)
              | replace(item.key, item.value)
          }}
      loop: >-
        {{
          {update_php_current_php_version: update_php_wanted_php_version}
            | combine(packages_replacements)
            | dict2items
        }}
      vars:
        packages_replacements:
          php8.2-json: php-json
    

    Given those tasks:

    - package_facts:
    
    - set_fact:
        php_packages_installed: >-
          {{ ansible_facts.packages.keys() | select('contains', 'php') }}
    
    - set_fact:
        php_packages_wanted: >-
          {{
            php_packages_wanted
              | default(php_packages_installed)
              | replace(item.key, item.value)
          }}
      loop: >-
        {{
          {update_php_current_php_version: update_php_wanted_php_version}
            | combine(packages_replacements)
            | dict2items
        }}
      vars:
        update_php_current_php_version: 7.4
        update_php_wanted_php_version: 8.2
        packages_replacements:
          php8.2-json: php-json
    
    - debug:
        msg:
          php_packages_installed: "{{ php_packages_installed }}"
          php_packages_wanted: "{{ php_packages_wanted }}"
    

    It would yield:

    ok: [ansible-debian-1] => 
      msg:
        php_packages_installed:
        - php7.4-cli
        - php7.4-common
        - php7.4-curl
        - php7.4-fpm
        - php7.4-gd
        - php7.4-gmp
        - php7.4-igbinary
        - php7.4-json
        php_packages_wanted:
        - php8.2-cli
        - php8.2-common
        - php8.2-curl
        - php8.2-fpm
        - php8.2-gd
        - php8.2-gmp
        - php8.2-igbinary
        - php-json