Damn.

Something I didn't want to hear while showing up to work:

I have excellent news for you! You're gonna like that!

Usually, when my coworker says that with that grin. Its smells fishy.

Really fishy.

Soooo, we will soon change the base OS of the company and we will switch to Amazon Linux 2022. What do you think about that?

The smell of rotten dead rabbit swished to my nose instantly.

@daywork we maintain a digital factory. And one of it's legacy core part is based on the fact that our Puppet infrastructure just works.

But before we leave it to die in its own vomit, we need to keep it alive until the company find out a new way to deploy teams' components.

Shirt.

We need a plan

Okay, so, what do we know?

  • The destination Operating System is Amazon Linux 2022
  • We want a Puppet infrastructure to work without too much hassle.
  • Our current version of Puppet is old. Really old. Kind of "EOLed in January 2021" old: Puppet 5
  • Our current base OS is CentOS 7, not yet EOLed (Maintenance Updates until 2024-06-30)
  • We have other tasks at the moment requiring our immediate attention at work
  • I don't want to have some mental charge about "Hey, maybe it won't work and we'll be fracked?"
  • I have too much time at home
  • I like dirty things and to try unexpected things

Let's get dirty

What's in the box?

Before trying things, let's check what is Amazon Linux 2022 made of.

It's User Guide specifies that:

  • It is based on Fedora as upstream
  • The AMI uses XFS as root filesystem
  • systemd-networkd service manages the network interface (The plague continue to spreads...)
  • Core toolchains versions are upgraded
  • It uses DNF rather than YUM for package managment
  • Some changes about SSHD
  • No EPEL or EPEL-like repositories currently work (oof)

There is no ISO to install outside of AWS. (This is something you could do with Amazon Linux 2), but there are container images availables on the Amazon ECR Public registry and the Docker Hub.

So... lets check that:

docker run -it amazonlinux:2022 /bin/bash

Unable to find image 'amazonlinux:2022' locally
2022: Pulling from library/amazonlinux
f692b7ceefbf: Pull complete 
Digest: sha256:62cc92115a0ba23ce26eaefe295b448e8319f69956828b04a80fa10481c0bb42
Status: Downloaded newer image for amazonlinux:2022

curl --version
curl 7.86.0 (x86_64-amazon-linux-gnu) libcurl/7.86.0 OpenSSL/3.0.5 zlib/1.2.11 libidn2/2.3.2 nghttp2/1.45.1

Ahah. This is gonna be fun. Yeah, I like to check the curl version because the 7.86.0 has issues with no_proxy...

How bad is the situation

Puppet 5 System Requirements

The Puppet 5 System Requirements states that it tested and published official puppet-agent package for:

Red Hat Enterprise Linux derivatives include Amazon Linux v1 (using RHEL 6 packages) and v2 (using RHEL 7 packages).

So Amazon Linux v2, the current AWS Linux optimized OS, was supported... that's one thing.

Also, Fedora was supported from version 26 to 29.

But as stated here (TODO), there is no EPEL repository available, so better check the others prerequisites:

  • Ruby 2.4.X and some mandatories libraries
  • Uh ... that is all? Well...

Even if we don't use the Enterprise version, the archived 2018.1 system requirements seems ok.

So... maybe it could work?

Enter Vagrant

I wanted to try Vagrant with the Docker provider.

Why? Why not? FOR SCIENCE!!!

# I have ~/.bin/ in my path
$ cd ~/.bin
$ wget https://releases.hashicorp.com/vagrant/2.3.4/vagrant_2.3.4_linux_amd64.zip
$ unzip vagrant_2.3.4_linux_amd64.zip 
$ mv vagrant vagrant_2.3.4
$ ln -s vagrant_2.3.4 vagrant
$ vagrant --version
Vagrant 2.3.4

Init the Vagrant project for a Puppet Server:

$ mkdir puppet_server
$ cd puppet_server && vagrant init

Part 1: Master of puppets, I'm pulling your strings (Puppet Server)

Okay, let's try to set up a Puppet Server by using the official EOL Extra Linux Puppet repository RPM:

First try

Create the following Vagrantfile:

# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
  config.vm.provider "docker" do |d|
    d.build_dir = "."
  end
end

And the following Dockerfile:

FROM amazonlinux:2022

RUN dnf install -y https://yum.puppet.com/eol-releases/puppet5-release-el-7.noarch.rpm

# Sleeping beauty
CMD ["sleep", "infinity"]

Aaaand letsgo!

