diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7201f8c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +projects diff --git a/.travis.yml b/.travis.yml index 4e577e6..666179f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,16 @@ sudo: required language: generic env: - OS: linux - IMAGE_NAME: vhost-proxy:test + REPO: docksal/vhost-proxy + IMAGE_VHOST_PROXY: ${REPO}:dev + DOCKSAL_VHOST_PROXY_ACCESS_LOG: 1 + DOCKSAL_VHOST_PROXY_DEBUG_LOG: 1 + DOCKSAL_VHOST_PROXY_STATS_LOG: 1 # Only use seconds here, so that these can be used with "sleep" as well PROJECT_INACTIVITY_TIMEOUT: 30s PROJECT_DANGLING_TIMEOUT: 60s - PROXY_DEBUG: 1 - DOCKSAL_VERSION: master + PROJECTS_ROOT: ${TRAVIS_BUILD_DIR}/projects + DOCKSAL_VERSION: develop services: - docker @@ -18,12 +21,37 @@ install: - sudo sudo curl -L https://raw.githubusercontent.com/docksal/docksal/${DOCKSAL_VERSION}/bin/fin -o /usr/local/bin/fin && sudo chmod +x /usr/local/bin/fin - fin version - fin update - - fin docker build -t ${IMAGE_NAME} . - - PROJECTS_ROOT=$TRAVIS_BUILD_DIR IMAGE_VHOST_PROXY=$IMAGE_NAME fin reset proxy - fin sysinfo before_script: + # Pull curl image with http2 support (used in tests) + - docker pull badouralix/curl-http2 - .travis/before_script.sh + - ls -la "$PROJECTS_ROOT" + - fin pl -a script: - - bats tests/smoke-test.bats + - make + - make start + - make exec -e CMD="env" # Views env variables picked up by proxy + - make exec -e CMD="ls -la /projects" # View the contents of the /projects folder + - make test + +after_success: | + if [[ "${TRAVIS_PULL_REQUEST}" == "false" ]]; then + # develop => edge + [[ "${TRAVIS_BRANCH}" == "develop" ]] && TAG="edge" + # master => latest + [[ "${TRAVIS_BRANCH}" == "master" ]] && TAG="latest" + # tags/v1.2.0 => 1.2 + [[ "${TRAVIS_TAG}" != "" ]] && TAG="${TRAVIS_TAG:1:3}" + + if [[ "$TAG" != "" ]]; then + docker login -u "${DOCKER_USER}" -p "${DOCKER_PASS}" + docker tag ${IMAGE_VHOST_PROXY} ${REPO}:${TAG} + docker push ${REPO}:${TAG} + fi + fi + +after_failure: + - make logs diff --git a/.travis/before_script.sh b/.travis/before_script.sh index f46ba34..734e939 100755 --- a/.travis/before_script.sh +++ b/.travis/before_script.sh @@ -1,15 +1,24 @@ #!/usr/bin/env bash +# Store build directory +cwd=$(pwd) + +rm -rf ${cwd}/projects +mkdir -p ${cwd}/projects/project1 +mkdir -p ${cwd}/projects/project2 +mkdir -p ${cwd}/projects/project3 + # Regular project -git clone https://github.com/docksal/drupal7.git ../drupal7 -cwd=$(pwd) && cd ../drupal7 -fin start -cd $cwd - -# Project with the "io.docksal.permanent" flag set to true -# This project will not be automatically cleaned even after DANGLING_TIMEOUT period. -git clone https://github.com/docksal/drupal8.git ../drupal8 -cwd=$(pwd) && cd ../drupal8 +cd ${cwd}/projects/project1 +mkdir .docksal && mkdir docroot +echo "Hello world: Project 1" > docroot/index.html +fin reset -f + +# Permanent project +# "io.docksal.permanent=true" (will not be automatically cleaned even after DANGLING_TIMEOUT period) +cd ${cwd}/projects/project2 +mkdir .docksal && mkdir docroot +echo "Hello world: Project 2" > docroot/index.html local_yml=" version: '2.1' @@ -19,6 +28,31 @@ services: - io.docksal.permanent=true " echo "$local_yml" > .docksal/docksal-local.yml +fin reset -f + +# Nodejs app project +# "io.docksal.virtual-port=3000" (proxy forwards HTTP requests to a custom port 3000 instead of the default 80) +cd ${cwd}/projects/project3 +git clone https://github.com/docksal/example-nodejs.git . +# Make this project permanent too, otherwise it will be killed before tests run +local_yml=" +version: '2.1' + +services: + cli: + labels: + - io.docksal.permanent=true +" +echo "$local_yml" > .docksal/docksal-local.yml +fin reset -f -fin start -cd $cwd +# Nodejs app (standalone container) +# Node need to make this one permanent ("io.docksal.permanent=true") since it is a standalone container and not a project +fin docker rm -vf nodejs || true +fin docker run -d --name=nodejs \ + -v $(pwd):/app \ + --label=io.docksal.virtual-host=nodejs.docksal \ + --label=io.docksal.virtual-port=3000 \ + --expose 3000 \ + node:alpine \ + node /app/index.js diff --git a/Dockerfile b/Dockerfile index 7c110eb..3c12b3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,18 +9,24 @@ RUN apk add --no-cache \ nginx-lua \ && rm -rf /var/cache/apk/* -ENV DOCKER_VERSION 17.06.0-ce -ENV DOCKER_GEN_VERSION 0.7.3 +ARG DOCKER_VERSION=17.09.1-ce +ARG DOCKER_GEN_VERSION=0.7.4 +ARG GOTPL_VERSION=0.1.5 -# Install docker client binary from Github (if not mounting binary from host) -RUN curl -sSL -O "https://download.docker.com/linux/static/stable/x86_64/docker-$DOCKER_VERSION.tgz" \ +# Install docker client binary (if not mounting binary from host) +RUN curl -sSL -O "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz" \ && tar zxf docker-$DOCKER_VERSION.tgz && mv docker/docker /usr/local/bin && rm -rf docker-$DOCKER_VERSION* # Install docker-gen -ENV DOCKER_GEN_TARFILE docker-gen-alpine-linux-amd64-$DOCKER_GEN_VERSION.tar.gz -RUN curl -sSL https://github.com/jwilder/docker-gen/releases/download/$DOCKER_GEN_VERSION/$DOCKER_GEN_TARFILE -O \ +ARG DOCKER_GEN_TARFILE=docker-gen-alpine-linux-amd64-$DOCKER_GEN_VERSION.tar.gz +RUN curl -sSL -O "https://github.com/jwilder/docker-gen/releases/download/${DOCKER_GEN_VERSION}/${DOCKER_GEN_TARFILE}" \ && tar -C /usr/local/bin -xvzf $DOCKER_GEN_TARFILE && rm $DOCKER_GEN_TARFILE +# Install gotpl +ARG GOTPL_TARFILE=gotpl-alpine-linux-amd64-${GOTPL_VERSION}.tar.gz +RUN curl -sSL -O "https://github.com/wodby/gotpl/releases/download/${GOTPL_VERSION}/${GOTPL_TARFILE}" \ + && tar -C /usr/local/bin -xvzf $GOTPL_TARFILE && rm $GOTPL_TARFILE + RUN chown -R nginx:nginx /var/lib/nginx # Generate a self-signed cert @@ -29,11 +35,11 @@ RUN apk add --no-cache openssl \ -keyout /etc/nginx/server.key -out /etc/nginx/server.crt \ && apk del openssl -COPY conf/nginx/nginx.conf /etc/nginx/nginx.conf -COPY conf/nginx/default.conf.tmpl /etc/nginx/default.conf.tmpl -COPY conf/nginx/default_locations.conf /etc/nginx/default_locations.conf +COPY conf/nginx /opt/conf/nginx COPY conf/sudoers /etc/sudoers -COPY conf/supervisord.conf /etc/supervisor.d/supervisord.ini +# Override the main supervisord config file, since some parameters are not overridable via an include +# See https://github.com/Supervisor/supervisor/issues/962 +COPY conf/supervisord.conf /etc/supervisord.conf COPY conf/crontab /var/spool/cron/crontabs/root COPY bin /usr/local/bin COPY www /var/www/proxy @@ -41,12 +47,18 @@ COPY www /var/www/proxy # Fix permissions RUN chmod 0440 /etc/sudoers -# Disable INACTIVITY_TIMEOUT by default -ENV PROJECT_INACTIVITY_TIMEOUT 0 -# Disable DANGLING_TIMEOUT by default -ENV PROJECT_DANGLING_TIMEOUT 0 -# Disable debug output by default -ENV PROXY_DEBUG 0 + +ENV \ + # Disable INACTIVITY_TIMEOUT by default + PROJECT_INACTIVITY_TIMEOUT=0 \ + # Disable DANGLING_TIMEOUT by default + PROJECT_DANGLING_TIMEOUT=0 \ + # Disable access log by default + ACCESS_LOG=0 \ + # Disable debug output by default + DEBUG_LOG=0 \ + # Disable stats log by default + STATS_LOG=0 ENTRYPOINT ["docker-entrypoint.sh"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fda9b15 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +-include env_make + +VERSION ?= dev + +REPO = docksal/vhost-proxy +NAME = docksal-vhost-proxy + +.PHONY: build test push shell run start stop logs debug clean release + +build: + fin docker build -t $(REPO):$(VERSION) . + +test: + IMAGE=$(REPO):$(VERSION) bats tests/smoke-test.bats + +push: + fin docker push $(REPO):$(VERSION) + +exec: + fin docker exec -it $(NAME) $(CMD) + +shell: + make exec -e CMD=bash + +start: + fin system reset vhost-proxy + +stop: + fin docker stop $(NAME) + +logs: + fin docker logs $(NAME) + +clean: + fin docker rm -f $(NAME) + rm -rf projects + fin cleanup + +debug: build start + fin docker logs -f $(NAME) + +release: build + make push -e VERSION=$(VERSION) + +# Curl command with http2 support via a docker container +# Usage: make curl -e ARGS='-kI https://docksal.io' +curl: + docker run -t --rm badouralix/curl-http2 $(ARGS) + +default: build diff --git a/README.md b/README.md index 1ac8e3f..1f0f4f7 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,50 @@ This image(s) is part of the [Docksal](http://docksal.io) image library. Start the proxy container: -``` +```bash docker run -d --name docksal-vhost-proxy --label "io.docksal.group=system" --restart=always --privileged --userns=host \ -p "${DOCKSAL_VHOST_PROXY_PORT_HTTP:-80}":80 -p "${DOCKSAL_VHOST_PROXY_PORT_HTTPS:-443}":443 \ - -e PROJECT_INACTIVITY_TIMEOUT="${PROJECT_INACTIVITY_TIMEOUT:-0}" \ -v /var/run/docker.sock:/var/run/docker.sock \ docksal/vhost-proxy ``` -## Configuration +## Container configuration + +Proxy reads routing settings from container labels. The following labels are supported: + +`io.docksal.virtual-host` + +Virtual host mapping. Supports any domain (but does not handle DNS), multiple values separated by commas, wildcard +sub-domains. + +Example: `io.docksal.virtual-host=example1.com,*.example2.com` + + +`io.docksal.virtual-port` + +Virtual port mapping. Useful when a container exposes an non-default HTTP port (other than port `80`). +Only supports HTTP, single value. + +Example: `io.docksal.virtual-port=3000` + +### Example + +Launching a nodejs app container using port `3000` and host `myapp.example.com` + +```bash +docker run -d --name=nodejs \ + -v $(pwd):/app \ + --label=io.docksal.virtual-host=myapp.example.com \ + --label=io.docksal.virtual-port=3000 \ + --expose 3000 \ + node:alpine \ + node /app/index.js +``` + +## Advanced proxy configuration + +These advanced settings can be used in CI sandbox environments and help keep the resource usage down by stopping +Docksal project containers after a period of inactivity. Projects are automatically restarting upon a new HTTP request. `PROJECT_INACTIVITY_TIMEOUT` @@ -32,13 +67,12 @@ This option is inactive by default (set to `0`). `PROJECT_DANGLING_TIMEOUT` -!!!warning "WARNING" - This is a destructive option. Use at your own risk! +**WARNING: This is a destructive option. Use at your own risk!** Defines the timeout (e.g. 168h) of inactivity after which the project stack and code base will be entirely wiped out from the host. This option is inactive by default (set to `0`). -For the cleanup job to work, proxy needs access to the directory, where project code bases are located on the host. +For the cleanup job to work, proxy needs access to the projects directory on the host. Create a Docker bind volume pointing to the directory where projects are stored: ``` @@ -46,13 +80,16 @@ docker volume create --name docksal_projects --opt type=none --opt device=$PROJE ``` -Start the proxy container with two additional options (in the middle): +then pass it using `-v docksal_projects:/projects` in `docker run` command. -``` +Example (extra configuration in the middle): + +```bash docker run -d --name docksal-vhost-proxy --label "io.docksal.group=system" --restart=always --privileged --userns=host \ -p "${DOCKSAL_VHOST_PROXY_PORT_HTTP:-80}":80 -p "${DOCKSAL_VHOST_PROXY_PORT_HTTPS:-443}":443 \ -e PROJECT_INACTIVITY_TIMEOUT="${PROJECT_INACTIVITY_TIMEOUT:-0}" \ + -e PROJECT_INACTIVITY_TIMEOUT="${PROJECT_INACTIVITY_TIMEOUT:-0}" \ -e PROJECT_DANGLING_TIMEOUT="${PROJECT_DANGLING_TIMEOUT:-0}" \ -v docksal_projects:/projects \ @@ -60,6 +97,12 @@ docker run -d --name docksal-vhost-proxy --label "io.docksal.group=system" --res docksal/vhost-proxy ``` -`PROXY_DEBUG` +## Logging and debugging + +The following container environment variables can be used to enabled various logging options (disabled by default). + +`ACCESS_LOG` - Set to `1` to enable access logging. +`DEBUG_LOG` - Set to `1` to enable debug logging. +`STATS_LOG` - Set to `1` to enable project stats logging. -Set to `1` to enable debug logging. Check logs with `docker logs docksal-vhost-proxy`. +Check logs with `docker logs docksal-vhost-proxy`. diff --git a/bin/docker-entrypoint.sh b/bin/docker-entrypoint.sh index afd2f83..7612756 100755 --- a/bin/docker-entrypoint.sh +++ b/bin/docker-entrypoint.sh @@ -5,6 +5,10 @@ # Service mode (run as root) if [[ "$1" == "supervisord" ]]; then + # Generate config files from templates + gotpl /opt/conf/nginx/nginx.conf.tmpl > /etc/nginx/nginx.conf + gotpl /opt/conf/nginx/proxyctl.conf.tmpl > /etc/nginx/proxyctl.conf + exec supervisord -c /etc/supervisord.conf # Command mode (run as docker user) else diff --git a/bin/proxyctl b/bin/proxyctl index c386b93..c095970 100755 --- a/bin/proxyctl +++ b/bin/proxyctl @@ -1,234 +1,349 @@ #!/usr/bin/env bash +### Configuration variables ### + +DEBUG_LOG=${DEBUG_LOG:-0} +STATS_LOG=${STATS_LOG:-0} + +### Helper functions ### + +# Checks if there was any recent container activity (entries in the container logs) +# `docker log` does honor stdout vs stderr outputs. We route everything to stdout to do the comparison (2>&1) +is_container_active () +{ + container_id="$1" + [[ "$(docker logs --tail 1 --since "$PROJECT_INACTIVITY_TIMEOUT" "$container_id" 2>&1)" != "" ]] +} +is_container_dangling () +{ + container_id="$1" + [[ "$(docker logs --tail 1 --since "$PROJECT_DANGLING_TIMEOUT" "$container_id" 2>&1)" == "" ]] +} + +### Functions ### + # Start containers for a project identified with a virtual host # @param $1 virtual host name -_start () +start () { + # Set logging prefix for the function and trigger a log entry + export LOG_PREFIX='[start]' && log + local vhost="$1" - [[ "$vhost" == "" ]] && echo "ERROR: Empty virtual host." && return 1 + [[ "$vhost" == "" ]] && log "ERROR: Empty virtual host." && return 1 - echo "Stoping docker-gen..." - /usr/bin/supervisorctl stop docker-gen + log "Stoping docker-gen..." + supervisorctl stop docker-gen - # Match vhost to a web container (exact match only) + # Match vhost to the primary project container (exact match only) local project_name local container_id - project_name=$(/usr/local/bin/docker ps -a \ + + # Filter: primary containers with a defined virtual host + project_name=$(docker ps -a \ + --filter "label=io.docksal.project-root" \ --filter "label=io.docksal.virtual-host=$vhost" \ - --filter "label=com.docker.compose.project" \ --format '{{.Label "com.docker.compose.project"}}') # If exact match was not found, then we have multiple domains/wildcards (e.g. example.com,*.example.com,example2.com) # More complex processing goes below. if [[ "$project_name" == "" ]]; then - container_id=$(/usr/local/bin/docker ps -a \ + container_id=$(docker ps -a \ --filter "label=io.docksal.virtual-host=$vhost" \ --format '{{.ID }}') if [[ "$container_id" == "" ]]; then - # Get a list of all (running and stopped) web containers and their virtual host label values - local webs=$(/usr/local/bin/docker ps -a \ + # Get a list of all (running and stopped) projects and their virtual host label values + local all_projects=$(docker ps -a \ + --filter "label=io.docksal.project-root" \ --filter "label=io.docksal.virtual-host" \ --format '{{ .ID }}:{{.Label "com.docker.compose.project"}}:{{.Label "io.docksal.virtual-host"}}') - # Look for a matching label among all webs - for web in $webs; do + # Look for a matching label among all projects + while read _project; do # Read variables - IFS=':' read web_container_id web_project_name web_project_vhost <<< "$web"; - # Split web_project_vhost - IFS=',' read -a web_project_vhost_arr <<< "$web_project_vhost" - for i in "${web_project_vhost_arr[@]}"; do + IFS=':' read _container_id _project_name _project_vhosts <<< "$_project"; + # Split _project_vhosts + # Cannot get "while IFS=',' read" work together, so using and array and 'for' instead here. + IFS=',' read -a _project_vhost_arr <<< "$_project_vhosts" + for i in "${_project_vhost_arr[@]}"; do # Turn domain name into a regular expression (e.g. *.example.com => .*\.example\.com) - i_reg=$(echo $i | sed "s/\./\\\\./g" | sed "s/\*/\.\*/g") + i_reg=$(echo "$i" | sed "s/\./\\\\./g" | sed "s/\*/\.\*/g") # Match vhost using the regular expression we created - echo "$vhost" | grep "^$i_reg$" >/dev/null - # Store match and break - [[ $? -eq 0 ]] && project_name="$web_project_name" && container_id="$web_container_id" && break + if (echo "$vhost" | grep "^$i_reg$" >/dev/null); then + project_name="$_project_name" + container_id="$_container_id" + # Break two levels up if match was found + break 2 + fi done - # Break if match was found - if [[ "$project_name" != "" ]] || [[ "$container_id" != "" ]]; then - break - fi - done + done <<< "$all_projects" fi fi # No match if project_name is empty here. if [[ "$project_name" == "" ]]; then if [[ "$container_id" == "" ]]; then - echo "ERROR: No matching projects or containers found for virtual host ${vhost}." \ - && echo "Starting docker-gen..." \ - && /usr/bin/supervisorctl start docker-gen \ + log "ERROR: No matching projects or containers found for virtual host ${vhost}." \ + && log "Starting docker-gen..." \ + && supervisorctl start docker-gen \ && return 1 else - echo "Starting single container $container_id..." \ - && /usr/local/bin/docker start "$container_id" \ - && echo "Starting docker-gen..." \ - && /usr/bin/supervisorctl start docker-gen \ + log "Starting single container $container_id..." \ + && docker start "$container_id" \ + && log "Starting docker-gen..." \ + && supervisorctl start docker-gen \ && return 0 fi else - - # Connecting/re-connecting vhost-ptoxy to the project network + # Connecting/re-connecting vhost-proxy to the project network local network="${project_name}_default" # Making sure the network exists - /usr/local/bin/docker network create "$network" >/dev/null 2>&1 + docker network create "$network" >/dev/null 2>&1 # Reconnect vhost-proxy to the project network (in case vhost-proxy has been recently reset) - /usr/local/bin/docker network connect "$network" docksal-vhost-proxy >/dev/null 2>&1 + docker network connect "$network" docksal-vhost-proxy >/dev/null 2>&1 if [[ $? == 0 ]]; then - echo "Connected proxy to network: ${network}." + log "Connected proxy to network: ${network}." fi - echo "Starting containers for $project_name..." + log "Starting containers for $project_name..." # Dirty hack to avoid using docker-compose and still be able to launch containers with dependencies up to 3 levels deep. for i in `seq 1 3`; do - echo "Pass #$i..." - /usr/local/bin/docker ps -qa --filter "label=com.docker.compose.project=${project_name}" | xargs /usr/local/bin/docker start + log "Pass #$i..." + docker ps -qa --filter "label=com.docker.compose.project=${project_name}" | xargs docker start done - echo "Starting docker-gen..." - /usr/bin/supervisorctl start docker-gen + log "Starting docker-gen..." + supervisorctl start docker-gen fi } # Stop containers for projects after a timeout set via PROJECT_INACTIVITY_TIMEOUT -_stop () +stop () { + # Set logging prefix for the function and trigger a log entry + export LOG_PREFIX='[stop]' && log + # Allow disabling PROJECT_INACTIVITY_TIMEOUT (0 = disabled) [[ "$PROJECT_INACTIVITY_TIMEOUT" == 0 ]] && exit - # Get a list of running web containers - local running_webs=$(/usr/local/bin/docker ps \ - --filter "label=com.docker.compose.service=web" \ - --format '{{.ID}}:{{.Label "com.docker.compose.project"}}') + # Filter: primary containers with a defined virtual host + local running_projects=$(docker ps \ + --filter "label=io.docksal.project-root" \ + --filter "label=io.docksal.virtual-host" \ + --format '{{ .ID }}:{{ .Label "com.docker.compose.project" }}') - for running_web in $running_webs; do + while read running_project; do # Read variables - IFS=':' read container_id project_name <<< "$running_web"; + IFS=':' read container_id project_name <<< "$running_project"; # Skip containers with empty values [[ "$project_name" == "" ]] && continue + # Skip active projects + is_container_active "$container_id" && continue - # See if there was any recent container activity (entries in the container logs) - # docker log does honor stdout vs stderr outputs. We route everything to stdout to do the comparison (2>&1) - if [[ "$(/usr/local/bin/docker logs --tail 1 --since $PROJECT_INACTIVITY_TIMEOUT $container_id 2>&1)" != "" ]]; then - # Active - echo "Project: $project_name is active. Skipping." - else - # Not active - echo "Project: $project_name is NOT active. Stopping..." - # Stop - /usr/local/bin/docker ps -q --filter "label=com.docker.compose.project=${project_name}" | xargs /usr/local/bin/docker stop - # Disconnect vhost-proxy from the project network and remove the network. - # See https://github.com/docksal/service-vhost-proxy/issues/6 for more details on why this is necessary. - local network="${project_name}_default" - /usr/local/bin/docker network disconnect "$network" docksal-vhost-proxy - /usr/local/bin/docker network rm "$network" - fi - done + # Stop inactive project containers + log "Stopping inactive project: ${project_name}..." + docker ps -q --filter "label=com.docker.compose.project=${project_name}" | xargs docker stop + # Disconnect vhost-proxy from the project network and remove the network. + # See https://github.com/docksal/service-vhost-proxy/issues/6 for more details on why this is necessary. + local network="${project_name}_default" + docker network disconnect "$network" docksal-vhost-proxy + docker network rm "$network" + done <<< "$running_projects" } # (Re)connect proxy to project networks. # Useful when proxy has been just re-provisioned and should be re-connected to existing project networks. -_networks () +networks () { - project_names=$(/usr/local/bin/docker ps \ + # Filter: primary containers with a defined virtual host + project_names=$(docker ps \ + --filter "label=io.docksal.project-root" \ --filter "label=io.docksal.virtual-host" \ --format '{{.Label "com.docker.compose.project"}}') - for project_name in $project_names; do + + while read project_name; do local network="${project_name}_default" # Making sure the network exists - /usr/local/bin/docker network create "$network" >/dev/null 2>&1 + docker network create "$network" >/dev/null 2>&1 # Reconnect vhost-proxy to the project network (in case vhost-proxy has been recently reset) - /usr/local/bin/docker network connect "$network" docksal-vhost-proxy >/dev/null 2>&1 + docker network connect "$network" docksal-vhost-proxy >/dev/null 2>&1 if [[ $? == 0 ]]; then - echo "Connected proxy to network: ${network}." + log "Connected proxy to network: ${network}." fi - done + done <<< "$project_names" } -_cron () +cron () { - _stop + # Set logging prefix for the function and trigger a log entry + export LOG_PREFIX='[cron]' && log + + stop + stats } # Removed projects (containers and sources) after a timeout set via PROJECT_DANGLING_TIMEOUT. # Projects with the label "io.docksal.permanent=true" are considered permanent and skipped. -_cleanup () +cleanup () { + # Set logging prefix for the function and trigger a log entry + export LOG_PREFIX='[cleanup]' && log + # Allow disabling PROJECT_DANGLING_TIMEOUT (0 = disabled) [[ "$PROJECT_DANGLING_TIMEOUT" == 0 ]] && exit + # Filter: primary containers with a defined virtual host projects=$(docker ps -a \ - --filter "label=com.docker.compose.project" \ - --filter "label=com.docker.compose.service=web" \ --filter "label=io.docksal.project-root" \ + --filter "label=io.docksal.virtual-host" \ --format '{{ .ID }}:{{ .Label "com.docker.compose.project" }}:{{ .Label "io.docksal.project-root" }}:{{ .Label "io.docksal.permanent" }}') - for project in $projects; do + while read project; do IFS=':' read container_id project_name project_root permanent <<< "$project" - if [[ "$(/usr/local/bin/docker logs --tail 1 --since $PROJECT_DANGLING_TIMEOUT $container_id)" != "" ]]; then - # Active - echo "Project: $project_name is not dangling. Skipping." - else - if [[ "$permanent" == "true" ]]; then - # Not active but keep alive - echo "Project: $project_name is dangling, but marked as permanent. Skipping." - else - # Not active - echo "Project: $project_name is dangling. Removing..." - # Stop and remove containers - docker ps -qa --filter "label=com.docker.compose.project=${project_name}" | xargs docker rm -vf - # Disconnect vhost-proxy from the project network and remove the network. - # See https://github.com/docksal/service-vhost-proxy/issues/6 for more details on why this is necessary. - local network="${project_name}_default" - docker network disconnect "$network" docksal-vhost-proxy - docker network rm "$network" - echo "Removing project directory..." - # This assumes all projects are kept in the same directory, which is mounted at /projects - local mounted_project_root="/projects/$(basename $project_root)" - [[ -d $mounted_project_root ]] && rm -rf $mounted_project_root - # Remove dangling images - echo "Removing dangling images..." - docker images -qf dangling=true | xargs docker rmi 2>/dev/null - # Remove dangling images - echo "Removing dangling volumes..." - docker volume ls -qf dangling=true | xargs docker volume rm 2>/dev/null - fi + # Skip permenent projects + [[ "$permanent" == "true" ]] && continue + # Skip active/not dangling projects + ! is_container_dangling "$container_id" && continue + + # Remove dangling project containers + log "Removing dangling project: ${project_name}..." + docker ps -qa --filter "label=com.docker.compose.project=${project_name}" | xargs docker rm -f + # Disconnect vhost-proxy from the project network and remove the network. + # See https://github.com/docksal/service-vhost-proxy/issues/6 for more details on why this is necessary. + local network="${project_name}_default" + docker network disconnect "$network" docksal-vhost-proxy + docker network rm "$network" + # Remove project directory + # This assumes all projects are kept in the same directory, which is mounted at /projects + local mounted_project_root="/projects/$(basename ${project_root})" + if [[ -d "$mounted_project_root" ]]; then + log "Removing directory: $mounted_project_root..." + rm -rf "$mounted_project_root" fi - done + done <<< "$projects" + + echo "Removing dangling images..." + docker image prune -f + echo "Removing dangling volumes..." + docker volume prune -f + echo "Removing dangling networks..." + docker network prune -f } # Trigger nginx config reload -_notify () +notify () { - if [[ "$PROXY_DEBUG" == 1 ]]; then - cat /etc/nginx/conf.d/default.conf + # Dump vhosts config into logs in debug mode + if [[ "$DEBUG_LOG" != 0 ]]; then + echo "DEBUG: /etc/nginx/conf.d/vhosts.conf" + echo "--------------------------------------------------" + cat /etc/nginx/conf.d/vhosts.conf + echo "--------------------------------------------------" fi + nginx -t && nginx -s reload } +# Helper for writing log messages +# @param $@ Log message +log () +{ + echo "$(date +"%F %H:%M:%S") [proxyctl] ${LOG_PREFIX} $@" +} + +# Print stats for projects +stats () +{ + # Return if stats logging is disabled + [[ "$STATS_LOG" == 0 ]] && return + + # Set logging prefix for the function and trigger a log entry + export LOG_PREFIX='[stats]' && log + + # Filter: primary containers with a defined virtual host + projects=$(docker ps -a \ + --filter "label=io.docksal.project-root" \ + --filter "label=io.docksal.virtual-host" \ + --format '{{ .ID }}:{{ .Label "com.docker.compose.project" }}:{{ .RunningFor }}:{{ .Status }}:{{ .Label "io.docksal.permanent" }}') + + # Stats + local runing + local active + local permanent + local dangling + # Aggregated stats + local sum_total=0 + local sum_running=0 + local sum_permanent=0 + local sum_active=0 + local sum_dangling=0 + + while read project; do + IFS=':' read container_id project_name created status permanent <<< "$project" + + let "sum_total += 1" + + if [[ "$status" =~ "Up" ]]; then + let "sum_running += 1" + running=1 + else + running=0 + fi + + if is_container_active "$container_id"; then + let "sum_active += 1" + active=1 + else + active=0 + fi + + if [[ "$permanent" != "true" ]]; then + permanent=0 + else + let "sum_permanent += 1" + permanent=1 + fi + + if is_container_dangling "$container_id"; then + let "sum_dangling += 1" + dangling=1 + else + dangling=0 + fi + + log "Project: ${project_name} | Created: ${created} | Status: ${status} | Running: ${running} | Active: ${active} | Permanent: ${permanent} | Dangling: ${dangling}" + done <<< "$projects" + + # Aggregated stats + log "[summary] Total: ${sum_total} | Running: ${sum_running} | Active: ${sum_active} | Permanent: ${sum_permanent} | Dangling: ${sum_dangling}" +} + #-------------------------- RUNTIME STARTS HERE ---------------------------- # Parse other parameters case "$1" in start) shift - _start "$@" + start "$@" ;; stop) - _stop + stop ;; cron) - _cron + cron ;; notify) - _notify + notify ;; networks) - _networks + networks ;; cleanup) - _cleanup + cleanup + ;; + stats) + stats ;; *) - echo "Usage: $0 start |stop" + echo "Usage: $0 start |stop|cron|notify|networks|cleanup|stats" esac diff --git a/conf/crontab b/conf/crontab index 6cfa2cd..ed696a6 100644 --- a/conf/crontab +++ b/conf/crontab @@ -1,2 +1,5 @@ -*/5 * * * * /usr/local/bin/proxyctl cron -0 0 * * * /usr/local/bin/proxyctl cleanup +# Run `proxyctl cron` every minute +* * * * * /usr/local/bin/proxyctl cron + +# Run `proxyctl cleanup` every hour +0 * * * * /usr/local/bin/proxyctl cleanup diff --git a/conf/nginx/nginx.conf b/conf/nginx/nginx.conf.tmpl similarity index 88% rename from conf/nginx/nginx.conf rename to conf/nginx/nginx.conf.tmpl index 0814e2f..0f9ef0c 100644 --- a/conf/nginx/nginx.conf +++ b/conf/nginx/nginx.conf.tmpl @@ -3,7 +3,7 @@ worker_processes 1; pid /var/run/nginx.pid; daemon off; -env PROXY_DEBUG; +env DEBUG_LOG; # Send logs to stderr error_log /dev/stderr warn; @@ -18,7 +18,11 @@ http { default_type application/octet-stream; log_format simple '$remote_addr [$time_local] "$request" $status $body_bytes_sent "$http_referer"'; + {{ if ne (getenv "ACCESS_LOG") "0" }} access_log /dev/stdout simple; + {{ else }} + access_log off; + {{ end }} lua_shared_dict hosts 1m; @@ -26,24 +30,23 @@ http { listen 80; server_name _; # This is just an invalid value which will never trigger on a real hostname. - include /etc/nginx/default_locations.conf; + include /etc/nginx/proxyctl.conf; } server { - listen 443; + listen 443 ssl http2; server_name _; # This is just an invalid value which will never trigger on a real hostname. ssl_certificate /etc/nginx/server.crt; ssl_certificate_key /etc/nginx/server.key; - ssl on; ssl_session_cache builtin:1000 shared:SSL:10m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4; ssl_prefer_server_ciphers on; - include /etc/nginx/default_locations.conf; + include /etc/nginx/proxyctl.conf; } # If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the @@ -76,7 +79,9 @@ http { # Disable output response body bufferring proxy_buffering off; # Increase output response headers buffer size (this is separate from proxy_buffering, defaults to 4k) - proxy_buffer_size 8k; + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; # Fixes random issues with POST requests # See https://github.com/dockerfile/nginx/issues/4#issuecomment-209440995 diff --git a/conf/nginx/default_locations.conf b/conf/nginx/proxyctl.conf.tmpl similarity index 88% rename from conf/nginx/default_locations.conf rename to conf/nginx/proxyctl.conf.tmpl index f561b6e..d5e853b 100644 --- a/conf/nginx/default_locations.conf +++ b/conf/nginx/proxyctl.conf.tmpl @@ -4,7 +4,7 @@ location / { rewrite_by_lua_block { function dpr(message) - if (tonumber(os.getenv("PROXY_DEBUG")) > 0) then + if (tonumber(os.getenv("DEBUG_LOG")) > 0) then ngx.log(ngx.STDERR, "DEBUG: " .. message) end end @@ -14,7 +14,7 @@ location / { hosts:set(ngx.var.host, 1) dpr("Locked " .. ngx.var.host) -- Lanch project start script - local exit_code = os.execute("sudo /usr/local/bin/proxyctl start \"" .. ngx.var.host .. "\"") + local exit_code = os.execute("PATH=/usr/local/bin:$PATH sudo proxyctl start \"" .. ngx.var.host .. "\"") -- If all went well, reload the page if (exit_code == 0) then ngx.header.content_type = 'text/html'; diff --git a/conf/nginx/default.conf.tmpl b/conf/nginx/vhosts.conf.tmpl similarity index 82% rename from conf/nginx/default.conf.tmpl rename to conf/nginx/vhosts.conf.tmpl index c3fa5f6..90c176e 100644 --- a/conf/nginx/default.conf.tmpl +++ b/conf/nginx/vhosts.conf.tmpl @@ -4,25 +4,33 @@ {{ if .Address }} {{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}} {{ if and .Container.Node.ID .Address.HostPort }} - # {{ .Container.Node.Name }}/{{ .Container.Name }} - server {{ .Container.Node.Address.IP }}:{{ .Address.HostPort }}; + + # {{ .Container.Node.Name }}/{{ .Container.Name }} + server {{ .Container.Node.Address.IP }}:{{ .Address.HostPort }}; + {{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}} {{ else if .Network }} - # {{ .Container.Name }} - server {{ .Network.IP }}:{{ .Address.Port }}; + + # {{ .Container.Name }} + server {{ .Network.IP }}:{{ .Address.Port }}; + {{ end }} {{ else if .Network }} + # {{ .Container.Name }} server {{ .Network.IP }} down; + {{ end }} {{ end }} {{ define "http" }} - # HTTP + ## HTTP server { listen 80; {{ range $host := split .Hosts "," }} - server_name {{ $host }}; + + server_name {{ $host }}; + {{ end }} location / { @@ -32,18 +40,19 @@ {{ end }} {{ define "https" }} - # HTTPS + ## HTTPS server { - listen 443; + listen 443 ssl http2; {{ range $host := split .Hosts "," }} - server_name {{ $host }}; + + server_name {{ $host }}; + {{ end }} ssl_certificate /etc/nginx/server.crt; ssl_certificate_key /etc/nginx/server.key; - ssl on; ssl_session_cache builtin:1000 shared:SSL:10m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4; @@ -55,17 +64,18 @@ } {{ end }} -{{ $pr_containers := whereLabelExists $ "com.docker.compose.service" }} +{{ $dc_containers := whereLabelExists $ "com.docker.compose.service" }} -{{ range $hosts, $containers_vhost := groupByLabel $pr_containers "io.docksal.virtual-host" }} +{{ range $hosts, $containers_vhost := groupByLabel $dc_containers "io.docksal.virtual-host" }} {{ range $service, $containers := groupByLabel $containers_vhost "com.docker.compose.service" }} - {{ $pr_container := $containers | first }} {{ $project := index $pr_container.Labels "com.docker.compose.project" }} {{ $upstream := (print $project "-" $service) }} - ## Reachable via "{{ $project }}_default" network - upstream {{ $upstream }} { + + # -------------------------------------------------- # + # Reachable via "{{ $project }}_default" network + upstream {{ $upstream }} { {{ range $container := $containers }} {{ $addrLen := len $container.Addresses }} @@ -78,7 +88,8 @@ {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} {{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var, falling back to standard web port 80 */}} {{ else }} - {{ $port := coalesce $container.Env.VIRTUAL_PORT "80" }} + {{/* Assume port 80 by default */}} + {{ $port := or (index $container.Labels "io.docksal.virtual-port") "80" }} {{ $address := where $container.Addresses "Port" $port | first }} {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} {{ end }} @@ -86,7 +97,7 @@ {{ end }} {{ end }} {{ end }} - } + } {{ template "http" (dict "Hosts" $hosts "Upstream" $upstream) }} @@ -95,6 +106,7 @@ {{ end }} {{ end }} + # -------------------------------------------------- # {{ end }} @@ -119,7 +131,8 @@ {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} {{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var, falling back to standard web port 80 */}} {{ else }} - {{ $port := coalesce $container.Env.VIRTUAL_PORT "80" }} + {{/* Assume port 80 by default */}} + {{ $port := or (index $container.Labels "io.docksal.virtual-port") "80" }} {{ $address := where $container.Addresses "Port" $port | first }} {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} {{ end }} diff --git a/conf/supervisord.conf b/conf/supervisord.conf index fd60434..756ba1c 100644 --- a/conf/supervisord.conf +++ b/conf/supervisord.conf @@ -3,12 +3,13 @@ nodaemon = true # ---------------------------------------------------------------------------------------------------- # Optional stuff to make supervisord complain less about misc things not being configured +user = root +# Cannot use /dev/stdout here as supervisord will freak out logfile = /var/log/supervisord.log pidfile = /var/run/supervisord.pid [unix_http_server] file = /var/run/supervisord.sock -chmod = 0700 username = dummy password = dummy @@ -23,7 +24,7 @@ supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface # ---------------------------------------------------------------------------------------------------- [program:docker-gen] -command = docker-gen -watch -notify "proxyctl notify" -notify-output /etc/nginx/default.conf.tmpl /etc/nginx/conf.d/default.conf +command = docker-gen -watch -notify "proxyctl notify" -notify-output /opt/conf/nginx/vhosts.conf.tmpl /etc/nginx/conf.d/vhosts.conf stdout_logfile = /dev/stdout stdout_logfile_maxbytes = 0 stderr_logfile = /dev/stderr @@ -37,7 +38,8 @@ stderr_logfile = /dev/stderr stderr_logfile_maxbytes = 0 [program:crond] -command = crond -f -d 2 +# Logging for crond (-d x): default: 8, more logging: 6, most verbose: 0. +command = crond -f -d 8 stdout_logfile = /dev/stdout stdout_logfile_maxbytes = 0 stderr_logfile = /dev/stderr diff --git a/tests/smoke-test.bats b/tests/smoke-test.bats index be27420..0b9b912 100644 --- a/tests/smoke-test.bats +++ b/tests/smoke-test.bats @@ -16,121 +16,211 @@ teardown() { # Uncomment below, then comment skip in the test you want to debug. When done, reverse. #SKIP=1 -@test "Proxy container is up and using the \"${IMAGE_NAME}\" image" { - [[ $SKIP == 1 ]] && skip +@test "Proxy container is up and using the \"${IMAGE}\" image" { + [[ ${SKIP} == 1 ]] && skip run fin docker ps --filter "name=docksal-vhost-proxy" --format "{{ .Image }}" - [[ $output =~ "${IMAGE_NAME}" ]] + [[ "$output" =~ "$IMAGE" ]] + unset output } -@test "Proxy returns 404 for a non-existing virtual-host" { - [[ $SKIP == 1 ]] && skip +@test "Projects directory is mounted" { + [[ ${SKIP} == 1 ]] && skip - run curl -I http://test.docksal/robots.txt - [[ $output =~ "HTTP/1.1 404 Not Found" ]] + run fin docker exec docksal-vhost-proxy ls -la /projects + [[ "$output" =~ "project1" ]] + [[ "$output" =~ "project2" ]] } -@test "Proxy returns 200 for an existing virtual-host" { - [[ $SKIP == 1 ]] && skip +@test "Cron is working" { + [[ ${SKIP} == 1 ]] && skip - run curl -v http://drupal7.docksal/robots.txt - [[ $output =~ "robots.txt" ]] + # 'proxyctl cron' should be invoked every minute + sleep 60s - run curl -v http://drupal8.docksal/robots.txt - [[ $output =~ "robots.txt" ]] + run make logs + echo "$output" | grep "[proxyctl] [cron]" + unset output + + # Kill crontab once this test completes, so that cron does not interfere with the rest of the tests + make exec -e CMD="crontab -r" } -@test "Proxy stops project containers after \"${PROJECT_INACTIVITY_TIMEOUT}\" of inactivity" { - [[ $SKIP == 1 ]] && skip +@test "Test projects are up and running" { + [[ ${SKIP} == 1 ]] && skip - [[ "$PROJECT_DANGLING_TIMEOUT" == "0" ]] && \ - skip "Stopping has been disabled via PROJECT_INACTIVITY_TIMEOUT=0" + fin @project1 restart + fin @project2 restart + fin @project3 restart - sleep $PROJECT_INACTIVITY_TIMEOUT && sleep 5 - # Trigger proxyctl stop manually to skip the cron job wait. - fin docker exec docksal-vhost-proxy proxyctl stop + run fin pl + [[ "$output" =~ "project1" ]] + [[ "$output" =~ "project2" ]] + [[ "$output" =~ "project3" ]] +} + +@test "Proxy returns 404 for a non-existing virtual-host" { + [[ ${SKIP} == 1 ]] && skip - # Check project was stopped - [[ $(fin docker ps -a --filter "name=drupal7_web_1" --format "{{ .Status }}") =~ "Exited (0)" ]] - # Check project network was removed - [[ $(fin docker network ls -q --filter "name=drupal7_default" | wc -l) =~ "0" ]] + run curl -I http://nonsense.docksal + [[ "$output" =~ "HTTP/1.1 404 Not Found" ]] + unset output } -@test "Proxy starts an existing stopped project" { - [[ $SKIP == 1 ]] && skip +@test "Proxy returns 200 for an existing virtual-host" { + [[ ${SKIP} == 1 ]] && skip - [[ "$PROJECT_DANGLING_TIMEOUT" == "0" ]] && \ - skip "Stopping has been disabled via PROJECT_INACTIVITY_TIMEOUT=0" + run curl -I http://project1.docksal + [[ "$output" =~ "HTTP/1.1 200 OK" ]] + unset output - run curl http://drupal7.docksal/robots.txt - [[ $output =~ "Waking up the daemons..." ]] + run curl -I http://project2.docksal + [[ "$output" =~ "HTTP/1.1 200 OK" ]] + unset output } -@test "Proxy starts the project within 15 seconds" { - [[ $SKIP == 1 ]] && skip +# We have to use a different version of curl here built with http2 support +@test "Proxy uses HTTP/2 for HTTPS connections" { + [[ ${SKIP} == 1 ]] && skip - [[ "$PROJECT_DANGLING_TIMEOUT" == "0" ]] && \ - skip "Stopping has been disabled via PROJECT_INACTIVITY_TIMEOUT=0" + # Non-existing project + run make curl -e ARGS='-kI https://nonsense.docksal' + [[ "$output" =~ "HTTP/2 404" ]] + unset output - # Wait for start - sleep 15 - run curl http://drupal7.docksal/robots.txt - [[ $output =~ "robots.txt" ]] + # Existing projects + run make curl -e ARGS='-kI https://project1.docksal' + [[ "$output" =~ "HTTP/2 200" ]] + unset output + + run make curl -e ARGS='-kI https://project2.docksal' + [[ "$output" =~ "HTTP/2 200" ]] + unset output +} + +@test "Proxy stops project containers after \"${PROJECT_INACTIVITY_TIMEOUT}\" of inactivity" { + [[ ${SKIP} == 1 ]] && skip + + [[ "$PROJECT_INACTIVITY_TIMEOUT" == "0" ]] && + skip "Stopping has been disabled via PROJECT_INACTIVITY_TIMEOUT=0" + + # Restart projects to reset timing + fin @project1 restart + fin @project2 restart + + # Wait + date + sleep ${PROJECT_INACTIVITY_TIMEOUT} + date + + fin docker exec docksal-vhost-proxy proxyctl stats + # Trigger proxyctl stop manually to skip the cron job wait. + # Note: cron job may still have already happened here and stopped the inactive projects + fin docker exec docksal-vhost-proxy proxyctl stop + + # Check projects were stopped, but not removed + run fin pl -a + echo "$output" | grep project1 | grep 'Exited (0)' + echo "$output" | grep project2 | grep 'Exited (0)' + unset output + + # Check project networks were removed + run fin docker network ls + echo "$output" | grep -v project1 + echo "$output" | grep -v project2 + unset output } -@test "Proxy starts an existing stopped project via HTTPS" { - [[ $SKIP == 1 ]] && skip +@test "Proxy starts an existing stopped project (HTTP)" { + [[ ${SKIP} == 1 ]] && skip - [[ "$PROJECT_DANGLING_TIMEOUT" == "0" ]] && \ - skip "Stopping has been disabled via PROJECT_INACTIVITY_TIMEOUT=0" + # Make sure the project is stopped + fin @project1 stop - cwd=$(pwd) - cd ../drupal7 && fin stop - cd $cwd + run curl http://project1.docksal + [[ "$output" =~ "Waking up the daemons..." ]] + unset output - run curl -k https://drupal7.docksal/robots.txt - [[ $output =~ "Waking up the daemons..." ]] + run curl http://project1.docksal + [[ "$output" =~ "Project 1" ]] + unset output } -@test "Proxy starts the project via HTTPS within 15 seconds" { - [[ $SKIP == 1 ]] && skip +@test "Proxy starts an existing stopped project (HTTPS)" { + [[ ${SKIP} == 1 ]] && skip - [[ "$PROJECT_DANGLING_TIMEOUT" == "0" ]] && \ - skip "Stopping has been disabled via PROJECT_INACTIVITY_TIMEOUT=0" + # Make sure the project is stopped + fin @project1 stop - # Wait for start - sleep 15 - run curl -k https://drupal7.docksal/robots.txt - [[ $output =~ "robots.txt" ]] + run curl -k https://project1.docksal + [[ "$output" =~ "Waking up the daemons..." ]] + unset output + + run curl -k https://project1.docksal + [[ "$output" =~ "Project 1" ]] + unset output +} + +@test "Proxy cleans up non-permanent projects after \"${PROJECT_DANGLING_TIMEOUT}\" of inactivity" { + [[ ${SKIP} == 1 ]] && skip + + [[ "$PROJECT_DANGLING_TIMEOUT" == "0" ]] && + skip "Cleanup has been disabled via PROJECT_DANGLING_TIMEOUT=0" + + # Restart projects to reset timing + fin @project1 restart + fin @project2 restart + + # Wait + date + sleep ${PROJECT_DANGLING_TIMEOUT} + date + + fin docker exec docksal-vhost-proxy proxyctl stats + # Trigger proxyctl cleanup manually to skip the cron job wait. + fin docker exec docksal-vhost-proxy proxyctl cleanup + + # Check project1 containers were removed + run fin docker ps -a -q --filter "label=com.docker.compose.project=project1" + [[ "$output" == "" ]] + unset output + # Check project1 network was removed + run fin docker network ls + echo "$output" | grep -v project1 + unset output + # Check project1 folder was removed + fin docker exec docksal-vhost-proxy ls -la /projects + echo "$output" | grep -v project1 + + # Check that project2 still exist + run fin pl -a + echo "$output" | grep project2 + unset output + # Check that project2 folder was NOT removed + run fin docker exec docksal-vhost-proxy ls -la /projects + echo "$output" | grep project2 + unset output } -@test "Proxy cleans up projects after \"${PROJECT_DANGLING_TIMEOUT}\" of inactivity" { - [[ $SKIP == 1 ]] && skip +@test "Proxy can route request to a non-default port (project)" { + [[ ${SKIP} == 1 ]] && skip - [[ "$PROJECT_DANGLING_TIMEOUT" == "0" ]] && \ - skip "Cleanup has been disabled via PROJECT_DANGLING_TIMEOUT=0" + # Restart projects to reset timing + fin @project3 restart - sleep $PROJECT_DANGLING_TIMEOUT && sleep 5 - # Trigger proxyctl cleanup manually to skip the cron job wait. - fin docker exec docksal-vhost-proxy proxyctl cleanup + # TODO: WTF is it stopped here? + fin docker exec docksal-vhost-proxy proxyctl stats + curl -I http://example-nodejs.docksal - # Check project containers were removed - [[ $(fin docker ps -a -q --filter "label=com.docker.compose.project=drupal7" | wc -l) =~ "0" ]] - # Check project network was removed - [[ $(fin docker network ls -q --filter "name=drupal7_default" | wc -l) =~ "0" ]] - # Check project folder was removed - [[ ! -d "/projects/drupal7" ]] + run curl http://example-nodejs.docksal + [[ "$output" =~ "Hello World!" ]] + unset output } -@test "Proxy does not clean up permanent projects" { - [[ $SKIP == 1 ]] && skip - - [[ "$PROJECT_DANGLING_TIMEOUT" == "0" ]] && \ - skip "Cleanup has been disabled via PROJECT_DANGLING_TIMEOUT=0" +@test "Proxy can route request to a non-default port (standalone container)" { + [[ ${SKIP} == 1 ]] && skip - # Check that project containers exist - # Using both filter to be sure the label io.docksal.permanent was set properly on the drupal8 project web container - [[ $(fin docker ps -a --filter "label=io.docksal.permanent=true" --filter "name=drupal8_web_1" --format "{{ .Status }}") =~ "Exited (0)" ]] - # Check that project folder exists - [[ ! -d "/projects/drupal8" ]] + run curl -k http://nodejs.docksal + [[ "$output" =~ "Hello World!" ]] + unset output } diff --git a/www/loading.html b/www/loading.html index 01b1b21..c51d313 100644 --- a/www/loading.html +++ b/www/loading.html @@ -34,7 +34,7 @@
- Loading... + Loading...
diff --git a/www/not-found.html b/www/not-found.html index ba0bf7c..a90d7d6 100644 --- a/www/not-found.html +++ b/www/not-found.html @@ -33,7 +33,7 @@
- Loading... + Loading...