####################################################################################################################################
# ContainerTest.pm - Build containers for testing and documentation
####################################################################################################################################
package pgBackRestTest::Common::ContainerTest;

####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess longmess);
use English '-no_match_vars';

use Cwd qw(abs_path);
use Exporter qw(import);
    our @EXPORT = qw();
use File::Basename qw(dirname);
use Getopt::Long qw(GetOptions);

use pgBackRest::Common::Ini;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Version;

use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::VmTest;

####################################################################################################################################
# User/group definitions
####################################################################################################################################
use constant POSTGRES_GROUP                                         => 'postgres';
    push @EXPORT, qw(POSTGRES_GROUP);
use constant POSTGRES_GROUP_ID                                      => getgrnam(POSTGRES_GROUP) . '';
use constant POSTGRES_USER                                          => POSTGRES_GROUP;
use constant POSTGRES_USER_ID                                       => POSTGRES_GROUP_ID;

use constant TEST_GROUP                                             => getgrgid($UID) . '';
    push @EXPORT, qw(TEST_GROUP);
use constant TEST_GROUP_ID                                          => getgrnam(TEST_GROUP) . '';
use constant TEST_USER                                              => getpwuid($UID) . '';
    push @EXPORT, qw(TEST_USER);
use constant TEST_USER_ID                                           => $UID;

use constant BACKREST_USER                                          => 'backrest';
    push @EXPORT, qw(BACKREST_USER);
use constant BACKREST_USER_ID                                       => getpwnam(BACKREST_USER) . '';

####################################################################################################################################
# Package constants
####################################################################################################################################
use constant LIB_COVER_VERSION                                      => '1.23-2';
    push @EXPORT, qw(LIB_COVER_VERSION);
use constant LIB_COVER_PACKAGE                                      => 'libdevel-cover-perl_' . LIB_COVER_VERSION . '_amd64.deb';
    push @EXPORT, qw(LIB_COVER_PACKAGE);
use constant LIB_COVER_EXE                                          => '/usr/bin/cover';
    push @EXPORT, qw(LIB_COVER_EXE);

####################################################################################################################################
# Cert file constants
####################################################################################################################################
use constant CERT_FAKE_PATH                                         => '/etc/fake-cert';
use constant CERT_FAKE_CA                                           => CERT_FAKE_PATH . '/ca.crt';
    push @EXPORT, qw(CERT_FAKE_CA);
use constant CERT_FAKE_SERVER                                       => CERT_FAKE_PATH . '/server.crt';
    push @EXPORT, qw(CERT_FAKE_SERVER);
use constant CERT_FAKE_SERVER_KEY                                   => CERT_FAKE_PATH . '/server.key';
    push @EXPORT, qw(CERT_FAKE_SERVER_KEY);

####################################################################################################################################
# Container Debug - speeds container debugging by splitting each section into a separate intermediate container
####################################################################################################################################
use constant CONTAINER_DEBUG                                        => false;

####################################################################################################################################
# Container repo - defines the Docker repository where the containers will be located
####################################################################################################################################
sub containerRepo
{
    return BACKREST_EXE . qw(/) . 'test';
}

push @EXPORT, qw(containerRepo);

####################################################################################################################################
# Section header
####################################################################################################################################
sub sectionHeader
{
    return (CONTAINER_DEBUG ? "\n\nRUN \\\n" : " && \\\n\n");
}

####################################################################################################################################
# Write the Docker container
####################################################################################################################################
sub containerWrite
{
    my $oStorageDocker = shift;
    my $strTempPath = shift;
    my $strOS = shift;
    my $strTitle = shift;
    my $strImageParent = shift;
    my $strImage = shift;
    my $strCopy = shift;
    my $strScript = shift;
    my $bForce = shift;

    my $strTag = containerRepo() . ":${strImage}";
    &log(INFO, "Building ${strTag} image...");

    $strScript =
        "# ${strTitle} Container\n" .
        "FROM ${strImageParent}\n\n" .
        (defined($strCopy) ? "${strCopy}\n\n" : '') .
        "RUN echo '" . (CONTAINER_DEBUG ? 'DEBUG' : 'OPTIMIZED') . " BUILD'" .
        $strScript;

    # Write the image
    $oStorageDocker->put("${strTempPath}/${strImage}", trim($strScript) . "\n");
    executeTest('docker build' . (defined($bForce) && $bForce ? ' --no-cache' : '') .
                " -f ${strTempPath}/${strImage} -t ${strTag} ${strTempPath}",
                {bSuppressStdErr => true});
}