$ vagrant up
Bringing machine 'default' up with 'docker' provider...
==> default: Creating and configuring docker networks...
==> default: Building the container from a Dockerfile...
    default: Sending build context to Docker daemon  9.728kB
    default: Step 1/3 : FROM amazonlinux:2022
    default:  ---> 5fe27e80432b
    default: Step 2/3 : RUN dnf install -y https://yum.puppet.com/eol-releases/puppet5-release-el-7.noarch.rpm
    default:  ---> Running in 7e4ba6d822f9
    default: Amazon Linux 2022 repository                     25 MB/s |  11 MB     00:00    
    default: Last metadata expiration check: 0:00:02 ago on Wed Jan 25 21:15:35 2023.
    default: puppet5-release-el-7.noarch.rpm                  11 kB/s | 8.6 kB     00:00    
    default: (try to add '--skip-broken' to skip uninstallable packages)
    default: Error: 
    default:  Problem: conflicting requests
    default:   - nothing provides /bin/mkdir needed by puppet5-release-5.0.0-14.el7.noarch
    default: 
    default: The command '/bin/sh -c dnf install -y https://yum.puppet.com/eol-releases/puppet5-release-el-7.noarch.rpm' returned a non-zero code: 1
A Docker command executed by Vagrant didn't complete successfully!
The command run along with the output from the command is shown
below.

Command: ["docker", "build", "/home/lenain/work/puppet_server", {:notify=>[:stdout, :stderr]}]

Stderr: The command '/bin/sh -c dnf install -y https://yum.puppet.com/eol-releases/puppet5-release-el-7.noarch.rpm' returned a non-zero code: 1


Stdout: Sending build context to Docker daemon  9.728kB
Step 1/3 : FROM amazonlinux:2022
 ---> 5fe27e80432b
Step 2/3 : RUN dnf install -y https://yum.puppet.com/eol-releases/puppet5-release-el-7.noarch.rpm
 ---> Running in 7e4ba6d822f9
Amazon Linux 2022 repository                     25 MB/s |  11 MB     00:00    
Last metadata expiration check: 0:00:02 ago on Wed Jan 25 21:15:35 2023.
puppet5-release-el-7.noarch.rpm                  11 kB/s | 8.6 kB     00:00    
(try to add '--skip-broken' to skip uninstallable packages)
Error: 
 Problem: conflicting requests
  - nothing provides /bin/mkdir needed by puppet5-release-5.0.0-14.el7.noarch

Mmmmokay.

The Puppet repository package expects /bin/mkdir to be provided, but there is no package that provides this file, only /usr/bin/mkdir:

# dnf provides /bin/mkdir|grep Filename
Filename    : /usr/bin/mkdir
Filename    : /usr/bin/mkdir
Filename    : /usr/bin/mkdir

Building an RPM

Lets create a fake RPM to artificially provide the already existing /bin/mkdir!

Install required tools and create an RPM build tree in a temporary container:

$ docker run -ti -v $PWD:/mnt amazonlinux:2022
$ dnf -y install rpm-build rpmdevtools
$ rpmdev-setuptree

Create an ./rpmbuild/SPECS/fake-mkdir.spec:

Name:           fake-mkdir
Version:        0.1
Release:        1%{?dist}
Summary:        A fake provider to /bin/mkdir for Puppet
License:        AGPLv3+
Source0:        %{name}-%{version}.tar.gz
Provides:       /bin/mkdir

%description
A fake provider to /bin/mkdir for Puppet

%prep

%build

%install

%files

%changelog

Create a fake source file ./rpmbuild/SOURCES/fake-mkdir-0.1.tar.gz:

$ echo "Yes, you have a /bin/mkdir." > ./rpmbuild/SOURCES/README
$ tar cvzf ./rpmbuild/SOURCES/fake-mkdir-0.1.tar.gz -C ./rpmbuild/SOURCES/ README

Build the RPM:

$ rpmbuild -v -ba ~/rpmbuild/SPECS/fake-mkdir.spec

Check that the RPM does what it should do:

$ rpm -q --provides ./rpmbuild/RPMS/x86_64/fake-mkdir-0.1-1.amzn2022.x86_64.rpm 
/bin/mkdir
fake-mkdir = 0.1-1.amzn2022
fake-mkdir(x86-64) = 0.1-1.amzn2022

Put it out of the RPM build container:

$ mv ./rpmbuild/RPMS/x86_64/fake-mkdir-0.1-1.amzn2022.x86_64.rpm /mnt/.

Second try

Update the Dockerfile:

FROM amazonlinux:2022

ARG FAKE_MKDIR_RPM=fake-mkdir-0.1-1.amzn2022.x86_64.rpm
COPY $FAKE_MKDIR_RPM /tmp
RUN dnf install -y /tmp/$FAKE_MKDIR_RPM
RUN dnf install -y https://yum.puppet.com/eol-releases/puppet5-release-el-7.noarch.rpm

# Sleeping beauty
CMD ["sleep", "infinity"]

