diff --git a/.gitignore b/.gitignore index 8750fb4f7..70d847cfc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.phar phpunit.xml .phpunit.result.cache +docker-compose.override.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b79b3df2..283f81f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Added labels and selectors support. - Added support for placeholders in `run()` func. - Added support for secret passing in `run()` func without outputting to logs. +- Added docker-based E2E testing environment. [#2197] ### Changed - Refactored executor engine, up to 2x faster than before. @@ -596,6 +597,7 @@ - Fixed remove of shared dir on first deploy. +[#2197]: https://github.com/deployphp/deployer/issues/2197 [#1994]: https://github.com/deployphp/deployer/issues/1994 [#1990]: https://github.com/deployphp/deployer/issues/1990 [#1989]: https://github.com/deployphp/deployer/issues/1989 diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..0fd355a5a --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,82 @@ +FROM php:7.3-cli-alpine AS composer +RUN apk add wget +COPY ./scripts/install-composer.sh /tmp/install-composer.sh +RUN sh /tmp/install-composer.sh + + + + + +FROM php:7.3-cli-alpine AS deployer +RUN apk add \ + git \ + openssh-client \ + rsync + +RUN ssh-keygen \ + -q \ + -b 2048 \ + -t rsa \ + -f ~/.ssh/id_rsa + +ARG XDEBUG_VERSION=2.9.8 +RUN set -eux; \ + apk add --no-cache --virtual .build-deps $PHPIZE_DEPS; \ + pecl install xdebug-$XDEBUG_VERSION; \ + docker-php-ext-enable xdebug; \ + apk del .build-deps + +COPY --from=composer /tmp/composer /bin/composer +ENV E2E_ENV=1 +VOLUME [ "/project" ] +WORKDIR /project + + + + + +FROM php:7.3-apache AS server +RUN apt-get update && apt-get install -y \ + git \ + openssh-server \ + sudo \ + && rm -rf /var/lib/apt/lists/* + +# SSH login fix. Otherwise user is kicked off after login +RUN mkdir /run/sshd \ + && sed -i 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' /etc/pam.d/sshd + +# Configure Apache to expose healthcheck & configure site to use /var/www/html/current ad document root +COPY ./conf/healthcheck.conf /etc/apache2/sites-available/healthcheck.conf +COPY ./initial-site/index.html /var/www/html/initial-site/index.html + +ENV APACHE_DOCUMENT_ROOT /var/www/html/current +RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/000-default.conf \ + && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \ + && ln -s /var/www/html/initial-site /var/www/html/current \ + && chown -R www-data:www-data /var/www/html \ + && echo "Listen 81" >> /etc/apache2/ports.conf \ + && a2enmod rewrite \ + && a2ensite healthcheck + +RUN useradd \ + --create-home \ + deployer \ + && echo 'deployer:deployer' | chpasswd \ + && echo 'deployer ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && mkdir ~deployer/.ssh \ + && touch ~deployer/.ssh/authorized_keys \ + && chown -R deployer:deployer ~deployer/.ssh \ + && chmod 700 ~deployer/.ssh \ + && chmod 600 ~deployer/.ssh/authorized_keys + +COPY ./scripts/start-servers.sh /usr/local/bin/start-servers +COPY --from=composer /tmp/composer /usr/local/bin/composer +COPY --from=deployer /root/.ssh/id_rsa.pub /tmp/root_rsa.pub + +RUN chmod a+x /usr/local/bin/start-servers \ + && cat /tmp/root_rsa.pub >> ~deployer/.ssh/authorized_keys \ + && rm -rf /tmp/root_rsa.pub + +EXPOSE 22 80 81 +CMD [ "start-servers" ] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..1b323ba3c --- /dev/null +++ b/docker/README.md @@ -0,0 +1,88 @@ +# Deployer E2E testing environment + +This directory contains an end-to-end testing environment for Deployer. + +All commands mentioned in this readme, should be executed in the `docker` directory. + +## Requirements + +* Docker +* docker-compose + +## Running tests + +The E2E are started when running the `docker-compose up` command. +This will start the `server` container that has the Apache, OpenSSH & PHP 7.3 enabled. + +Once the `server` is up and running, the `deployer` container will be started and alongside it +the tests will be ran. + +## Adding new E2E tests + +The E2E test should be a part of the `e2e` test suite. +Each `e2e` test class should inherit from `AbstractE2ETest` class. + +Note: E2E tests will only run in an environment where env variable `E2E_ENV` has been set and has a truthy value. + +## Manually accessing the `deployer` container. + +The container can be accessed by running: + +```bash +docker-compose run deployer sh +``` + +This command will spawn a `sh` shell inside the `deployer` container. + +## About containers + +### `deployer` container + +The `deployer` container contains: + +* git +* PHP 7.3 with XDebug enabled +* rsync +* SSH client + +It is possible to access the `server` container via ssh by running: + +```bash +ssh deployer@server +``` + +`root`'s public key has been added to authorized keys for `deployer` user. + +#### Enabling XDebug + +To enable XDebug create a `docker-compose.override.yml` file with following content: + +```dockerfile +services: + deployer: + environment: + # See https://docs.docker.com/docker-for-mac/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host + # See https://github.com/docker/for-linux/issues/264 + # The `remote_host` below may optionally be replaced with `remote_connect_back=1` + XDEBUG_CONFIG: >- + remote_enable=1 + remote_host=${XDEBUG_HOST:-host.docker.internal} + remote_autostart=1 + remote_port=9000 + idekey=PHPSTORM + # This should correspond to the server declared in PHPStorm `Preferences | Languages & Frameworks | PHP | Servers` + # Then PHPStorm will use the corresponding path mappings + PHP_IDE_CONFIG: serverName=deployer-e2e +``` + +Note: you may want to set the `XDEBUG_HOST` env variable to point to your IP address when running tests in Linux. + +### `server` container + +The `server` container contains: + +* Apache (with the `DocumentRoot` set to `/var/www/html/current`) +* git +* PHP 7.3 +* SSH server with +* sudo (user `deployer` can use `sudo` without having to enter passwords) diff --git a/docker/conf/healthcheck.conf b/docker/conf/healthcheck.conf new file mode 100644 index 000000000..f51410f86 --- /dev/null +++ b/docker/conf/healthcheck.conf @@ -0,0 +1,32 @@ + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + #ServerName www.example.com + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + RewriteEngine on + RedirectMatch 204 /health_check + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..ba906bc1a --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,46 @@ +version: '2.4' + +services: + deployer: + build: + context: . + target: deployer + depends_on: + server: + condition: service_healthy + volumes: + - ./../:/project + command: "php vendor/bin/pest" + networks: + - e2e +# environment: +# # See https://docs.docker.com/docker-for-mac/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host +# # See https://github.com/docker/for-linux/issues/264 +# # The `remote_host` below may optionally be replaced with `remote_connect_back=1` +# XDEBUG_CONFIG: >- +# remote_enable=1 +# remote_host=${XDEBUG_RHOST:-host.docker.internal} +# remote_autostart=1 +# remote_port=9000 +# idekey=PHPSTORM +# # This should correspond to the server declared in PHPStorm `Preferences | Languages & Frameworks | PHP | Servers` +# # Then PHPStorm will use the corresponding path mappings +# PHP_IDE_CONFIG: serverName=deployer-e2e + + server: + build: + context: . + target: server + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:81/health_check"] + interval: 5s + timeout: 2s + retries: 3 + start_period: 2s + networks: + e2e: + aliases: + - server.test + +networks: + e2e: diff --git a/docker/initial-site/index.html b/docker/initial-site/index.html new file mode 100644 index 000000000..5016ba876 --- /dev/null +++ b/docker/initial-site/index.html @@ -0,0 +1,5 @@ + + + Hello World! + + \ No newline at end of file diff --git a/docker/scripts/install-composer.sh b/docker/scripts/install-composer.sh new file mode 100644 index 000000000..e2ac87df5 --- /dev/null +++ b/docker/scripts/install-composer.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +EXPECTED_CHECKSUM="$(wget -q -O - https://composer.github.io/installer.sig)" +php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" +ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" + +if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ] +then + >&2 echo 'ERROR: Invalid installer checksum' + rm composer-setup.php + exit 1 +fi + +php composer-setup.php --quiet --install-dir=/tmp --filename=composer +RESULT=$? +rm composer-setup.php +chmod a+x /tmp/composer +exit $RESULT \ No newline at end of file diff --git a/docker/scripts/start-servers.sh b/docker/scripts/start-servers.sh new file mode 100644 index 000000000..4fdb3220a --- /dev/null +++ b/docker/scripts/start-servers.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +echo "Starting SSH server" +/usr/sbin/sshd -D & + +echo "Starting Apache" +exec /usr/local/bin/apache2-foreground \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 59d8be4e4..f2a50f373 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -17,5 +17,8 @@ test/joy/ + + test/e2e/ + diff --git a/test/e2e/AbstractE2ETest.php b/test/e2e/AbstractE2ETest.php new file mode 100644 index 000000000..0a422e442 --- /dev/null +++ b/test/e2e/AbstractE2ETest.php @@ -0,0 +1,17 @@ +markTestSkipped('Cannot execute in non-E2E environment'); + } + } +} diff --git a/test/e2e/FunctionsE2ETest.php b/test/e2e/FunctionsE2ETest.php new file mode 100644 index 000000000..9f850e282 --- /dev/null +++ b/test/e2e/FunctionsE2ETest.php @@ -0,0 +1,27 @@ +init(self::RECIPE); + + $this->tester->run([ + 'test:functions:run-with-placeholders', + '-f' => self::RECIPE, + 'selector' => 'all', + ]); + + $display = $this->tester->getDisplay(); + $display = trim($display); // Output may contain newlines, so we should trim them in advance + + self::assertEquals(0, $this->tester->getStatusCode(), $display); + self::assertEquals('placeholder {{bar}} xyz%', $display); + } +} diff --git a/test/e2e/recipe/deploy.php b/test/e2e/recipe/deploy.php new file mode 100644 index 000000000..5a84c2aef --- /dev/null +++ b/test/e2e/recipe/deploy.php @@ -0,0 +1,13 @@ +setTag('e2e') + ->setRemoteUser('deployer') + ->setSshOptions( + [ + 'UserKnownHostsFile' => '/dev/null', + 'StrictHostKeyChecking' => 'no', + ] + ); diff --git a/test/e2e/recipe/functions.php b/test/e2e/recipe/functions.php new file mode 100644 index 000000000..0a5ca97e1 --- /dev/null +++ b/test/e2e/recipe/functions.php @@ -0,0 +1,12 @@ + '{{bar}}', 'baz' => 'xyz%' ]; + + $output = run($cmd, [ 'vars' => $vars ]); + output()->writeln($output); // we use this to skip \Deployer\parse() being called in normal \Deployer\writeln() +})->shallow();