####################################################################################################################################
# User/group creation
####################################################################################################################################
sub groupCreate
{
    my $strOS = shift;
    my $strName = shift;
    my $iId = shift;

    return "groupadd -g${iId} ${strName}";
}

sub userCreate
{
    my $strOS = shift;
    my $strName = shift;
    my $iId = shift;
    my $strGroup = shift;

    my $oVm = vmGet();

    if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_RHEL)
    {
        return "adduser -g${strGroup} -u${iId} -n ${strName}";
    }
    elsif ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_DEBIAN)
    {
        return "adduser --uid=${iId} --ingroup=${strGroup} --disabled-password --gecos \"\" ${strName}";
    }

    confess &log(ERROR, "unable to create user for os '${strOS}'");
}

####################################################################################################################################
# Setup SSH
####################################################################################################################################
sub sshSetup
{
    my $strOS = shift;
    my $strUser = shift;
    my $strGroup = shift;
    my $bControlMaster = shift;

    my $strScript = sectionHeader() .
        "# Setup SSH\n" .
        "    mkdir /home/${strUser}/.ssh && \\\n" .
        "    cp /root/.ssh/id_rsa  /home/${strUser}/.ssh/id_rsa && \\\n" .
        "    cp /root/.ssh/authorized_keys  /home/${strUser}/.ssh/authorized_keys && \\\n" .
        "    cp /root/.ssh/config /home/${strUser}/.ssh/config && \\\n";

    if ($bControlMaster)
    {
        $strScript .=
            "    echo '    ControlMaster auto' >> /home/${strUser}/.ssh/config && \\\n" .
            "    echo '    ControlPath /tmp/\%r\@\%h:\%p' >> /home/${strUser}/.ssh/config && \\\n" .
            "    echo '    ControlPersist 30' >> /home/${strUser}/.ssh/config && \\\n";
    }

    $strScript .=
        "    chown -R ${strUser}:${strGroup} /home/${strUser}/.ssh && \\\n" .
        "    chmod 700 /home/${strUser}/.ssh && \\\n" .
        "    chmod 600 /home/${strUser}/.ssh/*";

    return $strScript;
}

####################################################################################################################################
# Cert Setup
####################################################################################################################################
sub certSetup
{
    return
        sectionHeader() .
        "# Generate fake certs\n" .
        "    mkdir -p -m 755 /etc/fake-cert && \\\n" .
        "    cd /etc/fake-cert && \\\n" .
        "    openssl genrsa -out ca.key 2048 && \\\n" .
        "    openssl req -new -x509 -extensions v3_ca -key ca.key -out ca.crt -days 99999 \\\n" .
        "        -subj \"/C=US/ST=Country/L=City/O=Organization/CN=pgbackrest.org\" && \\\n" .
        "    openssl genrsa -out server.key 2048 && \\\n" .
        "    openssl req -new -key server.key -out server.csr \\\n" .
        "        -subj \"/C=US/ST=Country/L=City/O=Organization/CN=*.pgbackrest.org\" && \\\n" .
        "    openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 99999 \\\n" .
        "        -sha256";
}

####################################################################################################################################
# S3 server setup
####################################################################################################################################
sub s3ServerSetup
{
    my $strOS = shift;

    # Install node.js
    my $strScript = sectionHeader() .
        "# Install node.js\n";

    if ($strOS eq VM_CO7)
    {
        $strScript .=
            "    wget -O /root/nodejs.sh https://rpm.nodesource.com/setup_6.x && \\\n" .
            "    bash /root/nodejs.sh && \\\n" .
            "    yum install -y nodejs";
    }
    else
    {
        $strScript .=
            "    wget -O /root/nodejs.sh https://deb.nodesource.com/setup_6.x && \\\n" .
            "    bash /root/nodejs.sh && \\\n" .
            "    apt-get install -y nodejs";
    }

    # Install Scality S3
    $strScript .= sectionHeader() .
        "# Install Scality S3\n";

    $strScript .=
        "    wget -O /root/scalitys3.tar.gz https://github.com/scality/S3/archive/GA6.4.2.1.tar.gz && \\\n" .
        "    mkdir /root/scalitys3 && \\\n" .
        "    tar -C /root/scalitys3 --strip-components 1 -xvf /root/scalitys3.tar.gz && \\\n" .
        "    cd /root/scalitys3 && \\\n" .
        "    npm install && \\\n" .
        '    sed -i "0,/,/s//,\n    \"certFilePaths\":{\"key\":\"\/etc\/fake\-cert\/server.key\",\"cert\":' .
            '\"\/etc\/fake\-cert\/server.crt\",\"ca\":\"\/etc\/fake\-cert\/ca.crt\"},/"' . " \\\n" .
        '        ./config.json' . " && \\\n" .
        '    sed -i "s/ort\"\: 8000/ort\"\: 443/" ./config.json';

    return $strScript;
}