Lezgo!

Bringing machine 'default' up with 'docker' provider...
==> default: Creating and configuring docker networks...
==> default: Building the container from a Dockerfile...
    default: Sending build context to Docker daemon  17.92kB
    default: Step 1/6 : FROM amazonlinux:2022
    default:  ---> 5fe27e80432b
    default: Step 2/6 : ARG FAKE_MKDIR_RPM=fake-mkdir-0.1-1.amzn2022.x86_64.rpm
    default:  ---> Running in e94eca9b8269
    default: Removing intermediate container e94eca9b8269
    default:  ---> 487d5b5293f9
    default: Step 3/6 : COPY $FAKE_MKDIR_RPM /tmp
    default:  ---> 9875d1d2d441
    default: Step 4/6 : RUN dnf install -y /tmp/$FAKE_MKDIR_RPM
    default:  ---> Running in 0016e5f5c180
    default: Amazon Linux 2022 repository                     17 MB/s |  11 MB     00:00    
    default: Last metadata expiration check: 0:00:02 ago on Wed Jan 25 22:13:09 2023.
    default: Dependencies resolved.
    default: ================================================================================
    default:  Package          Architecture Version                 Repository          Size
    default: ================================================================================
    default: Installing:
    default:  fake-mkdir       x86_64       0.1-1.amzn2022          @commandline       6.0 k
    default: 
    default: Transaction Summary
    default: ================================================================================
    default: Install  1 Package
    default: 
    default: Total size: 6.0 k
    default: Installed size: 0  
    default: Downloading Packages:
    default: Running transaction check
    default: Transaction check succeeded.
    default: Running transaction test
    default: Transaction test succeeded.
    default: Running transaction
    default:   Preparing        :                                                        1/1
    default:  
    default:   Installing       : fake-mkdir-0.1-1.amzn2022.x86_64                       1/1
    default:  
    default:   Verifying        : fake-mkdir-0.1-1.amzn2022.x86_64                       1/1
    default:  
    default: 
    default: Installed:
    default:   fake-mkdir-0.1-1.amzn2022.x86_64                                              
    default: 
    default: Complete!
    default: Removing intermediate container 0016e5f5c180
    default:  ---> 00154047b3b6
    default: Step 5/6 : RUN dnf install -y https://yum.puppet.com/eol-releases/puppet5-release-el-7.noarch.rpm
    default:  ---> Running in 3c943960f20e
    default: Last metadata expiration check: 0:00:03 ago on Wed Jan 25 22:13:09 2023.
    default: puppet5-release-el-7.noarch.rpm                 103 kB/s | 8.6 kB     00:00    
    default: Dependencies resolved.
    default: ================================================================================
    default:  Package               Architecture Version            Repository          Size
    default: ================================================================================
    default: Installing:
    default:  puppet5-release       noarch       5.0.0-14.el7       @commandline       8.6 k
    default: 
    default: Transaction Summary
    default: ================================================================================
    default: Install  1 Package
    default: Total size: 8.6 k
    default: Installed size: 3.9 k
    default: Downloading Packages:
    default: Running transaction check
    default: Transaction check succeeded.
    default: Running transaction test
    default: Transaction test succeeded.
    default: Running transaction
    default:   Preparing        :                                                        1/1
    default:  
    default:   Running scriptlet: puppet5-release-5.0.0-14.el7.noarch                    1/1
    default:  
    default:   Installing       : puppet5-release-5.0.0-14.el7.noarch                    1/1
    default:  
    default:   Running scriptlet: puppet5-release-5.0.0-14.el7.noarch                    1/1
    default:  
    default:   Verifying        : puppet5-release-5.0.0-14.el7.noarch                    1/1
    default:  
    default: 
    default: Installed:
    default:   puppet5-release-5.0.0-14.el7.noarch                                           
    default: 
    default: Complete!
    default: Removing intermediate container 3c943960f20e
    default:  ---> d19dfb140f14
    default: Step 6/6 : CMD ["sleep", "infinity"]
    default:  ---> Running in caebb71aac56
    default: Removing intermediate container caebb71aac56
    default:  ---> 898257790bef
    default: Successfully built 898257790bef
    default: 
    default: Image: 898257790bef
==> default: Creating the container...
    default:   Name: puppet_server_default_1674684794
    default:  Image: 898257790bef
    default: Volume: /home/lenain/work/puppet_server:/vagrant
    default:  
    default: Container created: 917ddaac8b5d36bc
==> default: Enabling network interfaces...
==> default: Starting container...

Bwahahah! IT WORKED! (Doing the magic hand gesture)

Can I haz a Puppet Server now plz?

