Search code examples
dockerpuppet

How do I ensure legacy Docker packages are uninstalled before installing the new packages?


We have some older servers with the legacy Docker package installed from the distribution package repository. Either from manual installs

$ yum install docker

Or older manifests

package { 'docker': 
    ensure => present,
}

We want to migrate to the official Docker repository and packages via the "supported" puppetlabs-docker module.

include docker

However, the legacy Docker packages are not removed or otherwise managed by this new module!

[vagrant@localhost ~]$ sudo -i puppet apply -e 'include docker'
Notice: Compiled catalog for localhost.localdomain in environment production in 0.42 seconds
Notice: /Stage[main]/Docker::Repos/Yumrepo[docker]/ensure: created
Error: Execution of '/bin/yum -d 0 -e 0 -y install docker-ce' returned 1: Error: docker-ce conflicts with 2:docker-1.13.1-75.git8633870.el7.centos.x86_64
 You could try using --skip-broken to work around the problem
 You could try running: rpm -Va --nofiles --nodigest
Error: /Stage[main]/Docker::Install/Package[docker]/ensure: change from 'purged' to 'present' failed: Execution of '/bin/yum -d 0 -e 0 -y install docker-ce' returned 1: Error: docker-ce conflicts with 2:docker-1.13.1-75.git8633870.el7.centos.x86_64
 You could try using --skip-broken to work around the problem
 You could try running: rpm -Va --nofiles --nodigest

How do we make sure the legacy package is removed before installing the new package?


Solution

  • You want to start by ensuring the legacy package is absent.

    package { 'docker':
        ensure => absent,
    }
    

    But, you can't use Package['docker'], because the new puppetlabs-docker module already declares it. You have to do something like this:

    package { 'legacy-docker':
        ensure => absent,
        name => 'docker',
    }
    

    There are a number of legacy prerequisite packages that also need to be removed, too.

    package { [
        'docker-client',
        'docker-client-latest',
        'docker-common',
        'docker-latest',
        'docker-latest-logrotate',
        'docker-logrotate',
        'docker-selinux',
        'docker-engine-selinux',
        'docker-engine',
    ]:
        ensure => absent,
    }
    

    So, all together, this ordering seems to work implicitly.

    package { 'legacy-docker':
        ensure => absent,
        name => 'docker',
    }
    
    package { [
        'docker-client',
        'docker-client-latest',
        'docker-common',
        'docker-latest',
        'docker-latest-logrotate',
        'docker-logrotate',
        'docker-selinux',
        'docker-engine-selinux',
        'docker-engine',
    ]:
        ensure => absent,
    }
    
    include docker
    

    Actually, this seems to cause problems on subsequent runs of the manifest... common second-level dependencies are removed!

    Error: Execution of '/bin/rpm -e container-selinux-2.68-1.el7.noarch' returned 1: error: Failed dependencies:
            container-selinux >= 2.9 is needed by (installed) docker-ce-18.06.1.ce-3.el7.x86_64
    Error: /Stage[main]/Profile::Docker/Package[docker-selinux]/ensure: change from '2:2.68-1.el7' to 'absent' failed: Execution of '/bin/rpm -e container-selinux-2.68-1.el7.noarch' returned 1: error: Failed dependencies:
            container-selinux >= 2.9 is needed by (installed) docker-ce-18.06.1.ce-3.el7.x86_64
    Error: Execution of '/bin/rpm -e container-selinux-2.68-1.el7.noarch' returned 1: error: Failed dependencies:
            container-selinux >= 2.9 is needed by (installed) docker-ce-18.06.1.ce-3.el7.x86_64
    Error: /Stage[main]/Profile::Docker/Package[docker-engine-selinux]/ensure: change from '2:2.68-1.el7' to 'absent' failed: Execution of '/bin/rpm -e container-selinux-2.68-1.el7.noarch' returned 1: error: Failed dependencies:
            container-selinux >= 2.9 is needed by (installed) docker-ce-18.06.1.ce-3.el7.x86_64
    

    Well, the package resource doesn't have a refreshonly kind of property, so we need to resort to an exec resource. Ugh.

    package { 'legacy-docker':
        ensure => absent,
        name => 'docker',
        notify => Exec['autoremove'],
    }
    
    exec { 'autoremove':
        command => '/usr/bin/yum -y autoremove',
        refreshonly => true,
    }
    
    include docker
    

    This is... reasonable? The only thing might be ordering, you might explore explicit resource ordering using ->.