####################################################################################################################################
# Build containers
####################################################################################################################################
sub containerBuild
{
    my $oStorageDocker = shift;
    my $strVm = shift;
    my $bVmForce = shift;

    # Create temp path
    my $strTempPath = $oStorageDocker->pathGet('test/.vagrant/docker');
    $oStorageDocker->pathCreate($strTempPath, {strMode => '0770', bIgnoreExists => true, bCreateParent => true});

    # Remove old images on force
    if ($bVmForce)
    {
        my $strRegExp = '^' . containerRepo();

        if ($strVm ne 'all')
        {
            $strRegExp .= "\:${strVm}-";
        }

        executeTest("rm -f ${strTempPath}/" . ($strVm eq 'all' ? '*' : "${strVm}-*"));
        imageRemove("${strRegExp}.*");
    }

    # Create SSH key (if it does not already exist)
    if (-e "${strTempPath}/id_rsa")
    {
        &log(INFO, "SSH key already exists");
    }
    else
    {
        &log(INFO, "Building SSH keys...");

        executeTest("ssh-keygen -f ${strTempPath}/id_rsa -t rsa -b 1024 -N ''", {bSuppressStdErr => true});
    }

    # VM Images
    ################################################################################################################################
    my $oVm = vmGet();

    foreach my $strOS ($strVm eq 'all' ? VM_LIST : ($strVm))
    {
        my $oOS = $oVm->{$strOS};
        my $bDocBuild = false;

        # Deprecated OSs can only be used to build packages
        my $bDeprecated = $oVm->{$strOS}{&VM_DEPRECATED} ? true : false;

        # Base image
        ###########################################################################################################################
        my $strImageParent = "$$oVm{$strOS}{&VM_IMAGE}";
        my $strImage = "${strOS}-base";

        #---------------------------------------------------------------------------------------------------------------------------
        my $strCopy =
            "# Copy root SSH keys\n" .
            "COPY id_rsa /root/.ssh/id_rsa\n" .
            "COPY id_rsa.pub /root/.ssh/authorized_keys";

        #---------------------------------------------------------------------------------------------------------------------------
        my $strScript = sectionHeader() .
            "# Install base packages\n";

        if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_RHEL)
        {
            $strScript .=
                "    yum -y install epel-release && \\\n" .
                "    yum -y update && \\\n" .
                "    yum -y install openssh-server openssh-clients wget sudo python-pip build-essential git \\\n" .
                "        perl perl-Digest-SHA perl-DBD-Pg perl-XML-LibXML perl-IO-Socket-SSL \\\n" .
                "        gcc make perl-ExtUtils-MakeMaker perl-Test-Simple";

            if ($strOS eq VM_CO6)
            {
                $strScript .= ' perl-Time-HiRes perl-parent perl-JSON';
            }
            else
            {
                $strScript .= ' perl-JSON-PP';
            }
        }
        else
        {
            $strScript .=
                "    apt-get update && \\\n" .
                "    apt-get -y install openssh-server wget sudo python-pip build-essential git \\\n" .
                "        libdbd-pg-perl libhtml-parser-perl libio-socket-ssl-perl libxml-libxml-perl";

            if ($strOS eq VM_U14)
            {
                $strScript .= ' libnet-daemon-perl libplrpc-perl';
            }
        }

        #---------------------------------------------------------------------------------------------------------------------------
        $strScript .= sectionHeader() .
            "# Regenerate SSH keys\n" .
            "    rm -f /etc/ssh/ssh_host_rsa_key* && \\\n" .
            "    ssh-keygen -t rsa -b 1024 -f /etc/ssh/ssh_host_rsa_key";

        $strScript .= sectionHeader() .
            "# Fix SSH permissions\n" .
            "    chmod 700 /root/.ssh && \\\n" .
            "    chmod 600 /root/.ssh/*";

        $strScript .= sectionHeader() .
            "# Disable strict host key checking (so tests will run without a prior logon)\n" .
            "    echo 'Host *' > /root/.ssh/config && \\\n" .
            "    echo '    StrictHostKeyChecking no' >> /root/.ssh/config";

        $strScript .= sectionHeader() .
            "# Create banner to make sure pgBackRest ignores it\n" .
            "    echo '***********************************************' >  /etc/issue.net && \\\n" .
            "    echo 'Sample banner to make sure banners are skipped.' >> /etc/issue.net && \\\n" .
            "    echo ''                                                >> /etc/issue.net && \\\n" .
            "    echo 'More banner after a blank line.'                 >> /etc/issue.net && \\\n" .
            "    echo '***********************************************' >> /etc/issue.net && \\\n" .
            "    echo 'Banner /etc/issue.net'                           >> /etc/ssh/sshd_config";

        if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_DEBIAN)
        {
            $strScript .= sectionHeader() .
                "# Fix root tty\n" .
                "    sed -i 's/^mesg n/tty -s \\&\\& mesg n/g' /root/.profile";

            $strScript .= sectionHeader() .
                "# Suppress dpkg interactive output\n" .
                "    rm /etc/apt/apt.conf.d/70debconf";

            if ($strOS eq VM_U12)
            {
                $strScript .= sectionHeader() .
                    "# Create run directory required by SSH (not created automatically on Ubuntu 12.04)\n" .
                    "    mkdir -p /var/run/sshd";
            }
        }

        #---------------------------------------------------------------------------------------------------------------------------
        $strScript .= certSetup();

        #---------------------------------------------------------------------------------------------------------------------------
        $strScript .= sectionHeader() .
            "# Create test user\n" .
            '    ' . groupCreate($strOS, TEST_GROUP, TEST_GROUP_ID) . " && \\\n" .
            '    ' . userCreate($strOS, TEST_USER, TEST_USER_ID, TEST_GROUP) . " && \\\n" .
            '    mkdir -m 750 /home/' . TEST_USER . "/test && \\\n" .
            '    chown ' . TEST_USER . ':' . TEST_GROUP . ' /home/' . TEST_USER . "/test";

        $strScript .= sectionHeader() .
            "# Configure sudo\n";

        if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_RHEL)
        {
            $strScript .=
                "    echo '%" . TEST_GROUP . "        ALL=(ALL)       NOPASSWD: ALL' > /etc/sudoers.d/" . TEST_GROUP . " && \\\n" .
                "    sed -i 's/^Defaults    requiretty\$/\\# Defaults    requiretty/' /etc/sudoers";
        }
        else
        {
            $strScript .=
                "    echo '%" . TEST_GROUP . " ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers";
        }

        #---------------------------------------------------------------------------------------------------------------------------
        if (!$bDeprecated)
        {
            $strScript .=  sectionHeader() .
                "# Create PostgreSQL user/group with a known ids for testing\n" .
                '    ' . groupCreate($strOS, POSTGRES_GROUP, POSTGRES_GROUP_ID) . " && \\\n" .
                '    ' . userCreate($strOS, POSTGRES_USER, POSTGRES_USER_ID, POSTGRES_GROUP);

            $strScript .=  sectionHeader() .
                "# Install Postgresql packages\n";

            if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_RHEL)
            {
                if ($strOS eq VM_CO6)
                {
                    $strScript .=
                        "    rpm -ivh http://yum.postgresql.org/9.0/redhat/rhel-6-x86_64/pgdg-centos90-9.0-5.noarch.rpm \\\n" .
                        "        http://yum.postgresql.org/9.1/redhat/rhel-6-x86_64/pgdg-centos91-9.1-6.noarch.rpm \\\n" .
                        "        http://yum.postgresql.org/9.5/redhat/rhel-6-x86_64/pgdg-centos95-9.5-3.noarch.rpm \\\n" .
                        "        http://yum.postgresql.org/9.6/redhat/rhel-6-x86_64/pgdg-centos96-9.6-3.noarch.rpm";
                }
                elsif ($strOS eq VM_CO7)
                {
                    $strScript .=
                        "    rpm -ivh http://yum.postgresql.org/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm";
                }
            }
            else
            {
                $strScript .=
                    "    echo 'deb http://apt.postgresql.org/pub/repos/apt/ " .
                    $$oVm{$strOS}{&VM_OS_REPO} . "-pgdg main' >> /etc/apt/sources.list.d/pgdg.list && \\\n" .
                    "    wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \\\n" .
                    "    apt-get update";
            }

            if (defined($oOS->{&VM_DB}) && @{$oOS->{&VM_DB}} > 0)
            {
                $strScript .=  sectionHeader() .
                    "# Install Postgresql\n";

                if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_RHEL)
                {
                    $strScript .= "    yum -y install";
                }
                else
                {
                    $strScript .= "    apt-get install -y";
                }

                # Construct list of databases to install
                my $strRunAfterInstall;

                foreach my $strDbVersion (@{$oOS->{&VM_DB}})
                {
                    if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_RHEL)
                    {
                        my $strDbVersionNoDot = $strDbVersion;
                        $strDbVersionNoDot =~ s/\.//;

                        $strScript .=  " postgresql${strDbVersionNoDot}-server";
                    }
                    else
                    {
                        $strScript .= " postgresql-${strDbVersion}";

                        $strRunAfterInstall .= (defined($strRunAfterInstall) ? " && \\\n" : '') .
                            "    pg_dropcluster --stop ${strDbVersion} main";
                    }
                }

                # If there are commands to run after install
                if (defined($strRunAfterInstall))
                {
                    $strScript .= " && \\\n" . $strRunAfterInstall;
                }
            }
        }

        #---------------------------------------------------------------------------------------------------------------------------
        if (!$bDeprecated)
        {
            $strScript .= sectionHeader() .
                "# Install AWS CLI\n" .
                "    pip install --upgrade awscli && \\\n" .
                '    sudo -i -u ' . TEST_USER . " aws configure set region us-east-1 && \\\n" .
                '    sudo -i -u ' . TEST_USER . " aws configure set aws_access_key_id accessKey1 && \\\n" .
                '    sudo -i -u ' . TEST_USER . " aws configure set aws_secret_access_key verySecretKey1";
        }

        #---------------------------------------------------------------------------------------------------------------------------
        if (!$bDeprecated && $strOS ne VM_CO6 && $strOS ne VM_U12)
        {
            $strScript .= s3ServerSetup($strOS);
        }

        #---------------------------------------------------------------------------------------------------------------------------
        $strScript .=
            "\n\n# Start SSH when container starts\n" .
            'ENTRYPOINT ';

        if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_RHEL || $strOS eq VM_U12)
        {
            $strScript .= '/usr/sbin/sshd -D';
        }
        else
        {
            $strScript .= 'service ssh restart && bash';
        }

        containerWrite($oStorageDocker, $strTempPath, $strOS, 'Base', $strImageParent, $strImage, $strCopy, $strScript, $bVmForce);

        # Build image
        ###########################################################################################################################
        $strImageParent = containerRepo() . ":${strOS}-base";
        $strImage = "${strOS}-build";
        $strCopy = undef;

        # Install Perl packages
        if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_DEBIAN)
        {
            $strScript = sectionHeader() .
                "# Install package build tools\n" .
                "    apt-get install -y devscripts lintian libxml-checker-perl txt2man debhelper \\\n" .
                "        libpod-coverage-perl libppi-html-perl libtemplate-perl libtest-differences-perl";

            $strScript .=  sectionHeader() .
                "# Install pgBackRest package source\n" .
                "    git clone https://anonscm.debian.org/git/pkg-postgresql/pgbackrest.git /root/package-src";

            # Build only when a new version has been specified
            if (!$bDeprecated && !$oStorageDocker->exists("test/package/${strOS}-" . LIB_COVER_PACKAGE))
            {
                $strScript .=  sectionHeader() .
                    "# Install Devel::Cover package source & build\n" .
                    "    git clone https://anonscm.debian.org/git/pkg-perl/packages/libdevel-cover-perl.git" .
                        " /root/libdevel-cover-perl && \\\n" .
                    "    cd /root/libdevel-cover-perl && \\\n" .
                    "    git checkout debian/" . LIB_COVER_VERSION . " && \\\n" .
                    "    debuild -i -us -uc -b";
            }
        }
        else
        {
            $strScript = '';
        }

        containerWrite($oStorageDocker, $strTempPath, $strOS, 'Build', $strImageParent, $strImage, $strCopy,$strScript, $bVmForce);

        # Copy Devel::Cover to host so it can be installed in other containers (if it doesn't already exist)
        if (!$bDeprecated && !$oStorageDocker->exists("test/package/${strOS}-" . LIB_COVER_PACKAGE) &&
            $$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_DEBIAN)
        {
            executeTest('docker rm -f test-build', {bSuppressError => true});
            executeTest(
                "docker run -itd -h test-build --name=test-build" .
                " -v ${strTempPath}:${strTempPath} " . containerRepo() . ":${strOS}-build",
                {bSuppressStdErr => true});
            executeTest(
                "docker exec -i test-build " .
                "bash -c 'cp /root/" . LIB_COVER_PACKAGE . " ${strTempPath}/${strOS}-" . LIB_COVER_PACKAGE . "'");
            executeTest('docker rm -f test-build');

            $oStorageDocker->move(
                "test/.vagrant/docker/${strOS}-" . LIB_COVER_PACKAGE, "test/package/${strOS}-" . LIB_COVER_PACKAGE);
        }

        # S3 image
        ###########################################################################################################################
        if (!$bDeprecated)
        {
            $strImage = "${strOS}-s3-server";
            $strCopy = undef;

            if ($strOS ne VM_CO6 && $strOS ne VM_U12)
            {
                $strImageParent = containerRepo() . ":${strOS}-base";
                $strScript = '';
            }
            else
            {
                $strImageParent = $oVm->{&VM_U16}{&VM_IMAGE};

                $strScript = sectionHeader() .
                    "# Install required packages\n" .
                    "    apt-get update && \\\n" .
                    "    apt-get install -y wget git";

                $strScript .= certSetup();
                $strScript .= s3ServerSetup(VM_U16);

                $strScript .= sectionHeader() .
                    "# Fix root tty\n" .
                    "    echo 'tty -s && mesg n || true' > /root/.profile";
            }

            $strScript .= "\n\nENTRYPOINT npm start --prefix /root/scalitys3";

            containerWrite(
                $oStorageDocker, $strTempPath, $strOS, 'S3 Server', $strImageParent, $strImage, $strCopy, $strScript, $bVmForce);
        }

        # Test image
        ########################################################################################################################
        if (!$bDeprecated)
        {
            $strImageParent = containerRepo() . ":${strOS}-base";
            $strImage = "${strOS}-test";

            if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_DEBIAN)
            {
                $oStorageDocker->copy(
                    "test/package/${strOS}-" . LIB_COVER_PACKAGE, "test/.vagrant/docker/${strOS}-" . LIB_COVER_PACKAGE);

                $strCopy =
                    "# Copy Devel::Cover\n" .
                    "COPY ${strOS}-" . LIB_COVER_PACKAGE . ' /tmp/' . LIB_COVER_PACKAGE;

                $strScript = sectionHeader() .
                    "# Install Devel::Cover\n" .
                    '    dpkg -i /tmp/' . LIB_COVER_PACKAGE;
            }
            else
            {
                $strCopy = undef;
                $strScript = '';
            }

            #---------------------------------------------------------------------------------------------------------------------------
            $strScript .= sectionHeader() .
                "# Create pgbackrest user\n" .
                '    ' . userCreate($strOS, BACKREST_USER, BACKREST_USER_ID, TEST_GROUP);

            $strScript .=
                sshSetup($strOS, BACKREST_USER, TEST_GROUP, $$oVm{$strOS}{&VM_CONTROL_MASTER});

            $strScript .=
                sshSetup($strOS, TEST_USER, TEST_GROUP, $$oVm{$strOS}{&VM_CONTROL_MASTER});

            $strScript .=  sectionHeader() .
                "# Make " . TEST_USER . " home dir readable\n" .
                '    chmod g+r,g+x /home/' . TEST_USER;

            containerWrite(
                $oStorageDocker, $strTempPath, $strOS, 'Test', $strImageParent, $strImage, $strCopy, $strScript, $bVmForce);
        }
    }

    &log(INFO, "Build Complete");
}

push @EXPORT, qw(containerBuild);

####################################################################################################################################
# containerRemove
#
# Remove containers that match a regexp.
####################################################################################################################################
sub containerRemove
{
    my $strRegExp = shift;

    foreach my $strContainer (sort(split("\n", trim(executeTest('docker ps -a --format "{{.Names}}"')))))
    {
        if ($strContainer =~ $strRegExp)
        {
            executeTest("docker rm -f ${strContainer}", {bSuppressError => true});
        }
    }
}

push @EXPORT, qw(containerRemove);

####################################################################################################################################
# imageRemove
#
# Remove images that match a regexp.
####################################################################################################################################
sub imageRemove
{
    my $strRegExp = shift;

    foreach my $strImage (sort(split("\n", trim(executeTest('docker images --format "{{.Repository}}:{{.Tag}}"')))))
    {
        if ($strImage =~ $strRegExp)
        {
            &log(INFO, "Removing ${strImage} image...");
            executeTest("docker rmi -f ${strImage}");
        }
    }
}

1;