$ vagrant docker-exec -it -- /bin/sh
$ dnf list|grep puppetserver
puppetserver.noarch                                               5.3.16-1.el7                                puppet5
$ dnf install -y puppetserver
Last metadata expiration check: 0:00:44 ago on Wed Jan 25 22:16:29 2023.
Error: 
 Problem: conflicting requests
...
  - nothing provides java-1.8.0-openjdk-headless needed by puppetserver-5.3.9-1.el7.noarch
(try to add '--skip-broken' to skip uninstallable packages)

yum search java-1.8
Last metadata expiration check: 0:00:06 ago on Wed Jan 25 22:27:07 2023.
========================== Name Matched: java-1.8 =================================================
java-1.8.0-amazon-corretto.x86_64 : Amazon Corretto runtime environment
java-1.8.0-amazon-corretto-devel.x86_64 : Amazon Corretto development environment

Sad. Obviously Amazon only provides the corretto version of the JDK in their repositories, not the OpenJDK one.

Lets create a /etc/yum.repos.d/fedora.repo file with a fixed $releasever:

[fedora]
name=Fedora 37 - $basearch
#baseurl=http://download.example/pub/fedora/linux/releases/37/Everything/$basearch/os/
metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-37&arch=$basearch
enabled=1
countme=1
metadata_expire=7d
repo_gpgcheck=0
type=rpm
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-37-$basearch
skip_if_unavailable=False

Add Fedora GPG keys, update DNF, and try to install it this way.

$ dnf install -y https://kojipkgs.fedoraproject.org//packages/fedora-repos/37/1/noarch/fedora-gpg-keys-37-1.noarch.rpm
$ dnf update
$ dnf install -y puppetserver
...
Complete!
$ /opt/puppetlabs/server/apps/puppetserver/bin/puppetserver ruby --version
jruby 1.7.27 (1.9.3p551) 2017-05-11 8cdb01a on OpenJDK 64-Bit Server VM 1.8.0_345-b01 +jit [linux-amd64]
$ /opt/puppetlabs/server/apps/puppetserver/bin/puppetserver start
$ tail -f /var/log/puppetlabs/puppetserver/puppetserver.log
...
2023-01-25 23:01:55,345 INFO  [clojure-agent-send-pool-0] [puppetserver] Puppet Puppet settings initialized; run mode: master
2023-01-25 23:01:55,577 INFO  [clojure-agent-send-pool-0] [p.s.j.i.jruby-agents] Finished creating JRubyInstance 4 of 4

Uuuaaaaah? ME LIKEY!

Lets fix our Dockerfile:

FROM amazonlinux:2022

# Required for Java 1.8 OpenJDK and not Corretto
COPY fedora.repo /etc/yum.repos.d/fedora.repo
RUN dnf install -y https://kojipkgs.fedoraproject.org//packages/fedora-repos/37/1/noarch/fedora-gpg-keys-37-1.noarch.rpm && \
    dnf update

# Fake RPM to provide Puppet /bin/mkdir dependency
ARG FAKE_MKDIR_RPM=fake-mkdir-0.1-1.amzn2022.x86_64.rpm
COPY $FAKE_MKDIR_RPM /tmp
RUN dnf install -y /tmp/$FAKE_MKDIR_RPM && \
    rm /tmp/$FAKE_MKDIR_RPM

# Puppet Specific
RUN dnf install -y https://yum.puppet.com/eol-releases/puppet5-release-el-7.noarch.rpm && \
    dnf install -y puppetserver

# Puppet Server run as user puppet but the ssl directory is only writeable by root, fix this
RUN mkdir -p /etc/puppetlabs/puppet/ssl && \
    chown puppet:puppet /etc/puppetlabs/puppet/ssl

# Start the Puppet Server and expose the port
CMD ["/opt/puppetlabs/server/apps/puppetserver/bin/puppetserver", "foreground"]
EXPOSE 8140

We also need to expose the container port locally, so we add the forward_port directive to our Vagrantfile:

# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
  config.vm.provider "docker" do |d|
    d.build_dir = "."
  end
  config.vm.network "forwarded_port", guest: 8140, host: 8140
end

We restart our Vagrant Puppet Server Container:

$ vagrant reload

Check the logs:

$ vagrant docker-logs --follow
==> default: 2023-01-25 23:23:47,487 INFO  [clojure-agent-send-pool-0] [puppetserver] Puppet Puppet settings initialized; run mode: master
==> default: 2023-01-25 23:23:47,683 INFO  [clojure-agent-send-pool-0] [p.s.j.i.jruby-agents] Finished creating JRubyInstance 4 of 4

Is it accessible from the local network?

curl -k "https://127.0.0.1:8140/status/v1/simple"
running

NOICE!

Part 2: Just call my name 'cause I'll hear you scream (Puppet Agent)

TODO