#################################################################################################################################### # ContainerTest.pm - Build docker containers for testing and documentation #################################################################################################################################### package pgBackRestTest::Docker::ContainerTest; #################################################################################################################################### # Perl includes #################################################################################################################################### use strict; use warnings FATAL => qw(all); use Carp qw(confess longmess); use Cwd qw(abs_path); use Exporter qw(import); our @EXPORT = qw(); use File::Basename qw(dirname); use Getopt::Long qw(GetOptions); use Scalar::Util qw(blessed); use pgBackRest::Common::Ini; use pgBackRest::Common::Log; use pgBackRest::FileCommon; use pgBackRestTest::Common::ExecuteTest; use pgBackRestTest::Common::VmTest; use constant TEST_GROUP => 'admin'; use constant TEST_GROUP_ID => 1000; use constant TEST_USER => 'vagrant'; use constant TEST_USER_ID => TEST_GROUP_ID; use constant POSTGRES_GROUP => 'postgres'; use constant POSTGRES_GROUP_ID => 5000; use constant POSTGRES_USER => POSTGRES_GROUP; use constant POSTGRES_USER_ID => POSTGRES_GROUP_ID; use constant BACKREST_GROUP => POSTGRES_GROUP; use constant BACKREST_GROUP_ID => POSTGRES_GROUP_ID; use constant BACKREST_USER => 'backrest'; use constant BACKREST_USER_ID => 5001; #################################################################################################################################### # User/group creation #################################################################################################################################### sub groupCreate { my $strOS = shift; my $strName = shift; my $iId = shift; return "RUN groupadd -g${iId} ${strName}"; } sub userCreate { my $strOS = shift; my $strName = shift; my $iId = shift; my $strGroup = shift; if ($strOS eq OS_CO6 || $strOS eq OS_CO7) { return "RUN adduser -g${strGroup} -u${iId} -n ${strName}"; } elsif ($strOS eq OS_U12 || $strOS eq OS_U14) { return "RUN adduser --uid=${iId} --ingroup=${strGroup} --disabled-password --gecos \"\" ${strName}"; } confess &log(ERROR, "unable to create user for os '${strOS}'"); } sub postgresGroupCreate { my $strOS = shift; return "# Create PostgreSQL group\n" . groupCreate($strOS, POSTGRES_GROUP, POSTGRES_GROUP_ID); } sub postgresUserCreate { my $strOS = shift; return "# Create PostgreSQL user\n" . userCreate($strOS, POSTGRES_USER, POSTGRES_USER_ID, POSTGRES_GROUP); } sub backrestUserCreate { my $strOS = shift; return "# Create BackRest group\n" . userCreate($strOS, BACKREST_USER, BACKREST_USER_ID, BACKREST_GROUP); } #################################################################################################################################### # Create configuration file #################################################################################################################################### sub backrestConfigCreate { my $strOS = shift; my $strUser = shift; my $strGroup = shift; return "# Create pgbackrest.conf\n" . "RUN touch /etc/pgbackrest.conf\n" . "RUN chmod 640 /etc/pgbackrest.conf\n" . "RUN chown ${strUser}:${strGroup} /etc/pgbackrest.conf"; } #################################################################################################################################### # Write the Docker container #################################################################################################################################### sub containerWrite { my $strTempPath = shift; my $strImageName = shift; my $strImage = shift; my $bForce = shift; # Write the image fileStringWrite("${strTempPath}/${strImageName}", "$strImage\n", false); executeTest('docker build' . (defined($bForce) && $bForce ? ' --no-cache' : '') . " -f ${strTempPath}/${strImageName} -t backrest/${strImageName} ${strTempPath}", {bSuppressStdErr => true}); } #################################################################################################################################### # Setup SSH #################################################################################################################################### sub sshSetup { my $strOS = shift; my $strUser = shift; my $strGroup = shift; return "# Setup SSH\n" . "RUN mkdir /home/${strUser}/.ssh\n" . "COPY id_rsa /home/${strUser}/.ssh/id_rsa\n" . "COPY id_rsa.pub /home/${strUser}/.ssh/authorized_keys\n" . "RUN chown -R ${strUser}:${strGroup} /home/${strUser}/.ssh\n" . "RUN chmod 700 /home/${strUser}/.ssh\n" . "RUN echo 'Host *' > /home/${strUser}/.ssh/config\n" . "RUN echo ' StrictHostKeyChecking no' >> /home/${strUser}/.ssh/config\n" . "RUN echo ' LogLevel quiet' >> /home/${strUser}/.ssh/config\n" . "RUN echo ' ControlMaster auto' >> /home/${strUser}/.ssh/config\n" . "RUN echo ' ControlPath /tmp/\%r\@\%h:\%p' >> /home/${strUser}/.ssh/config\n" . "RUN echo ' ControlPersist 30' >> /home/${strUser}/.ssh/config"; } #################################################################################################################################### # Repo Setup #################################################################################################################################### sub repoSetup { my $strOS = shift; my $strUser = shift; my $strGroup = shift; return "# Setup repository\n" . "RUN mkdir /var/lib/pgbackrest\n" . "RUN chown -R ${strUser}:${strGroup} /var/lib/pgbackrest\n" . "RUN chmod 750 /var/lib/pgbackrest"; } #################################################################################################################################### # Install Perl packages #################################################################################################################################### sub perlInstall { my $strOS = shift; my $strImage = "# Install Perl packages\n"; if ($strOS eq OS_CO6) { $strImage .= "RUN yum -y install perl perl-Time-HiRes perl-parent perl-JSON perl-Digest-SHA perl-DBD-Pg"; } elsif ($strOS eq OS_CO7) { $strImage .= "RUN yum -y install perl perl-Thread-Queue perl-JSON-PP perl-Digest-SHA perl-DBD-Pg"; } elsif ($strOS eq OS_U12 || $strOS eq OS_U14) { $strImage .= "RUN apt-get -y install libdbd-pg-perl libdbi-perl libnet-daemon-perl libplrpc-perl"; } return $strImage; } #################################################################################################################################### # Build containers #################################################################################################################################### sub containerBuild { my $strVm = shift; my $bVmForce = shift; # Create temp path my $strTempPath = dirname(abs_path($0)) . '/.vagrant/docker'; if (!-e $strTempPath) { mkdir $strTempPath or confess &log(ERROR, "unable to create ${strTempPath}"); } # 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 768 -N ''", {bSuppressStdErr => true}); } my $oyVm = vmGet(); foreach my $strOS (sort(keys(%$oyVm))) { if ($strVm ne 'all' && $strVm ne $strOS) { next; } my $oOS = $$oyVm{$strOS}; my $strImage; my $strImageName; # Base image ########################################################################################################################### $strImageName = "${strOS}-base"; &log(INFO, "Building ${strImageName} image..."); $strImage = "# Base Container\nFROM "; if ($strOS eq OS_CO6) { $strImage .= 'centos:6'; } elsif ($strOS eq OS_CO7) { $strImage .= 'centos:7'; } elsif ($strOS eq OS_U12) { $strImage .= 'ubuntu:12.04'; } elsif ($strOS eq OS_U14) { $strImage .= 'ubuntu:14.04'; } # Install SSH $strImage .= "\n\n# Install SSH\n"; if ($strOS eq OS_CO6 || $strOS eq OS_CO7) { $strImage .= "RUN yum -y install openssh-server openssh-clients\n"; } elsif ($strOS eq OS_U12 || $strOS eq OS_U14) { $strImage .= "RUN apt-get update\n" . "RUN apt-get -y install openssh-server\n"; } $strImage .= "RUN rm -f /etc/ssh/ssh_host_rsa_key*\n" . "RUN ssh-keygen -t rsa -b 768 -f /etc/ssh/ssh_host_rsa_key"; # Create PostgreSQL Group $strImage .= "\n\n" . postgresGroupCreate($strOS); # Add PostgreSQL packages $strImage .= "\n\n# Add PostgreSQL packages\n"; if ($strOS eq OS_CO6) { $strImage .= "RUN rpm -ivh http://yum.postgresql.org/9.0/redhat/rhel-6-x86_64/pgdg-centos90-9.0-5.noarch.rpm\n" . "RUN rpm -ivh http://yum.postgresql.org/9.1/redhat/rhel-6-x86_64/pgdg-centos91-9.1-4.noarch.rpm\n" . "RUN rpm -ivh http://yum.postgresql.org/9.2/redhat/rhel-6-x86_64/pgdg-centos92-9.2-6.noarch.rpm\n" . "RUN rpm -ivh http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-centos93-9.3-1.noarch.rpm\n" . "RUN rpm -ivh http://yum.postgresql.org/9.4/redhat/rhel-6-x86_64/pgdg-centos94-9.4-1.noarch.rpm\n" . "RUN rpm -ivh http://yum.postgresql.org/9.5/redhat/rhel-6-x86_64/pgdg-centos95-9.5-2.noarch.rpm"; } elsif ($strOS eq OS_CO7) { $strImage .= "RUN rpm -ivh http://yum.postgresql.org/9.3/redhat/rhel-7-x86_64/pgdg-centos93-9.3-1.noarch.rpm\n" . "RUN rpm -ivh http://yum.postgresql.org/9.4/redhat/rhel-7-x86_64/pgdg-centos94-9.4-1.noarch.rpm\n" . "RUN rpm -ivh http://yum.postgresql.org/9.5/redhat/rhel-7-x86_64/pgdg-centos95-9.5-1.noarch.rpm"; } elsif ($strOS eq OS_U12 || $strOS eq OS_U14) { if ($strOS eq OS_U12) { $strImage .= "RUN apt-get install -y sudo\n"; } $strImage .= "RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ " . ($strOS eq OS_U12 ? 'precise' : 'trusty') . "-pgdg main' >> /etc/apt/sources.list.d/pgdg.list\n" . "RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -\n" . "RUN apt-get update"; } # Create test group $strImage .= "\n\n# Create test group\n" . groupCreate($strOS, TEST_GROUP, TEST_GROUP_ID) . "\n"; if ($strOS eq OS_CO6 || $strOS eq OS_CO7) { $strImage .= "RUN yum -y install sudo\n" . "RUN echo '%" . TEST_GROUP . " ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/" . TEST_GROUP . "\n" . "RUN sed -i 's/^Defaults requiretty\$/\\# Defaults requiretty/' /etc/sudoers"; } elsif ($strOS eq OS_U12 || $strOS eq OS_U14) { $strImage .= "RUN sed -i 's/^\\\%admin.*\$/\\\%" . TEST_GROUP . " ALL\\=\\(ALL\\) NOPASSWD\\: ALL/' /etc/sudoers"; } # Create test user $strImage .= "\n\n# Create test user\n" . userCreate($strOS, TEST_USER, TEST_USER_ID, TEST_GROUP); # Suppress dpkg interactive output if ($strOS eq OS_U12 || $strOS eq OS_U14) { $strImage .= "\n\n# Suppress dpkg interactive output\n" . "RUN rm /etc/apt/apt.conf.d/70debconf"; } # Start SSH when container starts $strImage .= "\n\n# Start SSH when container starts\n"; # This is required in newer versions of u12 containers - seems like a bug in the caontainer if ($strOS eq OS_U12) { $strImage .= "RUN mkdir /var/run/sshd\n"; } if ($strOS eq OS_U14) { $strImage .= "ENTRYPOINT service ssh restart && bash"; } else { $strImage .= "ENTRYPOINT /usr/sbin/sshd -D"; } # Write the image containerWrite($strTempPath, $strImageName, $strImage, $bVmForce); # Db image ########################################################################################################################### foreach my $strDbVersion (@{$$oOS{db}}) { my $strDbVersionNoDot = $strDbVersion; $strDbVersionNoDot =~ s/\.//; $strImageName = "${strOS}-db-${strDbVersionNoDot}"; &log(INFO, "Building ${strImageName} image..."); $strImage = "# Database Container\nFROM backrest/${strOS}-base"; # Create PostgreSQL User $strImage .= "\n\n" . postgresUserCreate($strOS); # Install SSH key $strImage .= "\n\n" . sshSetup($strOS, POSTGRES_USER, POSTGRES_GROUP); # Install PostgreSQL $strImage .= "\n\n# Install PostgreSQL"; if ($strOS eq OS_U12 || $strOS eq OS_U14) { $strImage .= "\nRUN apt-get install -y postgresql-${strDbVersion}" . "\nRUN pg_dropcluster --stop ${strDbVersion} main"; } elsif ($strOS eq OS_CO6) { $strImage .= "\nRUN yum -y install postgresql${strDbVersionNoDot}-server"; } elsif ($strOS eq OS_CO7) { $strImage .= "\nRUN yum -y install postgresql${strDbVersionNoDot}-server"; } # Write the image containerWrite($strTempPath, $strImageName, $strImage, $bVmForce); } # Db Doc image ########################################################################################################################### $strImageName = "${strOS}-db-doc"; &log(INFO, "Building ${strImageName} image..."); $strImage = "# Database Doc Container\nFROM backrest/${strOS}-db-94"; # Create configuration file $strImage .= "\n\n" . backrestConfigCreate($strOS, POSTGRES_USER, POSTGRES_GROUP); # Write the image containerWrite($strTempPath, $strImageName, $strImage, $bVmForce); # Backup image ########################################################################################################################### $strImageName = "${strOS}-backup"; &log(INFO, "Building ${strImageName} image..."); $strImage = "# Backup Container\nFROM backrest/${strOS}-base"; # Create BackRest User $strImage .= "\n\n" . backrestUserCreate($strOS); # Install SSH key $strImage .= "\n\n" . sshSetup($strOS, BACKREST_USER, BACKREST_GROUP); # Write the image containerWrite($strTempPath, $strImageName, $strImage, $bVmForce); # Backup Doc image ########################################################################################################################### $strImageName = "${strOS}-backup-doc"; &log(INFO, "Building ${strImageName} image..."); $strImage = "# Backup Doc Container\nFROM backrest/${strOS}-backup"; # Create configuration file $strImage .= "\n\n" . backrestConfigCreate($strOS, BACKREST_USER, BACKREST_GROUP); # Setup repository $strImage .= "\n\n" . repoSetup($strOS, BACKREST_USER, BACKREST_GROUP); # Install Perl packages $strImage .= "\n\n" . perlInstall($strOS) . "\n"; # Write the image containerWrite($strTempPath, $strImageName, $strImage, $bVmForce); # Test image ########################################################################################################################### foreach my $strDbVersion (@{$$oOS{db}}) { my $strDbVersionNoDot = $strDbVersion; $strDbVersionNoDot =~ s/\.//; $strImageName = "${strOS}-test-${strDbVersionNoDot}"; &log(INFO, "Building ${strImageName} image..."); $strImage = "# Test Container\nFROM backrest/${strOS}-db-${strDbVersionNoDot}"; # Create BackRest User $strImage .= "\n\n" . backrestUserCreate($strOS); # Install SSH key $strImage .= "\n\n" . sshSetup($strOS, BACKREST_USER, BACKREST_GROUP); # Install SSH key for vagrant user $strImage .= "\n\n" . sshSetup($strOS, TEST_USER, TEST_GROUP); # Put vagrant user in postgres group so tests work properly (this will be removed in the future) $strImage .= "\n\n# Add postgres group to vagrant user\n" . "RUN usermod -g " . BACKREST_GROUP . " -G " . TEST_GROUP . " " . TEST_USER; # Install Perl packages $strImage .= "\n\n" . perlInstall($strOS) . "\n"; # Make PostgreSQL home group readable $strImage .= "\n\n# Make vagrant home dir readable\n" . "RUN chown -R vagrant:postgres /home/vagrant\n" . "RUN chmod g+r,g+x /home/vagrant"; # Write the image containerWrite($strTempPath, $strImageName, $strImage, $bVmForce); } } } push @EXPORT, qw(containerBuild); 1;