diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0aee722483c..72a555571e9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,6 +58,7 @@ etc). Please include: * The output of `free -m` * The output of `docker version`. * The output of `docker -D info`. +* The output of `sigil -v`. * The output of `docker run --rm -ti gliderlabs/herokuish:latest herokuish version` * The output of `dokku version`. * The output of `dokku plugin`. diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 573ded12b2d..46cb87b9a47 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -7,6 +7,7 @@ Output of the following commands - `free -m`: - `docker version`: - `docker -D info`: +- `sigil -v`: - `docker run -ti gliderlabs/herokuish:latest herokuish version`: - `dokku version`: - `dokku plugin`: diff --git a/Makefile b/Makefile index 715e95e8f84..1613d728910 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ DOKKU_VERSION = master SSHCOMMAND_URL ?= https://raw.githubusercontent.com/dokku/sshcommand/master/sshcommand PLUGN_URL ?= https://github.com/dokku/plugn/releases/download/v0.2.1/plugn_0.2.1_linux_x86_64.tgz +SIGIL_URL ?= https://github.com/gliderlabs/sigil/releases/download/v0.3.3/sigil_0.3.3_Linux_x86_64.tgz STACK_URL ?= https://github.com/gliderlabs/herokuish.git PREBUILT_STACK_URL ?= gliderlabs/herokuish:latest DOKKU_LIB_ROOT ?= /var/lib/dokku @@ -77,7 +78,7 @@ plugin-dependencies: plugn plugins: plugn docker sudo -E dokku plugin:install --core -dependencies: apt-update sshcommand plugn docker help2man man-db +dependencies: apt-update sshcommand plugn docker help2man man-db sigil $(MAKE) -e stack apt-update: @@ -98,6 +99,10 @@ plugn: wget -qO /tmp/plugn_latest.tgz ${PLUGN_URL} tar xzf /tmp/plugn_latest.tgz -C /usr/local/bin +sigil: + wget -qO /tmp/sigil_latest.tgz ${SIGIL_URL} + tar xzf /tmp/sigil_latest.tgz -C /usr/local/bin + docker: aufs apt-get install -qq -y curl egrep -i "^docker" /etc/group || groupadd docker diff --git a/deb.mk b/deb.mk index b93af50912b..2ccf58ffdd1 100644 --- a/deb.mk +++ b/deb.mk @@ -20,34 +20,52 @@ SSHCOMMAND_VERSION ?= 0.1.0 SSHCOMMAND_ARCHITECTURE = amd64 SSHCOMMAND_PACKAGE_NAME = sshcommand_$(SSHCOMMAND_VERSION)_$(SSHCOMMAND_ARCHITECTURE).deb -GOROOT = /usr/lib/go -GOBIN = /usr/bin/go +SIGIL_DESCRIPTION = 'Standalone string interpolator and template processor' +SIGIL_REPO_NAME ?= gliderlabs/sigil +SIGIL_VERSION ?= 0.3.3 +SIGIL_ARCHITECTURE = amd64 +SIGIL_PACKAGE_NAME = sigil_$(SIGIL_VERSION)_$(SIGIL_ARCHITECTURE).deb + +GOROOT = /usr/local/go +GOBIN = /usr/local/go/bin/go GOPATH = /home/vagrant/gocode +GOTARBALL = go1.5.3.linux-amd64.tar.gz -.PHONY: install-from-deb deb-all deb-herokuish deb-dokku deb-plugn deb-setup deb-sshcommand +.PHONY: install-from-deb deb-all deb-herokuish deb-dokku deb-plugn deb-setup deb-sshcommand deb-sigil install-from-deb: - echo "--> Initial apt-get update" + @echo "--> Initial apt-get update" sudo apt-get update -qq > /dev/null sudo apt-get install -qq -y apt-transport-https - echo "--> Installing docker" + @echo "--> Installing docker" wget -nv -O - https://get.docker.com/ | sh - echo "--> Installing dokku" + @echo "--> Installing dokku" wget -nv -O - https://packagecloud.io/gpg.key | apt-key add - - echo "deb https://packagecloud.io/dokku/dokku/ubuntu/ trusty main" | sudo tee /etc/apt/sources.list.d/dokku.list + @echo "deb https://packagecloud.io/dokku/dokku/ubuntu/ trusty main" | sudo tee /etc/apt/sources.list.d/dokku.list sudo apt-get update -qq > /dev/null - sudo apt-get install dokku + sudo DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get install -yy dokku -deb-all: deb-herokuish deb-dokku deb-plugn deb-sshcommand +golang: +ifeq (,$(wildcard /tmp/$(GOTARBALL))) + @echo "-> Installing golang" + mkdir -p /tmp/build/usr/local/bin $(GOPATH) + sudo apt-get clean + sudo apt-get update -qq > /dev/null + sudo apt-get install -qq -y git mercurial > /dev/null 2>&1 + sudo wget -nv -O /tmp/$(GOTARBALL) https://storage.googleapis.com/golang/$(GOTARBALL) + sudo tar -C /usr/local -xzf /tmp/$(GOTARBALL) +endif + +deb-all: deb-herokuish deb-dokku deb-plugn deb-sshcommand deb-sigil mv /tmp/*.deb . - echo "Done" + @echo "Done" deb-setup: - echo "-> Updating deb repository and installing build requirements" + @echo "-> Updating deb repository and installing build requirements" sudo apt-get update -qq > /dev/null - sudo apt-get install -qq -y gcc git ruby-dev ruby1.9.1 > /dev/null 2>&1 + sudo DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get install -qq -y gcc git ruby-dev ruby1.9.1 > /dev/null 2>&1 command -v fpm > /dev/null || sudo gem install fpm --no-ri --no-rdoc ssh -o StrictHostKeyChecking=no git@github.com || true @@ -55,26 +73,26 @@ deb-herokuish: deb-setup rm -rf /tmp/tmp /tmp/build $(HEROKUISH_PACKAGE_NAME) mkdir -p /tmp/tmp /tmp/build - echo "-> Creating deb files" - echo "#!/usr/bin/env bash" >> /tmp/tmp/post-install - echo "sleep 5" >> /tmp/tmp/post-install - echo "count=\`sudo docker images | grep gliderlabs/herokuish | wc -l\`" >> /tmp/tmp/post-install - echo 'if [ "$$count" -ne 0 ]; then' >> /tmp/tmp/post-install - echo " echo 'Removing old herokuish image'" >> /tmp/tmp/post-install - echo " sudo docker rmi gliderlabs/herokuish" >> /tmp/tmp/post-install - echo "fi" >> /tmp/tmp/post-install - echo "echo 'Importing herokuish into docker (around 5 minutes)'" >> /tmp/tmp/post-install - echo "sudo docker build -t gliderlabs/herokuish /var/lib/herokuish 1> /dev/null" >> /tmp/tmp/post-install - - echo "-> Cloning repository" + @echo "-> Creating deb files" + @echo "#!/usr/bin/env bash" >> /tmp/tmp/post-install + @echo "sleep 5" >> /tmp/tmp/post-install + @echo "count=\`sudo docker images | grep gliderlabs/herokuish | wc -l\`" >> /tmp/tmp/post-install + @echo 'if [ "$$count" -ne 0 ]; then' >> /tmp/tmp/post-install + @echo " echo 'Removing old herokuish image'" >> /tmp/tmp/post-install + @echo " sudo docker rmi gliderlabs/herokuish" >> /tmp/tmp/post-install + @echo "fi" >> /tmp/tmp/post-install + @echo "echo 'Importing herokuish into docker (around 5 minutes)'" >> /tmp/tmp/post-install + @echo "sudo docker build -t gliderlabs/herokuish /var/lib/herokuish 1> /dev/null" >> /tmp/tmp/post-install + + @echo "-> Cloning repository" git clone -q "https://github.com/$(HEROKUISH_REPO_NAME).git" /tmp/tmp/herokuish > /dev/null rm -rf /tmp/tmp/herokuish/.git /tmp/tmp/herokuish/.gitignore - echo "-> Copying files into place" + @echo "-> Copying files into place" mkdir -p "/tmp/build/var/lib" cp -rf /tmp/tmp/herokuish /tmp/build/var/lib/herokuish - echo "-> Creating $(HEROKUISH_PACKAGE_NAME)" + @echo "-> Creating $(HEROKUISH_PACKAGE_NAME)" sudo fpm -t deb -s dir -C /tmp/build -n herokuish -v $(HEROKUISH_VERSION) -a $(HEROKUISH_ARCHITECTURE) -p $(HEROKUISH_PACKAGE_NAME) --deb-pre-depends 'docker-engine-cs | docker-engine | lxc-docker (>= 1.6.1) | docker.io (>= 1.6.1) | tutum-agent' --deb-pre-depends sudo --after-install /tmp/tmp/post-install --url "https://github.com/$(HEROKUISH_REPO_NAME)" --description $(HEROKUISH_DESCRIPTION) --license 'MIT License' . mv *.deb /tmp @@ -105,41 +123,55 @@ deb-dokku: deb-setup dpkg-deb --build /tmp/build "/vagrant/dokku_`cat /tmp/build/var/lib/dokku/STABLE_VERSION`_$(DOKKU_ARCHITECTURE).deb" mv *.deb /tmp -deb-plugn: deb-setup +deb-plugn: deb-setup golang rm -rf /tmp/tmp /tmp/build $(PLUGN_PACKAGE_NAME) - mkdir -p /tmp/tmp /tmp/build + mkdir -p /tmp/tmp /tmp/build /tmp/build/usr/local/bin - echo "-> Cloning repository" + @echo "-> Cloning repository" git clone -q "https://github.com/$(PLUGN_REPO_NAME).git" /tmp/tmp/plugn > /dev/null rm -rf /tmp/tmp/plugn/.git /tmp/tmp/plugn/.gitignore - echo "-> Copying files into place" - mkdir -p /tmp/build/usr/local/bin $(GOPATH) - sudo apt-get clean - sudo apt-get update -qq > /dev/null - sudo apt-get install -qq -y git golang mercurial > /dev/null 2>&1 + @echo "-> Copying files into place" export PATH=$(PATH):$(GOROOT)/bin:$(GOPATH)/bin && export GOROOT=$(GOROOT) && export GOPATH=$(GOPATH):/tmp/tmp/plugn && go get github.com/dokku/plugn export PATH=$(PATH):$(GOROOT)/bin:$(GOPATH)/bin && export GOROOT=$(GOROOT) && export GOPATH=$(GOPATH):/tmp/tmp/plugn && cd /home/vagrant/gocode/src/github.com/dokku/plugn && make deps - export PATH=$(PATH):$(GOROOT)/bin:$(GOPATH)/bin && export GOROOT=$(GOROOT) && export GOPATH=$(GOPATH):/tmp/tmp/plugn && cd /home/vagrant/gocode/src/github.com/dokku/plugn && rm plugn && go build -o plugn + export PATH=$(PATH):$(GOROOT)/bin:$(GOPATH)/bin && export GOROOT=$(GOROOT) && export GOPATH=$(GOPATH):/tmp/tmp/plugn && cd /home/vagrant/gocode/src/github.com/dokku/plugn && rm -f plugn && go build -o plugn mv /home/vagrant/gocode/src/github.com/dokku/plugn/plugn /tmp/build/usr/local/bin/plugn - echo "-> Creating $(PLUGN_PACKAGE_NAME)" + @echo "-> Creating $(PLUGN_PACKAGE_NAME)" sudo fpm -t deb -s dir -C /tmp/build -n plugn -v $(PLUGN_VERSION) -a $(PLUGN_ARCHITECTURE) -p $(PLUGN_PACKAGE_NAME) --url "https://github.com/$(PLUGN_REPO_NAME)" --description $(PLUGN_DESCRIPTION) --license 'MIT License' . mv *.deb /tmp deb-sshcommand: deb-setup rm -rf /tmp/tmp /tmp/build $(SSHCOMMAND_PACKAGE_NAME) - mkdir -p /tmp/tmp /tmp/build + mkdir -p /tmp/tmp /tmp/build /tmp/build/usr/local/bin - echo "-> Cloning repository" + @echo "-> Cloning repository" git clone -q "https://github.com/$(SSHCOMMAND_REPO_NAME).git" /tmp/tmp/sshcommand > /dev/null rm -rf /tmp/tmp/sshcommand/.git /tmp/tmp/sshcommand/.gitignore - echo "-> Copying files into place" + @echo "-> Copying files into place" mkdir -p "/tmp/build/usr/local/bin" cp /tmp/tmp/sshcommand/sshcommand /tmp/build/usr/local/bin/sshcommand chmod +x /tmp/build/usr/local/bin/sshcommand - echo "-> Creating $(SSHCOMMAND_PACKAGE_NAME)" + @echo "-> Creating $(SSHCOMMAND_PACKAGE_NAME)" sudo fpm -t deb -s dir -C /tmp/build -n sshcommand -v $(SSHCOMMAND_VERSION) -a $(SSHCOMMAND_ARCHITECTURE) -p $(SSHCOMMAND_PACKAGE_NAME) --url "https://github.com/$(SSHCOMMAND_REPO_NAME)" --description $(SSHCOMMAND_DESCRIPTION) --license 'MIT License' . mv *.deb /tmp + +deb-sigil: deb-setup golang + rm -rf /tmp/tmp /tmp/build $(SIGIL_PACKAGE_NAME) + mkdir -p /tmp/tmp /tmp/build /tmp/build/usr/local/bin + + @echo "-> Cloning repository" + git clone -q "https://github.com/$(SIGIL_REPO_NAME).git" /tmp/tmp/sigil > /dev/null + rm -rf /tmp/tmp/sigil/.git /tmp/tmp/sigil/.gitignore + + @echo "-> Copying files into place" + export PATH=$(PATH):$(GOROOT)/bin:$(GOPATH)/bin && export GOROOT=$(GOROOT) && export GOPATH=$(GOPATH):/tmp/tmp/sigil && go get github.com/gliderlabs/sigil + export PATH=$(PATH):$(GOROOT)/bin:$(GOPATH)/bin && export GOROOT=$(GOROOT) && export GOPATH=$(GOPATH):/tmp/tmp/sigil && cd /home/vagrant/gocode/src/github.com/gliderlabs/sigil && make deps + export PATH=$(PATH):$(GOROOT)/bin:$(GOPATH)/bin && export GOROOT=$(GOROOT) && export GOPATH=$(GOPATH):/tmp/tmp/sigil && cd /home/vagrant/gocode/src/github.com/gliderlabs/sigil && rm -f sigil && go build -o sigil + mv /home/vagrant/gocode/src/github.com/gliderlabs/sigil/sigil /tmp/build/usr/local/bin/sigil + + @echo "-> Creating $(SIGIL_PACKAGE_NAME)" + sudo fpm -t deb -s dir -C /tmp/build -n sigil -v $(SIGIL_VERSION) -a $(SIGIL_ARCHITECTURE) -p $(SIGIL_PACKAGE_NAME) --url "https://github.com/$(SIGIL_REPO_NAME)" --description $(SIGIL_DESCRIPTION) --license 'MIT License' . + mv *.deb /tmp diff --git a/docs/appendices/0.5.0-migration-guide.md b/docs/appendices/0.5.0-migration-guide.md new file mode 100644 index 00000000000..3719582bb42 --- /dev/null +++ b/docs/appendices/0.5.0-migration-guide.md @@ -0,0 +1,20 @@ +# 0.5.0 Migration Guide +## nginx-vhosts +- The nginx-vhosts template language is now [sigil](https://github.com/gliderlabs/sigil) + - No need to escape literal `$` characters (or other "bash-isms") + - Template variables are represented as {{ .VARIABLE_NAME }} + - A detailed list of template variables can be found [here](/dokku/nginx#available-template-variables) +- A custom nginx-vhosts template must be named `nginx.conf.sigil` + - The default path for this custom template is the root of your repo (i.e. `/app` in the container or `WORKDIR` if defined in a dockerfile app) + - Dokku no longer looks for this file in `/home/dokku/myapp` on the dokku server + - Check out an example template [here](/dokku/nginx/) +- Support for server-wide SSL certs have been dropped in favor of using the `certs` plugin + - `dokku certs:add myapp < certs.tar` +- All domains for an SSL-enabled app will be redirected to https by default + - This can be overridden with a custom template +- Replaced "magic" `NO_VHOST` variable with `domains:enable/disable` +- Simplified zero downtime control + - `checks:enable/disable` +## Dockerfile apps with multiple ports +- Dockerfiles with multiple `EXPOSE` clauses will get all **tcp** ports proxied by default (nginx does not support proxying udp) + - UDP can be exposed by disabling the nginx proxy with `dokku proxy:disable myapp` diff --git a/docs/checks-examples.md b/docs/checks-examples.md index dbd4af5dae4..ab675965c4b 100644 --- a/docs/checks-examples.md +++ b/docs/checks-examples.md @@ -1,6 +1,10 @@ # Zero Downtime Deploys -> New as of 0.3.0 +``` +checks Show zero-downtime status +checks:disable Disable zero-downtime checks +checks:enable Enable zero-downtime checks +``` Following a deploy, dokku will now wait `10` seconds before routing traffic to the new container. If the container is not running after this time, then the deploy is failed and your old container will continue serving traffic. You can modify this value using the following command: @@ -38,14 +42,10 @@ Dokku will retry the checks `5` times until the checks are successful. If all 5 dokku config:set --global DOKKU_CHECKS_ATTEMPTS=2 ``` -You can also choose to skip checks completely either globally or on a per-application basis: +You can also choose to skip checks completely on a per-application basis: ```shell -# globally -dokku config:set --global DOKKU_SKIP_ALL_CHECKS=true - -# for APP -dokku config:set APP DOKKU_SKIP_ALL_CHECKS=true +dokku checks:disable APP ``` ## Checks Examples diff --git a/docs/deployment/ssl-configuration.md b/docs/deployment/ssl-configuration.md index 6eda4e4537b..0b646952b0a 100644 --- a/docs/deployment/ssl-configuration.md +++ b/docs/deployment/ssl-configuration.md @@ -101,14 +101,40 @@ Beware that if you enable the header and a subsequent deploy of your application ## Running behind a load balancer -> New as of 0.3.17 - Your application has access to the HTTP headers `X-Forwarded-Proto`, `X-Forwarded-For` and `X-Forwarded-Port`. These headers indicate the protocol of the original request (HTTP or HTTPS), the port number, and the IP address of the client making the request, respectively. The default configuration is for Nginx to set these headers. -If your server runs behind an HTTP/S load balancer, then Nginx will see all requests as coming from the load balancer. If your load balancer sets the `X-Forwarded-` headers, you can tell Nginx to pass these headers from load balancer to your application by setting the `DOKKU_SSL_TERMINATED` environment variable: +If your server runs behind an HTTP/S load balancer, then Nginx will see all requests as coming from the load balancer. If your load balancer sets the `X-Forwarded-` headers, you can tell Nginx to pass these headers from load balancer to your application by using the following [nginx custom template](/dokku/nginx/#customizing-the-nginx-configuration) ```shell -dokku config:set myapp DOKKU_SSL_TERMINATED=1 +server { + listen [::]:{{ .NGINX_PORT }}; + listen {{ .NGINX_PORT }}; + server_name {{ .NOSSL_SERVER_NAME }}; + access_log /var/log/nginx/{{ .APP }}-access.log; + error_log /var/log/nginx/{{ .APP }}-error.log; + + # set a custom header for requests + add_header X-Served-By www-ec2-01; + + location / { + proxy_pass http://{{ .APP }}; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + proxy_set_header X-Forwarded-For $http_x_forwarded_for; + proxy_set_header X-Forwarded-Port $http_x_forwarded_port; + proxy_set_header X-Request-Start $msec; + } + include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; + + upstream {{ .APP }} { + {{ range .DOKKU_APP_LISTENERS | split " " }} + server {{ . }}; + {{ end }} + } +} ``` Only use this option if: diff --git a/docs/nginx.md b/docs/nginx.md index 2f1ed78abbf..eb480927188 100644 --- a/docs/nginx.md +++ b/docs/nginx.md @@ -3,70 +3,74 @@ Dokku uses nginx as its server for routing requests to specific applications. By default, access and error logs are written for each app to `/var/log/nginx/${APP}-access.log` and `/var/log/nginx/${APP}-error.log` respectively ``` -nginx:access-logs [-t] Show the nginx access logs for an application (-t follows) -nginx:build-config (Re)builds nginx config for given app -nginx:disable Disable nginx for an application (forces container binding to external interface) -nginx:enable Enable nginx for an application -nginx:error-logs [-t] Show the nginx error logs for an application (-t follows) +nginx:access-logs [-t] Show the nginx access logs for an application (-t follows) +nginx:build-config (Re)builds nginx config for given app +nginx:error-logs [-t] Show the nginx error logs for an application (-t follows) ``` ## Customizing the nginx configuration -> New as of 0.3.10. +Dokku uses a templating library by the name of [sigil](https://github.com/gliderlabs/sigil) to generate nginx configuration for each app. If you'd like to provide a custom template for your application, there are a couple options: -Dokku currently templates out an nginx configuration that is included in the `nginx-vhosts` plugin. If you'd like to provide a custom template for your application, there are a few options: - -- Copy the existing pertinent (ssl or non-ssl) template found (by default) in `/var/lib/dokku/plugins/available/nginx-vhosts/templates`, place it either in `/home/dokku/APP` or check it into the root of your app repo, and name it one of the following: - - `nginx.conf.template` (since 0.3.10) - - `nginx.conf.ssl_terminated.template` (since 0.4.0) - - `nginx.ssl.conf.template` (since 0.4.2) - -> If placed on the dokku server, the template file **must** be owned by user and group `dokku:dokku`. - -For instance - assuming defaults - to customize the nginx template in use for the `myapp` application, create the file `nginx.conf.template` in your repo or on disk with the with the following contents: +- Copy the following example template to a file named `nginx.conf.sigil` and either: + - check it into the root of your app repo + - `ADD` it to your dockerfile `WORKDIR` +### Example Custom Template ``` server { - listen [::]:80; - listen 80; - server_name $NOSSL_SERVER_NAME; - access_log /var/log/nginx/${APP}-access.log; - error_log /var/log/nginx/${APP}-error.log; + listen [::]:{{ .NGINX_PORT }}; + listen {{ .NGINX_PORT }}; + server_name {{ .NOSSL_SERVER_NAME }}; + access_log /var/log/nginx/{{ .APP }}-access.log; + error_log /var/log/nginx/{{ .APP }}-error.log; # set a custom header for requests add_header X-Served-By www-ec2-01; location / { - proxy_pass http://$APP; + proxy_pass http://{{ .APP }}; proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; - proxy_set_header Host \$http_host; - proxy_set_header X-Forwarded-Proto \$scheme; - proxy_set_header X-Forwarded-For \$remote_addr; - proxy_set_header X-Forwarded-Port \$server_port; - proxy_set_header X-Request-Start \$msec; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Request-Start $msec; + } + include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; + + upstream {{ .APP }} { + {{ range .DOKKU_APP_LISTENERS | split " " }} + server {{ . }}; + {{ end }} } - include $DOKKU_ROOT/$APP/nginx.conf.d/*.conf; } ``` The above is a sample http configuration that adds an `X-Served-By` header to requests. -A few tips for custom nginx templates: - -- Special characters - dollar signs, spaces inside of quotes, etc. - should be escaped with a single backslash or can cause deploy failures. -- Templated files will be validated via `nginx -t`. -- Application environment variables can be used within your nginx configuration. - -After your changes a `dokku deploy myapp` will regenerate the `/home/dokku/myapp/nginx.conf` file which is then used. +### Available template variables +``` +{{ .APP }} Application name +{{ .APP_SSL_PATH }} Path to SSL certificate and key +{{ .DOKKU_ROOT }} Global dokku root directory (ex: app dir would be `{{ .DOKKU_ROOT }}/{{ .APP }}`) +{{ .DOKKU_APP_LISTENERS }} List of IP:PORT pairs of app containers +{{ .NGINX_PORT }} Non-SSL nginx listener port (same as `DOKKU_NGINX_PORT` config var) +{{ .NGINX_SSL_PORT }} SSL nginx listener port (same as `DOKKU_NGINX_SSL_PORT` config var) +{{ .NOSSL_SERVER_NAME }} List of non-SSL VHOSTS +{{ .RAW_TCP_PORTS }} List of exposed tcp ports as defined by Dockerfile `EXPOSE` directive (**Dockerfile apps only**) +{{ .SSL_INUSE }} Boolean set when an app is SSL-enabled +{{ .SSL_SERVER_NAME }} List of SSL VHOSTS +``` ### Customizing via configuration files included by the default templates -The default nginx.conf- templates will include everything from your apps `nginx.conf.d/` subdirectory in the main `server {}` block (see above): +The default nginx.conf template will include everything from your apps `nginx.conf.d/` subdirectory in the main `server {}` block (see above): ``` -include $DOKKU_ROOT/$APP/nginx.conf.d/*.conf; +include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; ``` That means you can put additional configuration in separate files, for example to limit the uploaded body size to 50 megabytes, do @@ -86,7 +90,7 @@ Applications typically have the following structure for their hostname: scheme://subdomain.domain.tld ``` -The `subdomain` is inferred from the pushed application name, while the `domain` is set during initial configuration in the `$DOKKU_ROOT/VHOST` file. +The `subdomain` is inferred from the pushed application name, while the `domain` is set during initial configuration in the `$DOKKU_ROOT/VHOST` file or via `dokku domains:set-global`. You can optionally override this in a plugin by implementing the `nginx-hostname` plugin trigger. For example, you can reverse the subdomain with the following sample `nginx-hostname` plugin trigger: @@ -104,30 +108,32 @@ If the `nginx-hostname` has no output, the normal hostname algorithm will be exe You can also use the built-in `domains` plugin to handle: -### Disabling VHOSTS +### Domains plugin -If desired, it is possible to disable vhosts by setting the environment variable `NO_VHOST=1`: +> New as of 0.3.10 ```shell -dokku config:set myapp NO_VHOST=1 +domains:add DOMAIN Add a domain to app +domains [] List domains +domains:clear Clear all domains for app +domains:disable Disable VHOST support +domains:enable Enable VHOST support +domains:remove DOMAIN Remove a domain from app +domains:set-global Set global domain name ``` -On subsequent deploys, the nginx virtualhost will be discarded. This is useful when deploying internal-facing services that should not be publicly routeable. As of 0.4.0, nginx will still be configured to proxy your app on some random high port. This allows internal services to maintain the same port between deployments. You may change this port by setting `DOKKU_NGINX_PORT` and/or `DOKKU_NGINX_SSL_PORT` (for services configured to use SSL.) - -### Domains plugin +### Disabling VHOSTS -> New as of 0.3.10 +If desired, it is possible to disable vhosts with the domains plugin. ```shell -domains:add DOMAIN Add a custom domain to app -domains List custom domains for app -domains:clear Clear all custom domains for app -domains:remove DOMAIN Remove a custom domain from app +dokku domains:disable myapp ``` -The domains plugin allows you to specify custom domains for applications. This plugin is aware of any ssl certificates that are imported via `nginx:import-ssl`. Be aware that setting `NO_VHOST` will override any custom domains. +On subsequent deploys, the nginx virtualhost will be discarded. This is useful when deploying internal-facing services that should not be publicly routeable. As of 0.4.0, nginx will still be configured to proxy your app on some random high port. This allows internal services to maintain the same port between deployments. You may change this port by setting `DOKKU_NGINX_PORT` and/or `DOKKU_NGINX_SSL_PORT` (for services configured to use SSL.) + -Custom domains are also backed up via the built-in `backup` plugin +The domains plugin allows you to specify custom domains for applications. This plugin is aware of any ssl certificates that are imported via `certs:add`. Be aware that disabling domains (with `domains:disable`) will override any custom domains. ```shell # where `myapp` is the name of your app @@ -145,26 +151,6 @@ dokku domains:clear myapp dokku domains:remove myapp example.com ``` -## Container network interface binding - -> New as of 0.3.13 - -The deployed docker container running your app's web process will bind to either the internal docker network interface (i.e. `docker inspect --format '{{ .NetworkSettings.IPAddress }}' $CONTAINER_ID`) or an external interface (i.e. 0.0.0.0) depending on dokku's VHOST configuration. Dokku will attempt to bind to the internal docker network interface unless you specifically set NO_VHOST for the given app or your dokku installation is not setup to use VHOSTS (i.e. $DOKKU_ROOT/VHOST is missing or $DOKKU_ROOT/HOSTNAME is set to an IPv4 or IPv6 address) - -```shell -# container bound to docker interface -root@dokku:~/dokku# docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -1b88d8aec3d1 dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute goofy_albattani - -root@dokku:~/dokku# docker inspect --format '{{ .NetworkSettings.IPAddress }}' goofy_albattani -172.17.0.6 - -# container bound to all interfaces (previous default) -root@dokku:/home/dokku# docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -d6499edb0edb dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute 0.0.0.0:49153->5000/tcp nostalgic_tesla -``` ## Default site diff --git a/docs/proxy.md b/docs/proxy.md new file mode 100644 index 00000000000..646a860ea40 --- /dev/null +++ b/docs/proxy.md @@ -0,0 +1,28 @@ +# Proxy plugin + +As of dokku 0.5.0, the proxy functionality has been decoupled from the nginx-vhosts plugin into the proxy plugin. In the future this will allow other proxy software (HAproxy for example) to be used instead of nginx. + +``` +proxy:disable Disable proxy for app +proxy:enable Enable proxy for app +proxy:set NOT IMPLEMENTED YET!! +``` + +## Container network interface binding + +By default, the deployed docker container running your app's web process will bind to the internal docker network interface (i.e. `docker inspect --format '{{ .NetworkSettings.IPAddress }}' $CONTAINER_ID`). This behavior can be modified per app by disabling the proxy (i.e. `dokku proxy:disable `). In this case, the container will bind to an external interface (i.e. 0.0.0.0) and your app container will be directly accessible by other hosts on your network. + +```shell +# container bound to docker interface +root@dokku:~/dokku# docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +1b88d8aec3d1 dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute node-js-app.web.1 + +root@dokku:~/dokku# docker inspect --format '{{ .NetworkSettings.IPAddress }}' node-js-app.web.1 +172.17.0.6 + +# container bound to all interfaces +root@dokku:/home/dokku# docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +d6499edb0edb dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute 0.0.0.0:49153->5000/tcp node-js-app.web.1 +``` diff --git a/dokku b/dokku index f69e4c60ffc..19720a56d4a 100755 --- a/dokku +++ b/dokku @@ -33,6 +33,8 @@ export DOKKU_CONTAINER_LABEL=dokku export DOKKU_GLOBAL_RUN_ARGS="--label=$DOKKU_CONTAINER_LABEL" source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/checks/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/proxy/functions" [[ -f $DOKKU_ROOT/dokkurc ]] && source $DOKKU_ROOT/dokkurc [[ -d $DOKKU_ROOT/.dokkurc ]] && for f in $DOKKU_ROOT/.dokkurc/*; do source $f; done @@ -104,15 +106,8 @@ case "$1" in DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" oldids=$(get_app_container_ids $APP) - DOKKU_APP_SKIP_ALL_CHECKS=$(dokku config:get $APP DOKKU_SKIP_ALL_CHECKS || true) - DOKKU_APP_SKIP_DEFAULT_CHECKS=$(dokku config:get $APP DOKKU_SKIP_DEFAULT_CHECKS || true) - DOKKU_GLOBAL_SKIP_ALL_CHECKS=$(dokku config:get --global DOKKU_SKIP_ALL_CHECKS || true) - DOKKU_GLOBAL_SKIP_DEFAULT_CHECKS=$(dokku config:get --global DOKKU_SKIP_DEFAULT_CHECKS || true) - - DOKKU_SKIP_ALL_CHECKS=${DOKKU_APP_SKIP_ALL_CHECKS:="$DOKKU_GLOBAL_SKIP_ALL_CHECKS"} - DOKKU_SKIP_DEFAULT_CHECKS=${DOKKU_APP_SKIP_DEFAULT_CHECKS:="$DOKKU_GLOBAL_SKIP_DEFAULT_CHECKS"} - DOKKU_DEFAULT_DOCKER_ARGS=$(: | plugn trigger docker-args-deploy $APP $IMAGE_TAG) + DOKKU_IS_APP_PROXY_ENABLED="$(is_app_proxy_enabled $APP)" while read -r line || [[ -n "$line" ]]; do [[ "$line" =~ ^#.* ]] && continue @@ -131,11 +126,11 @@ case "$1" in DOCKER_ARGS="$DOKKU_DEFAULT_DOCKER_ARGS" DOCKER_ARGS+=" -e DYNO='$PROC_TYPE.$CONTAINER_INDEX' " [[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" -e TRACE=true " - BIND_EXTERNAL=$(plugn trigger bind-external-ip $APP) [[ -n "$DOKKU_HEROKUISH" ]] && START_CMD="/start $PROC_TYPE" if [[ -z "$DOKKU_HEROKUISH" ]]; then + DOKKU_DOCKERFILE_PORTS=($(dokku config:get $APP DOKKU_DOCKERFILE_PORTS || true)) DOKKU_DOCKERFILE_PORT=$(dokku config:get $APP DOKKU_DOCKERFILE_PORT || true) DOKKU_DOCKERFILE_START_CMD=$(dokku config:get $APP DOKKU_DOCKERFILE_START_CMD || true) DOKKU_PROCFILE_START_CMD=$(get_cmd_from_procfile $APP $PROC_TYPE) @@ -143,18 +138,30 @@ case "$1" in fi if [[ "$PROC_TYPE" == "web" ]]; then - port=${DOKKU_DOCKERFILE_PORT:=5000} - if [[ "$BIND_EXTERNAL" = "true" ]]; then - id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -p $port -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) - port=$(docker port $id $port | sed 's/[0-9.]*://') - ipaddr=127.0.0.1 + if [[ -z "${DOKKU_DOCKERFILE_PORTS[*]}" ]]; then + port=5000 + DOKKU_DOCKER_PORT_ARGS+="-p $port" else + for p in ${DOKKU_DOCKERFILE_PORTS[*]};do + if [[ ! "$p" =~ .*udp.* ]]; then + # set port to first non-udp port + p=${p//\/tcp} + port=${port:="$p"} + fi + DOKKU_DOCKER_PORT_ARGS+=" -p $p " + done + fi + if [[ "$DOKKU_IS_APP_PROXY_ENABLED" = "true" ]]; then id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) ipaddr=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $id) # Docker < 1.9 compatibility if [[ -z $ipaddr ]]; then ipaddr=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $id) fi + else + id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) + port=$(docker port $id $port | sed 's/[0-9.]*://') + ipaddr=127.0.0.1 fi else id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOCKER_ARGS $IMAGE $START_CMD) @@ -168,8 +175,8 @@ case "$1" in } # run checks first, then post-deploy hooks, which switches Nginx traffic - if [[ "$DOKKU_SKIP_ALL_CHECKS" = "true" ]]; then - dokku_log_info1 "Skipping pre-flight checks" + if [[ "$(is_app_checks_enabled $APP)" == "false" ]]; then + dokku_log_info1 "zero downtime is disabled for app ($APP). skipping pre-flight checks" else trap kill_new INT TERM EXIT dokku_log_info1 "Running pre-flight checks" @@ -181,7 +188,6 @@ case "$1" in [[ -n "$id" ]] && echo $id > "$DOKKU_CONTAINER_ID_FILE" [[ -n "$ipaddr" ]] && echo $ipaddr > "$DOKKU_IP_FILE" [[ -n "$port" ]] && echo $port > "$DOKKU_PORT_FILE" - [[ -n "$port" ]] && echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$port" > "$DOKKU_ROOT/$APP/URL" # cleanup pre-migration files rm -f $DOKKU_ROOT/$APP/CONTAINER $DOKKU_ROOT/$APP/IP $DOKKU_ROOT/$APP/PORT diff --git a/plugins/00_dokku-standard/commands b/plugins/00_dokku-standard/commands index b72507d7bc5..b57be413235 100755 --- a/plugins/00_dokku-standard/commands +++ b/plugins/00_dokku-standard/commands @@ -4,6 +4,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/proxy/functions" case "$1" in build) @@ -35,8 +36,8 @@ case "$1" in dockerfile) # extract first port from Dockerfile - DOCKERFILE_PORT=$(get_dockerfile_exposed_port Dockerfile) - [[ -n "$DOCKERFILE_PORT" ]] && config_set --no-restart $APP DOKKU_DOCKERFILE_PORT=$DOCKERFILE_PORT + DOCKERFILE_PORTS=$(get_dockerfile_exposed_ports Dockerfile) + [[ -n "$DOCKERFILE_PORTS" ]] && config_set --no-restart $APP DOKKU_DOCKERFILE_PORTS="$DOCKERFILE_PORTS" plugn trigger pre-build-dockerfile "$APP" [[ "$DOKKU_DOCKERFILE_CACHE_BUILD" == "false" ]] && DOKKU_DOCKER_BUILD_OPTS="$DOKKU_DOCKER_BUILD_OPTS --no-cache" @@ -151,46 +152,7 @@ case "$1" in url | urls) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - APP="$2"; verify_app_name "$2" - eval "$(config_export app $APP)" - - if [[ -s "$DOKKU_ROOT/$APP/URLS" ]]; then - case "$1" in - url) - grep "^http" "$DOKKU_ROOT/$APP/URLS" | head -1 - ;; - urls) - grep "^http" "$DOKKU_ROOT/$APP/URLS" - ;; - esac - - exit 0 - fi - - SCHEME="http"; SSL="$DOKKU_ROOT/$APP/tls"; WILDCARD_SSL="$DOKKU_ROOT/tls" - if [[ -e "$SSL/server.crt" && -e "$SSL/server.key" ]] || [[ -e "$WILDCARD_SSL/server.crt" && -e "$WILDCARD_SSL/server.key" ]]; then - SCHEME="https" - fi - - if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then - shopt -s nullglob - for PORT_FILE in $DOKKU_ROOT/$APP/PORT.*; do - echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$(< "$PORT_FILE") (container)" - done - shopt -u nullglob - - DOKKU_NGINX_PORT=$(config_get $APP DOKKU_NGINX_PORT || true) - DOKKU_NGINX_SSL_PORT=$(config_get $APP DOKKU_NGINX_SSL_PORT || true) - - if [[ -n "$DOKKU_NGINX_PORT" ]]; then - echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$DOKKU_NGINX_PORT (nginx)" - fi - if [[ -n "$DOKKU_NGINX_SSL_PORT" ]]; then - echo "https://$(< "$DOKKU_ROOT/HOSTNAME"):$DOKKU_NGINX_SSL_PORT (nginx-ssl)" - fi - else - echo "$SCHEME://$(< "$DOKKU_ROOT/VHOST")" - fi + get_app_urls "$@" ;; report) @@ -201,6 +163,7 @@ case "$1" in docker version dokku_log_info1 "docker daemon info: " docker -D info + dokku_log_info1 "sigil version: $(sigil -v)" dokku_log_info1 "herokuish version: " docker run --rm -ti gliderlabs/herokuish:latest herokuish version dokku_log_info1 "dokku version: $(dokku version)" diff --git a/plugins/00_dokku-standard/install b/plugins/00_dokku-standard/install index 636145dfa69..9bbf574e2db 100755 --- a/plugins/00_dokku-standard/install +++ b/plugins/00_dokku-standard/install @@ -57,3 +57,5 @@ if [ ! -f "/home/dokku/HOSTNAME" ]; then fi EOF chmod +x /etc/update-motd.d/99-dokku + +chown dokku:dokku "$DOKKU_ROOT/HOSTNAME" "$DOKKU_ROOT/VHOST" diff --git a/plugins/00_dokku-standard/plugin.toml b/plugins/00_dokku-standard/plugin.toml index a11d892217b..1badd98f049 100644 --- a/plugins/00_dokku-standard/plugin.toml +++ b/plugins/00_dokku-standard/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core standard plugin" -version = "0.4.0" +version = "0.5.0" [plugin.config] diff --git a/plugins/certs/functions b/plugins/certs/functions index 1e852846b10..e4bc012c42a 100755 --- a/plugins/certs/functions +++ b/plugins/certs/functions @@ -7,13 +7,8 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" is_ssl_enabled() { local APP=$1; verify_app_name $APP APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" - WILDCARD_SSL_PATH="$DOKKU_ROOT/tls" if [[ -e "$APP_SSL_PATH/server.crt" ]] && [[ -e "$APP_SSL_PATH/server.key" ]]; then - echo app - return 0 - elif [[ -e "$WILDCARD_SSL_PATH/server.crt" ]] && [[ -e "$WILDCARD_SSL_PATH/server.key" ]]; then - echo global return 0 else return 1 @@ -22,22 +17,14 @@ is_ssl_enabled() { get_ssl_hostnames() { local APP=$1; verify_app_name $APP - case "$(is_ssl_enabled $APP)" in - app) - SSL_PATH="$DOKKU_ROOT/$APP/tls" - ;; - - global) - SSL_PATH="$DOKKU_ROOT/tls" - ;; - esac + local SSL_PATH="$DOKKU_ROOT/$APP/tls" - SSL_HOSTNAME=$(openssl x509 -in $SSL_PATH/server.crt -noout -subject | tr '/' '\n' | grep CN= | cut -c4-) - SSL_HOSTNAME_ALT=$(openssl x509 -in $SSL_PATH/server.crt -noout -text | grep --after-context=1 '509v3 Subject Alternative Name:' | tail -n 1 | sed -e "s/[[:space:]]*DNS://g" | tr ',' '\n' || true) + local SSL_HOSTNAME=$(openssl x509 -in $SSL_PATH/server.crt -noout -subject | tr '/' '\n' | grep CN= | cut -c4-) + local SSL_HOSTNAME_ALT=$(openssl x509 -in $SSL_PATH/server.crt -noout -text | grep --after-context=1 '509v3 Subject Alternative Name:' | tail -n 1 | sed -e "s/[[:space:]]*DNS://g" | tr ',' '\n' || true) if [[ -n "$SSL_HOSTNAME_ALT" ]]; then - SSL_HOSTNAMES="${SSL_HOSTNAME}\n${SSL_HOSTNAME_ALT}" + local SSL_HOSTNAMES="${SSL_HOSTNAME}\n${SSL_HOSTNAME_ALT}" else - SSL_HOSTNAMES=$SSL_HOSTNAME + local SSL_HOSTNAMES=$SSL_HOSTNAME fi echo -e $SSL_HOSTNAMES return 0 diff --git a/plugins/checks/check-deploy b/plugins/checks/check-deploy index f5513bf046d..ab37ef50049 100755 --- a/plugins/checks/check-deploy +++ b/plugins/checks/check-deploy @@ -36,6 +36,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/checks/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" APP="$1"; DOKKU_APP_CONTAINER_ID="$2"; DOKKU_APP_CONTAINER_TYPE="$3"; DOKKU_APP_LISTEN_PORT="$4"; DOKKU_APP_LISTEN_IP="$5" @@ -55,6 +56,11 @@ fi eval "$(config_export global)" eval "$(config_export app $APP)" +if [[ "$(is_app_checks_enabled $APP)" == "false" ]];then + dokku_log_info2_quiet "Zero downtime checks for app ($APP) have been disabled. moving on..." + exit 0 +fi + # Wait this many seconds (default 5) for server to start before running checks. WAIT="${DOKKU_CHECKS_WAIT:-5}" # Wait this many seconds (default 30) for each response. @@ -80,11 +86,6 @@ cleanup() { trap cleanup EXIT if [[ ! -s "${TMPDIR}/CHECKS" ]] || [[ "$DOKKU_APP_CONTAINER_TYPE" != "web" ]]; then - if [[ "$DOKKU_SKIP_DEFAULT_CHECKS" = "true" ]]; then - dokku_log_verbose "Skipping default check..." - exit 0 - fi - # We allow custom check for web instances only if [[ "$DOKKU_APP_CONTAINER_TYPE" == "web" ]]; then dokku_log_verbose "For more efficient zero downtime deployments, create a file CHECKS." diff --git a/plugins/checks/commands b/plugins/checks/commands new file mode 100755 index 00000000000..8f14188a8f5 --- /dev/null +++ b/plugins/checks/commands @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +[[ " checks checks:enable checks:disable help ps:help " == *" $1 "* ]] || exit $DOKKU_NOT_IMPLEMENTED_EXIT +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/checks/functions" + +checks_main() { + local desc="displays app zero-downtime status" + local ALL_APPS=$(dokku_apps) + if [[ -n "$1" ]]; then + local APP="$1" + fi + local APPS=${APP:="$ALL_APPS"} + + dokku_col_log_info1_quiet "App Name" "Zero-Downtime Status" + for app in $APPS; do + verify_app_name "$app" + dokku_col_log_msg "$app" "$(is_app_checks_enabled $app)" + done +} + +checks_enable() { + local desc="enable zero-downtime for app" + local APP="$1"; verify_app_name "$APP" + + if [[ "$(is_app_checks_enabled $APP)" == "false" ]]; then + dokku_log_info1 "Enabling zero downtime for app ($APP)" + [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 + config_set $CONFIG_SET_ARGS $APP DOKKU_CHECKS_ENABLED=1 + else + dokku_log_info1 "zero downtime is already enabled for app ($APP)" + fi +} + +checks_disable() { + local desc="disable zero-downtime for app" + local APP="$1"; verify_app_name "$APP" + + if [[ "$(is_app_checks_enabled $APP)" == "true" ]]; then + dokku_log_info1 "Disabling zero downtime for app ($APP)" + [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 + config_set $CONFIG_SET_ARGS $APP DOKKU_CHECKS_ENABLED=0 + else + dokku_log_info1 "zero downtime is already disable for app ($APP)" + fi +} + +case "$1" in + checks) + checks_main $2 + ;; + + checks:enable) + [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" + checks_enable $2 --no-restart + ;; + + checks:disable) + [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" + checks_disable $2 --no-restart + ;; + + help | ps:help) + cat<, Show zero-downtime status + checks:enable , Enable zero-downtime checks + checks:disable , Disable zero-downtime checks +EOF + ;; + + *) + exit $DOKKU_NOT_IMPLEMENTED_EXIT + ;; + +esac diff --git a/plugins/checks/functions b/plugins/checks/functions new file mode 100755 index 00000000000..5bc027c8fa4 --- /dev/null +++ b/plugins/checks/functions @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +is_app_checks_enabled() { + local desc="return app zero-downtime checks status" + local APP="$1"; verify_app_name "$APP" + local DOKKU_CHECKS_ENABLED=$(config_get $APP DOKKU_CHECKS_ENABLED) + + if [[ -z "$DOKKU_CHECKS_ENABLED" ]] || [[ "$DOKKU_CHECKS_ENABLED" == "1" ]];then + local status=true + else + local status=false + fi + + echo $status +} diff --git a/plugins/checks/install b/plugins/checks/install new file mode 100755 index 00000000000..3af04234051 --- /dev/null +++ b/plugins/checks/install @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +migrate_checks_vars() { + local APPS="$(dokku_apps)" + local GLOBAL_SKIP_ALL_CHECKS=$(dokku config:get --global DOKKU_SKIP_ALL_CHECKS || true) + local GLOBAL_SKIP_DEFAULT_CHECKS=$(dokku config:get --global DOKKU_SKIP_DEFAULT_CHECKS || true) + + local app + + dokku_log_info1 "Migrating zero downtime env variables. The following variables have been deprecated" + dokku_log_info2 "DOKKU_SKIP_ALL_CHECKS DOKKU_SKIP_DEFAULT_CHECKS" + dokku_log_info2 "Please use dokku checks:[disable|enable] to control zero downtime functionality" + + for app in $APPS; do + local APP_SKIP_ALL_CHECKS=$(dokku config:get $app DOKKU_SKIP_ALL_CHECKS || true) + local APP_SKIP_DEFAULT_CHECKS=$(dokku config:get $app DOKKU_SKIP_DEFAULT_CHECKS || true) + + if [[ "$APP_SKIP_ALL_CHECKS" == "true" ]] || [[ "$APP_SKIP_DEFAULT_CHECKS" == "true" ]] || \ + [[ "$GLOBAL_SKIP_ALL_CHECKS" == "true" ]] || [[ "$GLOBAL_SKIP_DEFAULT_CHECKS" == "true" ]]; then + dokku_log_info2 "" + dokku_log_info2 "zero downtime disabled for app ($app)" + config_set --no-restart $app DOKKU_CHECKS_ENABLED=0 + fi + if [[ -n "$APP_SKIP_ALL_CHECKS" ]] || [[ -n "$APP_SKIP_DEFAULT_CHECKS" ]]; then + config_unset --no-restart $app DOKKU_SKIP_ALL_CHECKS DOKKU_SKIP_DEFAULT_CHECKS + fi + dokku_log_info2 "Migration complete" + dokku_log_info2 "" + done + + if [[ -n "$GLOBAL_SKIP_ALL_CHECKS" ]] || [[ -n "$GLOBAL_SKIP_DEFAULT_CHECKS" ]]; then + dokku_log_info1 "Removing global zero downtime settings" + config_unset --global DOKKU_SKIP_ALL_CHECKS DOKKU_SKIP_DEFAULT_CHECKS + fi +} + +migrate_checks_vars "$@" diff --git a/plugins/checks/plugin.toml b/plugins/checks/plugin.toml index b03b5b7ba8f..380cb3c4ff2 100644 --- a/plugins/checks/plugin.toml +++ b/plugins/checks/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core checks plugin" -version = "0.4.0" +version = "0.5.0" [plugin.config] diff --git a/plugins/common/functions b/plugins/common/functions index b2bfa686a6b..d60fe425dd6 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -258,11 +258,6 @@ get_app_container_ids() { echo $DOKKU_CIDS } -get_dockerfile_exposed_port() { - local DOCKERFILE_PORT=$(grep "^EXPOSE \+[[:digit:]]\+\(\/tcp\)\? *$" $1 | head -1 | sed 's/EXPOSE \+\([[:digit:]]\+\)\(\/tcp\)\?.*/\1/' || true) - echo "$DOCKERFILE_PORT" -} - get_app_running_container_ids() { local APP=$1 verify_app_name $APP @@ -441,3 +436,166 @@ get_ipv6_regex() { # Fixes using a wildcard dns service such as xip.io which allows for *..xip.io echo "${RE_IPV6}\$" } + +get_global_vhost() { + local GLOBAL_VHOST_FILE="$DOKKU_ROOT/VHOST" + [[ -f "$GLOBAL_VHOST_FILE" ]] && local GLOBAL_VHOST=$(< "$GLOBAL_VHOST_FILE") + echo $GLOBAL_VHOST +} + +is_global_vhost_enabled() { + local desc="returns true if we have a valid global vhost set; otherwise returns false" + local GLOBAL_VHOST=$(get_global_vhost) + local GLOBAL_VHOST_ENABLED=true + local RE_IPV4="$(get_ipv4_regex)" + local RE_IPV6="$(get_ipv6_regex)" + + if [[ -z "$GLOBAL_VHOST" ]] || [[ "$GLOBAL_VHOST" =~ $RE_IPV4 ]] || [[ "$GLOBAL_VHOST" =~ $RE_IPV6 ]]; then + local GLOBAL_VHOST_ENABLED=false + fi + echo $GLOBAL_VHOST_ENABLED +} + +is_app_vhost_enabled() { + local desc="returns true or false if vhost support is enabled for a given application" + local APP=$1; verify_app_name $APP + + local NO_VHOST=$(config_get $APP NO_VHOST) + local APP_VHOST_ENABLED=true + + if [[ "$NO_VHOST" == "1" ]]; then + local APP_VHOST_ENABLED=false + fi + + echo $APP_VHOST_ENABLED +} + +disable_app_vhost() { + local desc="disable vhost support for given application" + local APP=$1; verify_app_name $APP + local APP_VHOST_FILE="$DOKKU_ROOT/$APP/VHOST" + local APP_URLS_FILE="$DOKKU_ROOT/$APP/URLS" + local DOKKU_NGINX_PORT=$(config_get $APP DOKKU_NGINX_PORT) + local DOKKU_NGINX_SSL_PORT=$(config_get $APP DOKKU_NGINX_SSL_PORT) + + if [[ -f "$APP_VHOST_FILE" ]]; then + dokku_log_info1 "VHOST support disabled, deleting $APP/VHOST" + rm "$APP_VHOST_FILE" + fi + if [[ -f "$APP_URLS_FILE" ]]; then + dokku_log_info1 "VHOST support disabled, deleting $APP/URLS" + rm "$APP_URLS_FILE" + fi + if [[ "$DOKKU_NGINX_PORT" == "80" ]]; then + config_unset --no-restart $APP DOKKU_NGINX_PORT + fi + if [[ "$DOKKU_NGINX_SSL_PORT" == "443" ]]; then + config_unset --no-restart $APP DOKKU_NGINX_SSL_PORT + fi + + [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 + config_set $CONFIG_SET_ARGS $APP NO_VHOST=1 +} + +enable_app_vhost() { + local desc="enable vhost support for given application" + local APP=$1; verify_app_name $APP + + config_unset --no-restart $APP DOKKU_NGINX_PORT DOKKU_NGINX_SSL_PORT + [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 + config_set $CONFIG_SET_ARGS $APP NO_VHOST=0 +} + +get_dockerfile_exposed_ports() { + local DOCKERFILE_PORTS=$(egrep "^EXPOSE " $1 | awk '{ print $2 }' | xargs) || true + echo "$DOCKERFILE_PORTS" +} + +get_app_raw_tcp_ports() { + local APP="$1"; verify_app_name "$APP" + local DOCKERFILE_PORTS="$(config_get $APP DOKKU_DOCKERFILE_PORTS)" + for p in $DOCKERFILE_PORTS; do + if [[ ! "$p" =~ .*udp.* ]]; then + p=${p//\/tcp} + raw_tcp_ports+="$p " + fi + done + raw_tcp_ports="$(echo $raw_tcp_ports| xargs)" + echo "$raw_tcp_ports" +} + +get_container_ports() { + local APP="$1"; verify_app_name "$APP" + local APP_CIDS="$(get_app_container_ids $APP)" + local cid + + for cid in $APP_CIDS; do + local container_ports="$(docker port $cid | awk '{ print $3 "->" $1}' | awk -F ":" '{ print $2 }')" + done + + echo $container_ports +} + +get_app_urls() { + local APP="$2"; verify_app_name "$APP" + local RAW_TCP_PORTS="$(get_app_raw_tcp_ports $APP)" + local URLS_FILE="$DOKKU_ROOT/$APP/URLS" + + if [[ -s "$URLS_FILE" ]]; then + local app_urls="$(egrep -v "^#" $URLS_FILE)" + if [[ -n "$RAW_TCP_PORTS" ]]; then + for url in ${app_urls[*]}; do + for p in $RAW_TCP_PORTS; do + local port_urls+="$url:$p " + done + done + local port_urls="$(echo $port_urls| xargs)" + fi + local URLS=${port_urls:=$app_urls} + case "$1" in + url) + echo "$URLS" | tr ' ' '\n' | head -n1 + ;; + urls) + echo "$URLS" | tr ' ' '\n' + ;; + esac + else + local SCHEME="http"; local SSL="$DOKKU_ROOT/$APP/tls" + local DOKKU_NGINX_PORT=$(config_get $APP DOKKU_NGINX_PORT || true) + local DOKKU_NGINX_SSL_PORT=$(config_get $APP DOKKU_NGINX_SSL_PORT || true) + + if [[ -e "$SSL/server.crt" && -e "$SSL/server.key" ]]; then + local SCHEME="https" + fi + + if [[ "$(is_app_proxy_enabled $APP)" == "false" ]]; then + if [[ -n "$RAW_TCP_PORTS" ]]; then + local APP_CONTAINER_PORTS="$(get_container_ports $APP)" + local app_port + for app_port in $APP_CONTAINER_PORTS; do + echo "$(< "$DOKKU_ROOT/HOSTNAME"):$app_port (container)" + done + else + shopt -s nullglob + for PORT_FILE in $DOKKU_ROOT/$APP/PORT.*; do + echo "$SCHEME://$(< "$DOKKU_ROOT/HOSTNAME"):$(< "$PORT_FILE") (container)" + done + shopt -u nullglob + fi + elif [[ -n "$DOKKU_NGINX_PORT" ]] || [[ -n "$DOKKU_NGINX_SSL_PORT" ]]; then + if [[ -n "$DOKKU_NGINX_PORT" ]];then + echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$DOKKU_NGINX_PORT ($(get_app_proxy_type $APP))" + fi + if [[ -n "$DOKKU_NGINX_SSL_PORT" ]]; then + echo "https://$(< "$DOKKU_ROOT/HOSTNAME"):$DOKKU_NGINX_SSL_PORT ($(get_app_proxy_type $APP)-ssl)" + fi + elif [[ -n "$RAW_TCP_PORTS" ]]; then + for p in $RAW_TCP_PORTS; do + echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$p" + done + else + echo "$SCHEME://$(< "$DOKKU_ROOT/VHOST")" + fi + fi +} diff --git a/plugins/common/plugin.toml b/plugins/common/plugin.toml index 6f55ffa7193..c0f04790526 100644 --- a/plugins/common/plugin.toml +++ b/plugins/common/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core common plugin" -version = "0.4.0" +version = "0.5.0" [plugin.config] diff --git a/plugins/config/functions b/plugins/config/functions index 2dbd4fb92f7..5fff3c8bdef 100644 --- a/plugins/config/functions +++ b/plugins/config/functions @@ -7,16 +7,17 @@ config_export() { local APP="$2" local ENV_FILE="$DOKKU_ROOT/$APP/ENV" - [[ $CONFIG_TYPE == "global" ]] && ENV_FILE="$DOKKU_ROOT/ENV" + [[ $CONFIG_TYPE == "global" ]] && local ENV_FILE="$DOKKU_ROOT/ENV" [[ ! -f $ENV_FILE ]] && return 0 [[ ! -s $ENV_FILE ]] && return 0 - VARS=$(grep -Eo "export ([a-zA-Z_][a-zA-Z0-9_]*=.*)" $ENV_FILE | cut -d" " -f2-) + local VARS=$(grep -Eo "export ([a-zA-Z_][a-zA-Z0-9_]*=.*)" $ENV_FILE | cut -d" " -f2-) echo "$VARS" | awk '{print "export " $0}' return 0 } config_parse_args() { + unset APP ENV_FILE DOKKU_CONFIG_TYPE DOKKU_CONFIG_RESTART case "$2" in --global) ENV_FILE="$DOKKU_ROOT/ENV" @@ -57,21 +58,21 @@ config_styled_hash () { local longest="" while read -r word; do - KEY=$(echo $word | cut -d"=" -f1) + local KEY=$(echo $word | cut -d"=" -f1) if [[ ${#KEY} -gt ${#longest} ]]; then - longest=$KEY + local longest=$KEY fi done <<< "$vars" while read -r word; do - KEY=$(echo $word | cut -d"=" -f1) - VALUE=$(echo $word | cut -d"=" -f2- | sed -e "s/^'//" -e "s/'$//" -e "s/\$$//g") + local KEY=$(echo $word | cut -d"=" -f1) + local VALUE=$(echo $word | cut -d"=" -f2- | sed -e "s/^'//" -e "s/'$//" -e "s/\$$//g") - num_zeros=$((${#longest} - ${#KEY})) - zeros=" " + local num_zeros=$((${#longest} - ${#KEY})) + local zeros=" " while [[ $num_zeros -gt 0 ]]; do - zeros="$zeros " - num_zeros=$((num_zeros - 1)) + local zeros="$zeros " + local num_zeros=$((num_zeros - 1)) done echo "$prefix$KEY:$zeros$VALUE" done <<< "$vars" @@ -101,13 +102,14 @@ is_config_export() { config_all() { [[ "$1" == "config" ]] || set -- "config" "$@" config_parse_args "$@" + config_create "$ENV_FILE" is_config_export "$@" && config_export "$DOKKU_CONFIG_TYPE" "$APP" && return 0 - [[ "$APP" ]] && DOKKU_CONFIG_TYPE=$APP + [[ "$APP" ]] && local DOKKU_CONFIG_TYPE=$APP [[ ! -s $ENV_FILE ]] && dokku_log_fail "no config vars for $DOKKU_CONFIG_TYPE" - VARS=$(grep -Eo "export ([a-zA-Z_][a-zA-Z0-9_]*=.*)" $ENV_FILE | cut -d" " -f2-) + local VARS=$(grep -Eo "export ([a-zA-Z_][a-zA-Z0-9_]*=.*)" $ENV_FILE | cut -d" " -f2-) for var in "$@"; do if [[ "$var" == "--shell" ]]; then @@ -135,7 +137,7 @@ config_get() { return 0 fi - KEY="$3" + local KEY="$3" grep -Eo "export ([a-zA-Z_][a-zA-Z0-9_]*=.*)" $ENV_FILE | grep "^export $KEY=" | cut -d"=" -f2- | sed -e "s/^'//" -e "s/'$//" } @@ -153,9 +155,9 @@ config_set() { fi config_create "$ENV_FILE" - ENV_ADD="" - ENV_TEMP=$(cat "${ENV_FILE}") - RESTART_APP=false + local ENV_ADD="" + local ENV_TEMP=$(cat "${ENV_FILE}") + local RESTART_APP=false shift 2 for var; do @@ -168,20 +170,20 @@ config_set() { done for var; do - KEY=$(echo ${var} | cut -d"=" -f1) - VALUE=$(echo ${var} | cut -d"=" -f2-) + local KEY=$(echo ${var} | cut -d"=" -f1) + local VALUE=$(echo ${var} | cut -d"=" -f2-) if [[ $KEY =~ [a-zA-Z_][a-zA-Z0-9_]* ]]; then - RESTART_APP=true - ENV_TEMP=$(echo "${ENV_TEMP}" | sed "/^export $KEY=/ d") - ENV_TEMP="${ENV_TEMP} + local RESTART_APP=true + local ENV_TEMP=$(echo "${ENV_TEMP}" | sed "/^export $KEY=/ d") + local ENV_TEMP="${ENV_TEMP} export $KEY='$VALUE'" - ENV_ADD=$(echo -e "${ENV_ADD}" | sed "/^$KEY=/ d") - ENV_ADD="${ENV_ADD}$ + local ENV_ADD=$(echo -e "${ENV_ADD}" | sed "/^$KEY=/ d") + local ENV_ADD="${ENV_ADD}$ ${var}" fi done - ENV_ADD=$(echo "$ENV_ADD" | tail -n +2) #remove first empty line + local ENV_ADD=$(echo "$ENV_ADD" | tail -n +2) #remove first empty line if [[ $RESTART_APP ]]; then dokku_log_info1 "Setting config vars" @@ -209,15 +211,14 @@ config_unset() { fi config_create "$ENV_FILE" - ENV_TEMP=$(cat "${ENV_FILE}") - VARS="${*:3}" + local ENV_TEMP=$(cat "${ENV_FILE}") + local VARS="${*:3}" for var in $VARS; do dokku_log_info1 "Unsetting $var" - ENV_TEMP=$(echo "${ENV_TEMP}" | sed "/^export $var=/ d") + local ENV_TEMP=$(echo "${ENV_TEMP}" | sed "/^export $var=/ d") config_write "$ENV_TEMP" - [[ "$var" == "NO_VHOST" ]] && config_set --no-restart $APP NO_VHOST=0 && DOKKU_CONFIG_RESTART=true done plugn trigger post-config-update $APP "unset" "$@" diff --git a/plugins/domains/commands b/plugins/domains/commands index 5b8c2d0faff..d3cf60520d9 100755 --- a/plugins/domains/commands +++ b/plugins/domains/commands @@ -1,5 +1,5 @@ #!/usr/bin/env bash -[[ " domains domains:setup domains:add domains:clear domains:remove help domains:help " == *" $1 "* ]] || exit $DOKKU_NOT_IMPLEMENTED_EXIT +[[ " domains domains:setup domains:add domains:clear domains:remove domains:disable domains:enable domains:set-global help domains:help " == *" $1 "* ]] || exit $DOKKU_NOT_IMPLEMENTED_EXIT set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/domains/functions" @@ -7,122 +7,68 @@ source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" case "$1" in domains) - [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - verify_app_name "$2" - APP="$2" - - dokku domains:setup $APP - if [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then - dokku_log_info2_quiet "$APP Domain Names" - get_app_domains "$APP" - else - dokku_log_fail "No domain names set for $APP" - fi + domains_main "$2" ;; domains:setup) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - verify_app_name "$2" - APP="$2"; VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" - RE_IPV4="$(get_ipv4_regex)"; RE_IPV6="$(get_ipv6_regex)" - - if [[ ! -f $VHOST_PATH ]]; then - if [[ -f "$DOKKU_ROOT/VHOST" ]]; then - VHOST=$(< "$DOKKU_ROOT/VHOST") - else - VHOST=$(< "$DOKKU_ROOT/HOSTNAME") - fi - if [[ "$VHOST" =~ $RE_IPV4 ]] || [[ "$VHOST" =~ $RE_IPV6 ]]; then - dokku_log_info2 "unsupported vhost config found. disabling vhost support" - disable_app_vhost $APP --no-restart $APP - else - if [[ -f "$DOKKU_ROOT/VHOST" ]]; then - dokku_log_info1 "Creating new $VHOST_PATH..." - SUBDOMAIN=${APP/%\.${VHOST}/} - hostname=$(: | plugn trigger nginx-hostname $APP $SUBDOMAIN $VHOST) - if [[ ! -n $hostname ]]; then - if [[ "$APP" == *.* ]] && [[ "$SUBDOMAIN" == "$APP" ]]; then - hostname="${APP/\//-}" - else - hostname="${APP/\//-}.$VHOST" - fi - fi - - echo "$hostname" > $VHOST_PATH - fi - fi - fi + domains_setup "$2" ;; domains:add) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - verify_app_name "$2" - APP="$2" if [[ -z "${*:3}" ]]; then - echo "Usage: dokku $1 $APP DOMAIN [DOMAIN ...]" + echo "Usage: dokku $1 $2 DOMAIN [DOMAIN ...]" echo "Must specify DOMAIN." exit 1 fi - - if [[ $(egrep -w "^{$3}$" "$DOKKU_ROOT/$APP/VHOST" > /dev/null 2>&1; echo $?) -eq 0 ]]; then - echo "$3 is already defined for $APP" - exit 1 - fi - - shift 2 - dokku domains:setup $APP - for DOMAIN in "$@"; do - echo "$DOMAIN" >> "$DOKKU_ROOT/$APP/VHOST" - done - plugn trigger post-domains-update $APP "add" "$@" - for DOMAIN in "$@"; do - dokku_log_info1 "Added $DOMAIN to $APP" - done - + shift 1 + domains_add "$@" ;; domains:clear) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - verify_app_name "$2" - APP="$2" - - rm -f "$DOKKU_ROOT/$APP/VHOST" - dokku domains:setup $APP - plugn trigger post-domains-update $APP "clear" - dokku_log_info1 "Cleared domains in $APP" - + domains_clear "$2" ;; domains:remove) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - verify_app_name "$2" - APP="$2" if [[ -z "${*:3}" ]]; then - echo "Usage: dokku $1 $APP DOMAIN [DOMAIN ...]" + echo "Usage: dokku $1 $2 DOMAIN [DOMAIN ...]" echo "Must specify DOMAIN." exit 1 fi - shift 2 - dokku domains:setup $APP - for DOMAIN in "$@"; do - sed -i "/^$DOMAIN$/d" "$DOKKU_ROOT/$APP/VHOST" - done - plugn trigger post-domains-update $APP "remove" "$@" - for DOMAIN in "$@"; do - dokku_log_info1 "Removed $DOMAIN from $APP" - done + shift 1 + domains_remove "$@" + ;; + + domains:disable) + [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" + domains_disable "$2" + ;; + + domains:enable) + [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" + domains_enable "$2" + ;; + domains:set-global) + [[ -z $2 ]] && dokku_log_fail "Please specify a global domain name" + domains_set_global "$2" ;; help | domains:help) cat<, List custom domains for app - domains:add DOMAIN, Add a custom domain to app - domains:clear , Clear all custom domains for app - domains:remove DOMAIN, Remove a custom domain from app + domains [], List domains + domains:add DOMAIN, Add a domain to app + domains:clear , Clear all domains for app + domains:disable , Disable VHOST support + domains:enable , Enable VHOST support + domains:remove DOMAIN, Remove a domain from app + domains:set-global , Set global domain name EOF ;; diff --git a/plugins/domains/functions b/plugins/domains/functions index 4b5da179013..135d207ba52 100755 --- a/plugins/domains/functions +++ b/plugins/domains/functions @@ -3,9 +3,168 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" get_app_domains() { + local desc="return app domains" + verify_app_name $1 local APP=$1; local APP_VHOST_FILE="$DOKKU_ROOT/$APP/VHOST" - verify_app_name $APP - if [[ -f "$APP_VHOST_FILE" ]];then - cat "$APP_VHOST_FILE" + local GLOBAL_VHOST_PATH="$DOKKU_ROOT/VHOST" + local GLOBAL_HOSTNAME_PATH="$DOKKU_ROOT/HOSTNAME" + + if [[ "$(is_app_vhost_enabled $APP)" == "true" ]]; then + if [[ -f "$APP_VHOST_FILE" ]]; then + cat "$APP_VHOST_FILE" + elif [[ -f "$GLOBAL_VHOST_PATH" ]]; then + cat "$GLOBAL_VHOST_PATH" + elif [[ -f "$GLOBAL_HOSTNAME_PATH" ]]; then + cat "$GLOBAL_HOSTNAME_PATH" + fi + fi +} + +get_default_vhost() { + local desc="return default vhost" + verify_app_name "$1" + local APP="$1"; local RE_IPV4="$(get_ipv4_regex)"; local RE_IPV6="$(get_ipv6_regex)" + + if [[ "$(is_global_vhost_enabled)" == "true" ]]; then + local VHOST=$(get_global_vhost) + if ! ([[ "$VHOST" =~ $RE_IPV4 ]] || [[ "$VHOST" =~ $RE_IPV6 ]]); then + local SUBDOMAIN=${APP/%\.${VHOST}/} + local hostname=$(: | plugn trigger nginx-hostname $APP $SUBDOMAIN $VHOST) + if [[ ! -n $hostname ]]; then + if [[ "$APP" == *.* ]] && [[ "$SUBDOMAIN" == "$APP" ]]; then + local hostname="${APP/\//-}" + else + local hostname="${APP/\//-}.$VHOST" + fi + fi + local DEFAULT_VHOST="$hostname" + fi + fi + echo "$DEFAULT_VHOST" +} + +domains_setup() { + local desc="setup domains to default state" + verify_app_name "$1" + local APP="$1"; local APP_VHOST_PATH="$DOKKU_ROOT/$APP/VHOST"; local GLOBAL_VHOST_PATH="$DOKKU_ROOT/VHOST" + local RE_IPV4="$(get_ipv4_regex)"; local RE_IPV6="$(get_ipv6_regex)" + local DEFAULT_VHOST="$(get_default_vhost $APP)" + + if [[ ! -f $APP_VHOST_PATH ]]; then + if [[ -n "$DEFAULT_VHOST" ]]; then + dokku_log_info1 "Creating new $APP_VHOST_PATH..." + echo "$DEFAULT_VHOST" > $APP_VHOST_PATH + else + dokku_log_info2 "no global VHOST set. disabling vhost support" + disable_app_vhost $APP --no-restart + fi + fi +} + +domains_main() { + local desc="print app domains" + local APP="$1" + + if [[ "$(is_global_vhost_enabled)" == "true" ]]; then + dokku_log_info2_quiet "Global Domain Name" + get_global_vhost + fi + if [[ -n "$APP" ]]; then + verify_app_name $APP + if [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then + dokku_log_info2_quiet "$APP Domain Names" + get_app_domains "$APP" + else + dokku_log_fail "No domain names set for $APP" + fi + fi +} + +domains_add() { + local desc="add list of domains to app" + verify_app_name "$1" + local APP="$1"; local APP_VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" + + if [[ $(egrep -w "^${2}$" "$APP_VHOST_PATH" > /dev/null 2>&1; echo $?) -eq 0 ]]; then + dokku_log_fail "$2 is already defined for $APP" + exit 1 + fi + shift 1 + + for DOMAIN in "$@"; do + echo "$DOMAIN" >> "$APP_VHOST_PATH" + dokku_log_info1 "Added $DOMAIN to $APP" + done + + if [[ "$(is_app_vhost_enabled $APP)" == "false" ]];then + domains_enable "$APP" --no-restart + fi + + plugn trigger post-domains-update $APP "add" "$@" +} + +domains_clear() { + local desc="clear all app domains" + verify_app_name "$1" + local APP="$1"; local APP_VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" + + rm -f "$APP_VHOST_PATH" + domains_setup "$APP" + plugn trigger post-domains-update $APP "clear" + dokku_log_info1 "Cleared domains in $APP" +} + +domains_remove() { + local desc="remove list of app domains" + verify_app_name "$1" + local APP="$1"; local APP_VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" + local DEFAULT_VHOST="$(get_default_vhost $APP)" + shift 1 + + for DOMAIN in "$@"; do + sed -i "/^$DOMAIN$/d" "$DOKKU_ROOT/$APP/VHOST" + dokku_log_info1 "Removed $DOMAIN from $APP" + done + plugn trigger post-domains-update $APP "remove" "$@" +} + +domains_disable() { + local desc="disable domains/VHOST support" + verify_app_name "$1" + local APP="$1"; local APP_VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" + + if [[ "$(is_app_vhost_enabled $APP)" == "true" ]];then + disable_app_vhost "$APP" + else + dokku_log_info1 "domains (VHOST) support is already disabled for app ($APP)" + fi +} + +domains_enable() { + local desc="enable domains/VHOST support" + verify_app_name "$1" + local APP="$1"; local APP_VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" + local DEFAULT_VHOST="$(get_default_vhost $APP)" + + if [[ "$(is_app_vhost_enabled $APP)" == "false" ]];then + if [[ -n "$DEFAULT_VHOST" ]]; then + echo "$DEFAULT_VHOST" > "$APP_VHOST_PATH" + fi + [[ "$2" == "--no-restart" ]] && local ENABLE_APP_VHOST_ARGS=$2 + enable_app_vhost "$APP" "$ENABLE_APP_VHOST_ARGS" + else + dokku_log_info1 "domains (VHOST) support is already enabled for app ($APP)" + fi +} + +domains_set_global() { + local desc="set global domain name" + local NEW_GLOBAL_VHOST="$1"; local GLOBAL_VHOST_PATH="$DOKKU_ROOT/VHOST" + + if [[ -n "$NEW_GLOBAL_VHOST" ]]; then + echo "$NEW_GLOBAL_VHOST" > $GLOBAL_VHOST_PATH + dokku_log_info1 "Set global domain to $NEW_GLOBAL_VHOST" + else + dokku_log_fail "New global domain name must not be blank" fi } diff --git a/plugins/domains/plugin.toml b/plugins/domains/plugin.toml index 61195367e58..9d17dbb95b5 100644 --- a/plugins/domains/plugin.toml +++ b/plugins/domains/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core domains plugin" -version = "0.4.0" +version = "0.5.0" [plugin.config] diff --git a/plugins/nginx-vhosts/bind-external-ip b/plugins/nginx-vhosts/bind-external-ip deleted file mode 100755 index 324d82852c7..00000000000 --- a/plugins/nginx-vhosts/bind-external-ip +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" - -APP="$1" - -if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then - echo true # bind to external ip. VHOST is disabled -elif [[ "$(is_global_vhost_enabled $APP)" == "false" ]] && [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then - echo true # bind to external ip. no global vhost or global vhost is an ip -else - echo false -fi diff --git a/plugins/nginx-vhosts/commands b/plugins/nginx-vhosts/commands index a46dfcdd817..97aabc3416a 100755 --- a/plugins/nginx-vhosts/commands +++ b/plugins/nginx-vhosts/commands @@ -1,5 +1,5 @@ #!/usr/bin/env bash -[[ " nginx:build-config nginx:access-logs nginx:error-logs nginx:disable nginx:enable help nginx:help " == *" $1 "* ]] || exit $DOKKU_NOT_IMPLEMENTED_EXIT +[[ " nginx:build-config nginx:access-logs nginx:error-logs help nginx:help " == *" $1 "* ]] || exit $DOKKU_NOT_IMPLEMENTED_EXIT set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" @@ -7,58 +7,13 @@ source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" case "$1" in nginx:build-config) - APP="$2"; DOKKU_APP_LISTEN_PORT="$3"; DOKKU_APP_LISTEN_IP="$4" - - nginx_build_config $APP $DOKKU_APP_LISTEN_PORT $DOKKU_APP_LISTEN_IP - - ;; - - nginx:access-logs|nginx:error-logs) - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - NGINX_LOGS_TYPE=${1#nginx:} - NGINX_LOGS_TYPE=${NGINX_LOGS_TYPE%-logs} - NGINX_LOGS_PATH="/var/log/nginx/$APP-$NGINX_LOGS_TYPE.log" - - if [[ $3 == "-t" ]]; then - NGINX_LOGS_ARGS="-F" - else - NGINX_LOGS_ARGS="-n 20" - fi - - tail "$NGINX_LOGS_ARGS" "$NGINX_LOGS_PATH" - ;; - - nginx:disable) [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - if [[ "$(is_app_vhost_enabled $APP)" == "true" ]]; then - config_set --no-restart $APP DOKKU_NO_NGINX=1 - disable_app_vhost $APP - elif [[ "$(is_app_nginx_enabled $APP)" == "true" ]]; then - config_set --no-restart $APP DOKKU_NO_NGINX=1 - nginx_build_config $APP - else - dokku_log_info1 "nginx is already disable for app ($APP)" - fi + nginx_build_config $2 ;; - nginx:enable) + nginx:access-logs|nginx:error-logs) [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 - verify_app_name "$2" - APP="$2" - - if [[ "$(is_app_nginx_enabled $APP)" == "false" ]]; then - config_unset --no-restart $APP DOKKU_NO_NGINX NO_VHOST - unset DOKKU_NO_NGINX NO_VHOST - nginx_build_config $APP - else - dokku_log_info1 "nginx is already enabled for app ($APP)" - fi + nginx_logs "$@" ;; help | nginx:help) @@ -66,8 +21,6 @@ case "$1" in nginx:build-config , (Re)builds nginx config for given app nginx:access-logs [-t], Show the nginx access logs for an application (-t follows) nginx:error-logs [-t], Show the nginx error logs for an application (-t follows) - nginx:disable , Disable nginx for an application (forces container binding to external interface) - nginx:enable , Enable nginx for an application EOF ;; diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index b0804f1e82c..16472c09170 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -4,72 +4,13 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/certs/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/domains/functions" - -is_app_nginx_enabled() { - local APP="$1" - verify_app_name "$APP" - - local DOKKU_NO_NGINX=$(config_get $APP DOKKU_NO_NGINX) - if [[ -z "$DOKKU_NO_NGINX" ]]; then - echo true - else - echo false - fi -} - -is_global_vhost_enabled() { - local GLOBAL_VHOST_FILE="$DOKKU_ROOT/VHOST" - local GLOBAL_VHOST_ENABLED=true - [[ -f "$GLOBAL_VHOST_FILE" ]] && local GLOBAL_VHOST=$(< "$GLOBAL_VHOST_FILE") - - # Ensure the ip address continues to the end of the line - # Fixes using a wildcard dns service such as xip.io which allows for *..xip.io - local RE_IPV4="$(get_ipv4_regex)" - local RE_IPV6="$(get_ipv6_regex)" - - if [[ -z "$GLOBAL_VHOST" ]] || [[ "$GLOBAL_VHOST" =~ $RE_IPV4 ]] || [[ "$GLOBAL_VHOST" =~ $RE_IPV6 ]]; then - local GLOBAL_VHOST_ENABLED=false - fi - echo $GLOBAL_VHOST_ENABLED -} - -is_app_vhost_enabled() { - local APP=$1; local APP_VHOST_FILE="$DOKKU_ROOT/$APP/VHOST" - verify_app_name $APP - - local NO_VHOST=$(config_get $APP NO_VHOST) - local APP_VHOST_ENABLED=true - - if [[ "$NO_VHOST" == "1" ]]; then - local APP_VHOST_ENABLED=false - elif [[ -f "$DOKKU_ROOT/$APP/nginx.conf" ]] && [[ ! -f "$APP_VHOST_FILE" ]] && [[ "$NO_VHOST" != "0" ]]; then - local APP_VHOST_ENABLED=false - fi - - echo $APP_VHOST_ENABLED -} - -disable_app_vhost() { - local APP=$1; local APP_VHOST_FILE="$DOKKU_ROOT/$APP/VHOST" - verify_app_name $APP - - if [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then - dokku_log_info1 "VHOST support disabled, deleting $APP/VHOST" - rm "$DOKKU_ROOT/$APP/VHOST" - fi - if [[ -f "$DOKKU_ROOT/$APP/URLS" ]]; then - dokku_log_info1 "VHOST support disabled, deleting $APP/URLS" - rm "$DOKKU_ROOT/$APP/URLS" - fi - - [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 - config_set $CONFIG_SET_ARGS $APP NO_VHOST=1 -} +source "$PLUGIN_AVAILABLE_PATH/ps/functions" validate_nginx() { + local desc="validate entire nginx config" set +e sudo /usr/sbin/nginx -t > /dev/null 2>&1 - exit_code=$? + local exit_code=$? set -e if [[ "$exit_code" -ne "0" ]]; then sudo /usr/sbin/nginx -t @@ -78,6 +19,7 @@ validate_nginx() { } restart_nginx() { + local desc="restart nginx for given distros" case "$DOKKU_DISTRO" in debian) sudo /usr/sbin/invoke-rc.d nginx reload > /dev/null @@ -93,147 +35,165 @@ restart_nginx() { esac } -nginx_build_config() { - local APP="$1"; local DOKKU_APP_LISTEN_PORT="$2"; local DOKKU_APP_LISTEN_IP="$3" - verify_app_name "$APP" - VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" - URLS_PATH="$DOKKU_ROOT/$APP/URLS" - WILDCARD_SSL_PATH="$DOKKU_ROOT/tls" - APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" - APP_NGINX_TEMPLATE="$DOKKU_ROOT/$APP/nginx.conf.template" - APP_NGINX_SSL_TEMPLATE="$DOKKU_ROOT/$APP/nginx.ssl.conf.template" - - eval "$(config_export global)" - eval "$(config_export app $APP)" - - if [[ ! -n "$DOKKU_NO_NGINX" ]]; then - if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then - shopt -s nullglob - for DOKKU_APP_IP_FILE in $DOKKU_ROOT/$APP/IP.web.*; do - DOKKU_APP_PORT_FILE="${DOKKU_APP_IP_FILE//IP/PORT}" - DOKKU_APP_LISTENER_IP=$(< $DOKKU_APP_IP_FILE) - DOKKU_APP_LISTENER_PORT=$(< $DOKKU_APP_PORT_FILE) - - DOKKU_APP_LISTENERS+=" " - DOKKU_APP_LISTENERS+="$DOKKU_APP_LISTENER_IP:$DOKKU_APP_LISTENER_PORT" - DOKKU_APP_LISTENERS+=" " - done - shopt -u nullglob - fi - - DOKKU_APP_CIDS=($(get_app_container_ids $APP)) - for file in nginx.conf.template nginx.ssl.conf.template; do - docker cp "${DOKKU_APP_CIDS[0]}:/app/${file}" "$DOKKU_ROOT/$APP/" 2> /dev/null || true - done - - [[ -f "$APP_NGINX_TEMPLATE" ]] && NGINX_TEMPLATE="$APP_NGINX_TEMPLATE" && NGINX_CUSTOM_TEMPLATE="true" && dokku_log_info1 'Overriding default nginx.conf with detected nginx.conf.template' +nginx_logs() { + local desc="display app nginx logs" + local APP="$2"; verify_app_name "$APP" + local NGINX_LOGS_TYPE=${1#nginx:} + local NGINX_LOGS_TYPE=${NGINX_LOGS_TYPE%-logs} + local NGINX_LOGS_PATH="/var/log/nginx/$APP-$NGINX_LOGS_TYPE.log" - NGINX_CONF=$(mktemp -t "nginx.conf.XXXXXX") - SCHEME="http" + if [[ $3 == "-t" ]]; then + NGINX_LOGS_ARGS="-F" + else + NGINX_LOGS_ARGS="-n 20" + fi - # if the app has is not vhost enabled then, let's make sure we're cleaned up - [[ "$(is_app_vhost_enabled $APP)" == "false" ]] && disable_app_vhost $APP --no-restart + tail "$NGINX_LOGS_ARGS" "$NGINX_LOGS_PATH" +} - if [[ -z "$DOKKU_NGINX_PORT" ]]; then - if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then +configure_nginx_ports() { + local desc="configure nginx listening ports" + local APP=$1; verify_app_name "$APP" + local RAW_TCP_PORTS="$(get_app_raw_tcp_ports $APP)" + local DOKKU_NGINX_PORT=$(config_get $APP DOKKU_NGINX_PORT) + local DOKKU_NGINX_SSL_PORT=$(config_get $APP DOKKU_NGINX_SSL_PORT) + local IS_APP_VHOST_ENABLED="$(is_app_vhost_enabled $APP)" + + if [[ -z "$DOKKU_NGINX_PORT" ]]; then + if [[ -z "$RAW_TCP_PORTS" ]]; then + if [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then dokku_log_info1 "no nginx port set. setting to random open high port" local NGINX_PORT=$(get_available_port) else local NGINX_PORT=80 fi - config_set --no-restart $APP DOKKU_NGINX_PORT=${NGINX_PORT} - else - NGINX_PORT=$DOKKU_NGINX_PORT fi + if [[ -n "$NGINX_PORT" ]]; then + config_set --no-restart $APP DOKKU_NGINX_PORT="${NGINX_PORT}" + fi + fi - NONSSL_VHOSTS=$(get_app_domains $APP) - if [[ -n "$(is_ssl_enabled $APP)" ]]; then - SSL_HOSTNAME=$(get_ssl_hostnames $APP) - SSL_INUSE=true + if (is_ssl_enabled $APP) && [[ -z "$DOKKU_NGINX_SSL_PORT" ]] && [[ -z "$RAW_TCP_PORTS" ]]; then + if [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then + dokku_log_info1 "no nginx ssl port set. setting to random open high port" + local NGINX_SSL_PORT=$(get_available_port) + else + local NGINX_SSL_PORT=443 + fi + config_set --no-restart $APP DOKKU_NGINX_SSL_PORT=${NGINX_SSL_PORT} + fi +} - [[ -n "$SSL_HOSTNAME" ]] && SSL_HOSTNAME_REGEX=$(echo "$SSL_HOSTNAME" | xargs | sed 's|\.|\\.|g' | sed 's/\*/\[^\.\]\*/g' | sed 's/ /|/g') - if ! (egrep -q "^${SSL_HOSTNAME_REGEX}$" $VHOST_PATH &> /dev/null); then - dokku_log_info1 "No matching configured domains for $APP found in SSL certificate. Your app will show as insecure in a browser if accessed via SSL" - dokku_log_info1 "Please add appropriate domains via the dokku domains command" - [[ -n "$NONSSL_VHOSTS" ]] && dokku_log_info1 "Configured domains for app:" - for domain in $(echo $NONSSL_VHOSTS| xargs); do - dokku_log_info2 "$domain" - done - [[ -n "$SSL_HOSTNAME" ]] && dokku_log_info1 "Domains found in SSL certificate:" - for domain in $(echo $SSL_HOSTNAME | xargs); do - dokku_log_info2 "$domain" - done - fi +validate_ssl_domains() { + local desc="check configured domains against SSL cert contents and show warning if mismatched" + local APP=$1; verify_app_name "$APP" + local SSL_HOSTNAME=$(get_ssl_hostnames $APP) + local SSL_HOSTNAME_REGEX=$(echo "$SSL_HOSTNAME" | xargs | sed 's|\.|\\.|g' | sed 's/\*/\[^\.\]\*/g' | sed 's/ /|/g') + local domain + + if ! (egrep -q "^${SSL_HOSTNAME_REGEX}$" $VHOST_PATH &> /dev/null); then + dokku_log_info1 "No matching configured domains for $APP found in SSL certificate. Your app will show as insecure in a browser if accessed via SSL" + dokku_log_info1 "Please add appropriate domains via the dokku domains command" + [[ -n "$NONSSL_VHOSTS" ]] && dokku_log_info1 "Configured domains for app:" + for domain in $(echo $NONSSL_VHOSTS| xargs); do + dokku_log_info2 "$domain" + done + [[ -n "$SSL_HOSTNAME" ]] && dokku_log_info1 "Domains found in SSL certificate:" + for domain in $(echo $SSL_HOSTNAME | xargs); do + dokku_log_info2 "$domain" + done + fi +} - if [[ "$(is_ssl_enabled $APP)" == "app" ]]; then - SSL_DIRECTIVES=$(cat </dev/null || true +} - [[ -f "$APP_NGINX_SSL_TEMPLATE" ]] && NGINX_SSL_TEMPLATE="$APP_NGINX_SSL_TEMPLATE" && dokku_log_info1 'Overriding default nginx.ssl.conf with detected nginx.ssl.conf.template' - [[ -z "$NGINX_SSL_TEMPLATE" ]] && NGINX_SSL_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/nginx.ssl.conf.template" - if [[ "$(is_app_vhost_enabled $APP)" == "true" ]]; then - SSL_VHOSTS=$(egrep "^${SSL_HOSTNAME_REGEX}$" $VHOST_PATH || true) - else - SSL_VHOSTS=$(< $DOKKU_ROOT/HOSTNAME) - fi - NONSSL_VHOSTS=$(egrep -v "^${SSL_HOSTNAME_REGEX}$" $VHOST_PATH 2> /dev/null || true) +nginx_build_config() { + local desc="build nginx config to proxy app containers using sigil" + local APP="$1"; verify_app_name "$APP" + local DOKKU_APP_LISTEN_PORT="$2"; local DOKKU_APP_LISTEN_IP="$3" + local VHOST_PATH="$DOKKU_ROOT/$APP/VHOST"; local URLS_PATH="$DOKKU_ROOT/$APP/URLS" + local NGINX_TEMPLATE_NAME="nginx.conf.sigil" + local DEFAULT_NGINX_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/$NGINX_TEMPLATE_NAME" + local NGINX_TEMPLATE="$DEFAULT_NGINX_TEMPLATE"; local SCHEME=http + local NGINX_TEMPLATE_SOURCE="built-in"; local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" + local RAW_TCP_PORTS="$(get_app_raw_tcp_ports $APP)" + + local DOKKU_DISABLE_PROXY=$(config_get $APP DOKKU_DISABLE_PROXY) + local IS_APP_VHOST_ENABLED=$(is_app_vhost_enabled $APP) + + if [[ -z "$DOKKU_DISABLE_PROXY" ]]; then + if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then + shopt -s nullglob + local DOKKU_APP_IP_FILE + for DOKKU_APP_IP_FILE in $DOKKU_ROOT/$APP/IP.web.*; do + local DOKKU_APP_PORT_FILE="${DOKKU_APP_IP_FILE//IP/PORT}" + local DOKKU_APP_LISTENER_IP=$(< $DOKKU_APP_IP_FILE) + local DOKKU_APP_LISTENER_PORT=$(< $DOKKU_APP_PORT_FILE) - if [[ -z "$DOKKU_NGINX_SSL_PORT" ]]; then - if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then - dokku_log_info1 "no nginx ssl port set. setting to random open high port" - local NGINX_SSL_PORT=$(get_available_port) + if [[ -z "$RAW_TCP_PORTS" ]]; then + local DOKKU_APP_LISTENERS+=" $DOKKU_APP_LISTENER_IP:$DOKKU_APP_LISTENER_PORT " else - NGINX_SSL_PORT=443 + local DOKKU_APP_LISTENERS+=" $DOKKU_APP_LISTENER_IP " fi - config_set --no-restart $APP DOKKU_NGINX_SSL_PORT=${NGINX_SSL_PORT} - else - local NGINX_SSL_PORT=$DOKKU_NGINX_SSL_PORT - fi - - while read -r line; do - [[ -z "$line" ]] && continue - dokku_log_info1 "Configuring SSL for $line...(using $NGINX_SSL_TEMPLATE)" - SSL_SERVER_NAME=$line - NOSSL_SERVER_NAME=$line - eval "cat <<< \"$(< $NGINX_SSL_TEMPLATE)\" >> $NGINX_CONF" - done <<< "$SSL_VHOSTS" + local DOKKU_APP_LISTENERS=$(echo $DOKKU_APP_LISTENERS | xargs) + done + shopt -u nullglob + elif [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then + local PASSED_LISTEN_IP_PORT=true fi - if [[ -n "$DOKKU_SSL_TERMINATED" ]] && [[ -z "$NGINX_CUSTOM_TEMPLATE" ]]; then - NGINX_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/nginx.conf.ssl_terminated.template" - elif [[ -z "$NGINX_CUSTOM_TEMPLATE" ]]; then - NGINX_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/nginx.conf.template" - fi + local TMPDIR=$(mktemp -d /tmp/nginx_template.XXXXX) + local NGINX_CONF=$(mktemp -p ${TMPDIR} -t "nginx.conf.XXXXXX") + local CUSTOM_NGINX_TEMPLATE="$TMPDIR/$NGINX_TEMPLATE_NAME" + trap 'rm -rf "$NGINX_CONF $TMPDIR" > /dev/null' RETURN - if [[ -n "$NONSSL_VHOSTS" ]]; then - NOSSL_SERVER_NAME=$(echo $NONSSL_VHOSTS | tr '\n' ' ') - xargs -i echo "-----> Configuring {}...(using $NGINX_TEMPLATE)" <<< "$NONSSL_VHOSTS" - eval "cat <<< \"$(< $NGINX_TEMPLATE)\" >> $NGINX_CONF" - fi - if [[ "$(is_app_vhost_enabled $APP)" == "false" ]] || ([[ -z "$NONSSL_VHOSTS" ]] && [[ -z "$SSL_VHOSTS" ]]); then - eval "cat <<< \"$(< $NGINX_TEMPLATE)\" >> $NGINX_CONF" - sed --in-place -n -e '/^.*server_name.*$/!p' $NGINX_CONF + get_custom_nginx_template "$APP" "$CUSTOM_NGINX_TEMPLATE" + if [[ -f "$CUSTOM_NGINX_TEMPLATE" ]]; then + dokku_log_info1 'Overriding default nginx.conf with detected nginx.conf.sigil' + local NGINX_TEMPLATE="$CUSTOM_NGINX_TEMPLATE" + local NGINX_TEMPLATE_SOURCE="app-supplied" fi - if [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then - echo "upstream $APP { server $DOKKU_APP_LISTEN_IP:$DOKKU_APP_LISTEN_PORT; }" >> $NGINX_CONF - elif [[ -n "$DOKKU_APP_LISTENERS" ]]; then - echo "upstream $APP { " >> $NGINX_CONF - for listener in $DOKKU_APP_LISTENERS; do - echo " server $listener;" >> $NGINX_CONF - done - echo "}" >> $NGINX_CONF + # setup nginx listen ports + configure_nginx_ports "$APP" + local NGINX_PORT=$(config_get $APP DOKKU_NGINX_PORT) + local NGINX_SSL_PORT=$(config_get $APP DOKKU_NGINX_SSL_PORT) + + local NONSSL_VHOSTS=$(get_app_domains "$APP") + local NOSSL_SERVER_NAME=$(echo $NONSSL_VHOSTS | xargs) + if is_ssl_enabled "$APP"; then + local SSL_INUSE=true; local SCHEME=https + validate_ssl_domains "$APP" + local SSL_HOSTNAME=$(get_ssl_hostnames $APP) + local SSL_HOSTNAME_REGEX=$(echo "$SSL_HOSTNAME" | xargs | sed 's|\.|\\.|g' | sed 's/\*/\[^\.\]\*/g' | sed 's/ /|/g') + + if [[ "$IS_APP_VHOST_ENABLED" == "true" ]]; then + local SSL_VHOSTS=$(egrep "^${SSL_HOSTNAME_REGEX}$" $VHOST_PATH || true) + else + local SSL_VHOSTS=$(< $DOKKU_ROOT/HOSTNAME) + fi + local SSL_SERVER_NAME=$(echo $SSL_VHOSTS | xargs) fi + local SIGIL_PARAMS=(-f $NGINX_TEMPLATE APP="$APP" DOKKU_ROOT="$DOKKU_ROOT" + NOSSL_SERVER_NAME="$NOSSL_SERVER_NAME" + DOKKU_APP_LISTENERS="$DOKKU_APP_LISTENERS" + PASSED_LISTEN_IP_PORT="$PASSED_LISTEN_IP_PORT" + DOKKU_APP_LISTEN_PORT="$DOKKU_APP_LISTEN_PORT" DOKKU_APP_LISTEN_IP="$DOKKU_APP_LISTEN_IP" + APP_SSL_PATH="$APP_SSL_PATH" SSL_INUSE="$SSL_INUSE" SSL_SERVER_NAME="$SSL_SERVER_NAME" + NGINX_PORT="$NGINX_PORT" NGINX_SSL_PORT="$NGINX_SSL_PORT" RAW_TCP_PORTS="$RAW_TCP_PORTS") + + # execute sigil template processing + xargs -i echo "-----> Configuring {}...(using $NGINX_TEMPLATE_SOURCE template)" <<< "$(echo ${SSL_VHOSTS} ${NONSSL_VHOSTS} | tr ' ' '\n' | sort -u)" + # echo "sigil ${SIGIL_PARAMS[@]}" + sigil "${SIGIL_PARAMS[@]}" > $NGINX_CONF if is_deployed "$APP"; then dokku_log_info1 "Creating $SCHEME nginx.conf" @@ -251,13 +211,12 @@ EOF validate_nginx && restart_nginx fi - if [[ "$(is_app_vhost_enabled $APP)" == "true" ]]; then + if ([[ -n "$NONSSL_VHOSTS" ]] || [[ -n "$SSL_VHOSTS" ]]) && [[ "$IS_APP_VHOST_ENABLED" == "true" ]]; then echo "# THIS FILE IS GENERATED BY DOKKU - DO NOT EDIT, YOUR CHANGES WILL BE OVERWRITTEN" > $URLS_PATH - xargs -i echo "https://{}" <<< "${SSL_VHOSTS}" >> $URLS_PATH - xargs -i echo "http://{}" <<< "${NONSSL_VHOSTS}" >> $URLS_PATH + xargs -i echo "$SCHEME://{}" <<< "$(echo ${SSL_VHOSTS} ${NONSSL_VHOSTS} | tr ' ' '\n' | sort -u)" >> $URLS_PATH fi else - # note because this clause is long. if $DOKKU_NO_NGINX is set: + # note because this clause is long. if $DOKKU_DISABLE_PROXY is set: dokku_log_info1 "nginx support is disabled for app ($APP)." if [[ -f "$DOKKU_ROOT/$APP/nginx.conf" ]]; then dokku_log_info1 "deleting nginx.conf" diff --git a/plugins/nginx-vhosts/plugin.toml b/plugins/nginx-vhosts/plugin.toml index 7356a63d829..1641116ab3e 100644 --- a/plugins/nginx-vhosts/plugin.toml +++ b/plugins/nginx-vhosts/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core nginx-vhosts plugin" -version = "0.4.0" +version = "0.5.0" [plugin.config] diff --git a/plugins/nginx-vhosts/post-delete b/plugins/nginx-vhosts/post-delete index c4e7b2b9e04..5e237c09024 100755 --- a/plugins/nginx-vhosts/post-delete +++ b/plugins/nginx-vhosts/post-delete @@ -1,5 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/proxy/functions" case "$DOKKU_DISTRO" in debian) diff --git a/plugins/nginx-vhosts/post-deploy b/plugins/nginx-vhosts/post-deploy index 7bdf101053a..9bc8ae8d62a 100755 --- a/plugins/nginx-vhosts/post-deploy +++ b/plugins/nginx-vhosts/post-deploy @@ -1,15 +1,23 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/domains/functions" source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" +source "$PLUGIN_AVAILABLE_PATH/proxy/functions" -APP="$1" -if [[ -f "$DOKKU_ROOT/$APP/IP.web.1" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT.web.1" ]]; then - if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then - dokku_log_info1 "VHOST support disabled. Skipping domains setup" - elif [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then - dokku domains:setup $APP +nginx_post_deploy() { + local APP="$1" + if [[ -f "$DOKKU_ROOT/$APP/IP.web.1" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT.web.1" ]]; then + if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then + dokku_log_info1 "VHOST support disabled. Skipping domains setup" + elif [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then + domains_setup $APP + fi + + nginx_build_config $APP fi +} - nginx_build_config $APP +if [[ "$(get_app_proxy_type $1)" == "nginx" ]]; then + nginx_post_deploy "$@" fi diff --git a/plugins/nginx-vhosts/post-domains-update b/plugins/nginx-vhosts/post-domains-update index b62f24ac588..ee1a4ab3064 100755 --- a/plugins/nginx-vhosts/post-domains-update +++ b/plugins/nginx-vhosts/post-domains-update @@ -2,7 +2,8 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" +source "$PLUGIN_AVAILABLE_PATH/proxy/functions" -APP="$1" - -nginx_build_config $APP +if [[ "$(get_app_proxy_type $1)" == "nginx" ]]; then + nginx_build_config $1 +fi diff --git a/plugins/nginx-vhosts/proxy-disable b/plugins/nginx-vhosts/proxy-disable new file mode 100755 index 00000000000..50485a9a663 --- /dev/null +++ b/plugins/nginx-vhosts/proxy-disable @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/proxy/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/ps/functions" + +nginx_disable() { + local desc="disable nginx proxy" + local APP="$1"; verify_app_name "$APP" + + if [[ "$(get_app_proxy_type $APP)" == "nginx" ]]; then + disable_app_vhost $APP --no-restart + ps_restart $APP + fi +} + +nginx_disable "$@" diff --git a/plugins/nginx-vhosts/proxy-enable b/plugins/nginx-vhosts/proxy-enable new file mode 100755 index 00000000000..b99804b42d8 --- /dev/null +++ b/plugins/nginx-vhosts/proxy-enable @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/proxy/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/ps/functions" + +nginx_enable() { + local desc="enable nginx proxy" + local APP="$1"; verify_app_name "$APP" + + if [[ "$(get_app_proxy_type $APP)" == "nginx" ]]; then + enable_app_vhost $APP --no-restart + ps_restart $APP + fi +} + +nginx_enable "$@" diff --git a/plugins/nginx-vhosts/templates/location.config b/plugins/nginx-vhosts/templates/location.config new file mode 100644 index 00000000000..83178d75869 --- /dev/null +++ b/plugins/nginx-vhosts/templates/location.config @@ -0,0 +1,20 @@ + location / { + + gzip on; + gzip_min_length 1100; + gzip_buffers 4 32k; + gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; + gzip_vary on; + gzip_comp_level 6; + + proxy_pass http://{{ .APP }}; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Request-Start $msec; + } + include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; diff --git a/plugins/nginx-vhosts/templates/log.config b/plugins/nginx-vhosts/templates/log.config new file mode 100644 index 00000000000..435ec58b0f6 --- /dev/null +++ b/plugins/nginx-vhosts/templates/log.config @@ -0,0 +1,2 @@ + access_log /var/log/nginx/{{ .APP }}-access.log; + error_log /var/log/nginx/{{ .APP }}-error.log; diff --git a/plugins/nginx-vhosts/templates/nginx.conf.sigil b/plugins/nginx-vhosts/templates/nginx.conf.sigil new file mode 100644 index 00000000000..0c5b335a0b4 --- /dev/null +++ b/plugins/nginx-vhosts/templates/nginx.conf.sigil @@ -0,0 +1,10 @@ +{{ if .RAW_TCP_PORTS }} +{{ include "tcp.server.config" . }} +{{ else if .SSL_INUSE }} +{{ include "nossl.server.config" . }} +{{ include "ssl.server.config" . }} +{{ include "upstream.config" . }} +{{ else }} +{{ include "nossl.server.config" . }} +{{ include "upstream.config" . }} +{{ end }} diff --git a/plugins/nginx-vhosts/templates/nginx.conf.ssl_terminated.template b/plugins/nginx-vhosts/templates/nginx.conf.ssl_terminated.template deleted file mode 100644 index 642b0d65370..00000000000 --- a/plugins/nginx-vhosts/templates/nginx.conf.ssl_terminated.template +++ /dev/null @@ -1,19 +0,0 @@ -# Nginx configuration when running behind a load balancer that terminates SSL -# connections (e.g. AWS ELB) -server { - listen [::]:$NGINX_PORT; - listen $NGINX_PORT; - server_name $NOSSL_SERVER_NAME; - location / { - proxy_pass http://$APP; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host \$http_host; - proxy_set_header X-Forwarded-Proto \$http_x_forwarded_proto; - proxy_set_header X-Forwarded-For \$http_x_forwarded_for; - proxy_set_header X-Forwarded-Port \$http_x_forwarded_port; - proxy_set_header X-Request-Start \$msec; - } - include $DOKKU_ROOT/$APP/nginx.conf.d/*.conf; -} diff --git a/plugins/nginx-vhosts/templates/nginx.conf.template b/plugins/nginx-vhosts/templates/nginx.conf.template deleted file mode 100644 index 23a88e1dab9..00000000000 --- a/plugins/nginx-vhosts/templates/nginx.conf.template +++ /dev/null @@ -1,28 +0,0 @@ -server { - listen [::]:$NGINX_PORT; - listen $NGINX_PORT; - server_name $NOSSL_SERVER_NAME; - access_log /var/log/nginx/${APP}-access.log; - error_log /var/log/nginx/${APP}-error.log; - - location / { - - gzip on; - gzip_min_length 1100; - gzip_buffers 4 32k; - gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; - gzip_vary on; - gzip_comp_level 6; - - proxy_pass http://$APP; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host \$http_host; - proxy_set_header X-Forwarded-Proto \$scheme; - proxy_set_header X-Forwarded-For \$remote_addr; - proxy_set_header X-Forwarded-Port \$server_port; - proxy_set_header X-Request-Start \$msec; - } - include $DOKKU_ROOT/$APP/nginx.conf.d/*.conf; -} diff --git a/plugins/nginx-vhosts/templates/nginx.ssl.conf.template b/plugins/nginx-vhosts/templates/nginx.ssl.conf.template deleted file mode 100644 index ac49e6e9bcb..00000000000 --- a/plugins/nginx-vhosts/templates/nginx.ssl.conf.template +++ /dev/null @@ -1,40 +0,0 @@ -server { - listen [::]:$NGINX_PORT; - listen $NGINX_PORT; - server_name $NOSSL_SERVER_NAME; - access_log /var/log/nginx/${APP}-access.log; - error_log /var/log/nginx/${APP}-error.log; - return 301 https://\$host:$NGINX_SSL_PORT\$request_uri; -} - -server { - listen [::]:$NGINX_SSL_PORT ssl spdy; - listen $NGINX_SSL_PORT ssl spdy; - server_name $SSL_SERVER_NAME; - access_log /var/log/nginx/${APP}-access.log; - error_log /var/log/nginx/${APP}-error.log; -$SSL_DIRECTIVES - - keepalive_timeout 70; - add_header Alternate-Protocol $NGINX_SSL_PORT:npn-spdy/2; - location / { - - gzip on; - gzip_min_length 1100; - gzip_buffers 4 32k; - gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; - gzip_vary on; - gzip_comp_level 6; - - proxy_pass http://$APP; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host \$http_host; - proxy_set_header X-Forwarded-Proto \$scheme; - proxy_set_header X-Forwarded-For \$remote_addr; - proxy_set_header X-Forwarded-Port \$server_port; - proxy_set_header X-Request-Start \$msec; - } - include $DOKKU_ROOT/$APP/nginx.conf.d/*.conf; -} diff --git a/plugins/nginx-vhosts/templates/nossl.server.config b/plugins/nginx-vhosts/templates/nossl.server.config new file mode 100644 index 00000000000..3998232afb5 --- /dev/null +++ b/plugins/nginx-vhosts/templates/nossl.server.config @@ -0,0 +1,11 @@ +server { + listen [::]:{{ .NGINX_PORT }}; + listen {{ .NGINX_PORT }}; + {{ if .NOSSL_SERVER_NAME }}server_name {{ .NOSSL_SERVER_NAME }}; {{ end }} +{{ include "log.config" . }} +{{ if .SSL_INUSE }} + return 301 https://$host:{{ .NGINX_SSL_PORT }}$request_uri; +{{ else }} +{{ include "location.config" . }} +{{ end }} +} diff --git a/plugins/nginx-vhosts/templates/ssl.server.config b/plugins/nginx-vhosts/templates/ssl.server.config new file mode 100644 index 00000000000..490a13bb12a --- /dev/null +++ b/plugins/nginx-vhosts/templates/ssl.server.config @@ -0,0 +1,12 @@ +server { + listen [::]:{{ .NGINX_SSL_PORT }} ssl spdy; + listen {{ .NGINX_SSL_PORT }} ssl spdy; + {{ if .SSL_SERVER_NAME }}server_name {{ .SSL_SERVER_NAME }}; {{ end }} +{{ include "log.config" . }} + ssl_certificate {{ .APP_SSL_PATH }}/server.crt; + ssl_certificate_key {{ .APP_SSL_PATH }}/server.key; + + keepalive_timeout 70; + add_header Alternate-Protocol {{ .NGINX_SSL_PORT }}:npn-spdy/2; +{{ include "location.config" . }} +} diff --git a/plugins/nginx-vhosts/templates/tcp.server.config b/plugins/nginx-vhosts/templates/tcp.server.config new file mode 100644 index 00000000000..46cb9546d14 --- /dev/null +++ b/plugins/nginx-vhosts/templates/tcp.server.config @@ -0,0 +1,35 @@ +{{ range .RAW_TCP_PORTS | split " " }} +{{ $port := . }} +server { + listen [::]:{{ $port }}; + listen {{ $port }}; + {{ if $.NOSSL_SERVER_NAME }}server_name {{ $.NOSSL_SERVER_NAME }}; {{ end }} + access_log /var/log/nginx/{{ $.APP }}-access.log; + error_log /var/log/nginx/{{ $.APP }}-error.log; + + location / { + + gzip on; + gzip_min_length 1100; + gzip_buffers 4 32k; + gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; + gzip_vary on; + gzip_comp_level 6; + + proxy_pass http://{{ $.APP }}-{{ $port }}; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Request-Start $msec; + } +} +include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; +{{ if $.DOKKU_APP_LISTENERS }} +upstream {{ $.APP }}-{{ $port }} { +{{ range $.DOKKU_APP_LISTENERS | split " " }} server {{ . }}:{{ $port }};{{ end }} +} +{{ end }}{{ end }} diff --git a/plugins/nginx-vhosts/templates/upstream.config b/plugins/nginx-vhosts/templates/upstream.config new file mode 100644 index 00000000000..1f9a08e70de --- /dev/null +++ b/plugins/nginx-vhosts/templates/upstream.config @@ -0,0 +1,9 @@ +{{ if .DOKKU_APP_LISTENERS }} +upstream {{ .APP }} { +{{ range .DOKKU_APP_LISTENERS | split " " }} server {{ . }}; +{{ end }}} +{{ else if .PASSED_LISTEN_IP_PORT }} +upstream {{ .APP }} { + server {{ .DOKKU_APP_LISTEN_IP }}:{{ .DOKKU_APP_LISTEN_PORT }}; +} +{{ end }} diff --git a/plugins/proxy/commands b/plugins/proxy/commands new file mode 100755 index 00000000000..c49a573cb91 --- /dev/null +++ b/plugins/proxy/commands @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +[[ " proxy proxy:set proxy:enable proxy:disable help proxy:help " == *" $1 "* ]] || exit $DOKKU_NOT_IMPLEMENTED_EXIT +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/proxy/functions" + +case "$1" in + proxy) + proxy_main $2 + ;; + + proxy:set) + [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" + proxy_set $2 + ;; + + proxy:enable) + [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" + proxy_enable $2 --no-restart + ;; + + proxy:disable) + [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" + proxy_disable $2 --no-restart + ;; + + help | proxy:help) + cat<, Show proxy for app + proxy:enable , Enable proxy for app + proxy:disable , Disable proxy for app + proxy:set , NOT IMPLEMENTED YET!! +EOF + ;; + + *) + exit $DOKKU_NOT_IMPLEMENTED_EXIT + ;; + +esac diff --git a/plugins/proxy/functions b/plugins/proxy/functions new file mode 100755 index 00000000000..a8799b99474 --- /dev/null +++ b/plugins/proxy/functions @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +is_app_proxy_enabled() { + local desc="return true if proxy is enabled; otherwise return false" + local APP="$1"; verify_app_name "$APP" + local APP_PROXY_ENABLED=true + + local DOKKU_DISABLE_PROXY=$(config_get $APP DOKKU_DISABLE_PROXY) + if [[ -n "$DOKKU_DISABLE_PROXY" ]]; then + local APP_PROXY_ENABLED=false + fi + echo $APP_PROXY_ENABLED +} + +get_app_proxy_type() { + local desc="return app proxy type" + local APP="$1"; verify_app_name "$APP" + local APP_PROXY_TYPE="nginx" + + echo $APP_PROXY_TYPE +} + +proxy_main() { + local desc="displays app proxy implementation" + local ALL_APPS=$(dokku_apps) + if [[ -n "$1" ]]; then + local APP="$1" + fi + local APPS=${APP:="$ALL_APPS"} + + dokku_col_log_info1_quiet "App Name" "Proxy Type" + for app in $APPS; do + verify_app_name "$app" + dokku_col_log_msg "$app" "$(get_app_proxy_type $app)" + done +} + +proxy_set() { + local desc="enable proxy for app" + local APP="$1"; verify_app_name "$APP" + + dokku_log_info1 "proxy:set not implemented" +} + +proxy_enable() { + local desc="enable proxy for app" + local APP="$1"; verify_app_name "$APP" + + if [[ "$(is_app_proxy_enabled $APP)" == "false" ]]; then + dokku_log_info1 "Enabling proxy for app ($APP)" + [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 + config_unset $CONFIG_SET_ARGS $APP DOKKU_DISABLE_PROXY + plugn trigger proxy-enable "$APP" + else + dokku_log_info1 "proxy is already enabled for app ($APP)" + fi +} + +proxy_disable() { + local desc="disable proxy for app" + local APP="$1"; verify_app_name "$APP" + + if [[ "$(is_app_proxy_enabled $APP)" == "true" ]]; then + dokku_log_info1 "Disabling proxy for app ($APP)" + [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 + config_set $CONFIG_SET_ARGS $APP DOKKU_DISABLE_PROXY=1 + plugn trigger proxy-disable "$APP" + else + dokku_log_info1 "proxy is already disable for app ($APP)" + fi +} diff --git a/plugins/proxy/plugin.toml b/plugins/proxy/plugin.toml new file mode 100644 index 00000000000..2c803d5e3cb --- /dev/null +++ b/plugins/proxy/plugin.toml @@ -0,0 +1,4 @@ +[plugin] +description = "dokku core proxy plugin" +version = "0.5.0" +[plugin.config] diff --git a/plugins/ps/commands b/plugins/ps/commands index 9b6c6f0ca53..b353a8d8e64 100755 --- a/plugins/ps/commands +++ b/plugins/ps/commands @@ -7,72 +7,38 @@ source "$PLUGIN_AVAILABLE_PATH/ps/functions" case "$1" in ps) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - APP="$2"; verify_app_name "$APP"; DOKKU_APP_RUNNING_CONTAINER_IDS=$(get_app_running_container_ids $APP) - ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 - - for CID in $DOKKU_APP_RUNNING_CONTAINER_IDS; do - has_tty && DOKKU_RUN_OPTS="-i -t" - dokku_log_info1_quiet "running processes in container: $CID" - docker exec $DOKKU_RUN_OPTS $CID /bin/sh -c "ps auxwww" - done + ps_main $2 ;; ps:start) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - APP="$2"; IMAGE_TAG=$(get_running_image_tag $APP); - verify_app_name "$APP" - - ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 - - if ! (is_app_running $APP); then - release_and_deploy "$APP" "$IMAGE_TAG" - else - echo "App $APP already running" - fi + ps_start $2 ;; ps:stop) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - APP="$2"; DOKKU_APP_RUNNING_CONTAINER_IDS=$(get_app_running_container_ids $APP) - verify_app_name "$APP" - - ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 - - if [[ -n "$DOKKU_APP_RUNNING_CONTAINER_IDS" ]]; then - echo "Stopping $APP ..." - docker stop $DOKKU_APP_RUNNING_CONTAINER_IDS > /dev/null || true - plugn trigger post-stop $APP - else - echo "App $APP already stopped" - fi + ps_stop $2 ;; ps:rebuild) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - APP="$2"; verify_app_name "$APP" - - plugn trigger receive-app $APP + ps_rebuild $2 ;; ps:rebuildall) for app in $(dokku_apps); do - is_deployed $app && dokku ps:rebuild $app + is_deployed $app && ps_rebuild $app done ;; ps:restart) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - APP="$2"; IMAGE_TAG=$(get_running_image_tag $APP) - verify_app_name "$APP" - - ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 - - release_and_deploy "$APP" "$IMAGE_TAG" + ps_restart $2 ;; ps:restartall) for app in $(dokku_apps); do - dokku ps:restart $app + ps_restart $app done ;; @@ -81,34 +47,15 @@ case "$1" in DOKKU_APP_RESTORE=$(dokku config:get $app DOKKU_APP_RESTORE || true) if [[ $DOKKU_APP_RESTORE != 0 ]]; then echo "Restoring app $app ..." - dokku ps:start $app + ps_start $app fi done ;; ps:scale) [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - APP="$2"; IMAGE_TAG=$(get_running_image_tag $APP) - verify_app_name "$APP" - - DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" - shift 2 - - [[ ! -e $DOKKU_SCALE_FILE ]] && generate_scale_file "$APP" "$IMAGE_TAG" - if [[ -z "$@" ]];then - dokku_log_info1_quiet "Scaling for $APP" - dokku_col_log_info1_quiet "proctype" "qty" - dokku_col_log_info1_quiet "--------" "---" - while read -r line || [[ -n "$line" ]]; do - [[ -z "$line" ]] && continue - PROC_NAME=${line%%=*} - PROC_COUNT=${line#*=} - dokku_col_log_info1 "$PROC_NAME" "$PROC_COUNT" - done < "$DOKKU_SCALE_FILE" - else - set_scale "$APP" "$@" - release_and_deploy "$APP" "$IMAGE_TAG" - fi + shift 1 + ps_scale "$@" ;; help | ps:help) diff --git a/plugins/ps/functions b/plugins/ps/functions index 385ac3fe316..35d021da30b 100755 --- a/plugins/ps/functions +++ b/plugins/ps/functions @@ -79,3 +79,82 @@ set_scale() { fi done } + +ps_main() { + local APP="$1"; verify_app_name "$APP" + local DOKKU_APP_RUNNING_CONTAINER_IDS=$(get_app_running_container_ids $APP) + + ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 + + for CID in $DOKKU_APP_RUNNING_CONTAINER_IDS; do + has_tty && DOKKU_RUN_OPTS="-i -t" + dokku_log_info1_quiet "running processes in container: $CID" + docker exec $DOKKU_RUN_OPTS $CID /bin/sh -c "ps auxwww" + done +} + +ps_start() { + local APP="$1"; verify_app_name "$APP" + local IMAGE_TAG=$(get_running_image_tag $APP); + + ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 + + if ! (is_app_running $APP); then + release_and_deploy "$APP" "$IMAGE_TAG" + else + echo "App $APP already running" + fi +} + +ps_stop() { + local APP="$1"; verify_app_name "$APP" + local DOKKU_APP_RUNNING_CONTAINER_IDS=$(get_app_running_container_ids $APP) + + ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 + + if [[ -n "$DOKKU_APP_RUNNING_CONTAINER_IDS" ]]; then + echo "Stopping $APP ..." + docker stop $DOKKU_APP_RUNNING_CONTAINER_IDS > /dev/null || true + plugn trigger post-stop $APP + else + echo "App $APP already stopped" + fi +} + +ps_rebuild() { + local APP="$1"; verify_app_name "$APP" + + plugn trigger receive-app $APP +} + +ps_restart() { + local APP="$1"; verify_app_name "$APP" + local IMAGE_TAG=$(get_running_image_tag $APP) + + ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 + + release_and_deploy "$APP" "$IMAGE_TAG" +} + +ps_scale() { + local APP="$1"; verify_app_name "$APP" + local IMAGE_TAG=$(get_running_image_tag $APP) + local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + shift 1 + + [[ ! -e $DOKKU_SCALE_FILE ]] && generate_scale_file "$APP" "$IMAGE_TAG" + if [[ -z "$@" ]];then + dokku_log_info1_quiet "Scaling for $APP" + dokku_col_log_info1_quiet "proctype" "qty" + dokku_col_log_info1_quiet "--------" "---" + while read -r line || [[ -n "$line" ]]; do + [[ -z "$line" ]] && continue + PROC_NAME=${line%%=*} + PROC_COUNT=${line#*=} + dokku_col_log_info1 "$PROC_NAME" "$PROC_COUNT" + done < "$DOKKU_SCALE_FILE" + else + set_scale "$APP" "$@" + release_and_deploy "$APP" "$IMAGE_TAG" + fi +} diff --git a/tests/apps/dockerfile-noexpose/Dockerfile b/tests/apps/dockerfile-noexpose/Dockerfile index 2580af6d38b..86399657a53 100644 --- a/tests/apps/dockerfile-noexpose/Dockerfile +++ b/tests/apps/dockerfile-noexpose/Dockerfile @@ -1,10 +1,4 @@ -FROM ubuntu:trusty -ENV LC_ALL C -ENV DEBIAN_FRONTEND noninteractive -ENV DEBCONF_NONINTERACTIVE_SEEN true - -RUN apt-get install -y software-properties-common && add-apt-repository ppa:chris-lea/node.js && apt-get update -RUN apt-get install -y build-essential curl postgresql-client-9.3 nodejs git +FROM node:4 COPY . /app WORKDIR /app diff --git a/tests/apps/dockerfile/Dockerfile b/tests/apps/dockerfile/Dockerfile index cd47658a53a..fa82185d56b 100644 --- a/tests/apps/dockerfile/Dockerfile +++ b/tests/apps/dockerfile/Dockerfile @@ -1,14 +1,9 @@ -FROM ubuntu:trusty -ENV LC_ALL C -ENV DEBIAN_FRONTEND noninteractive -ENV DEBCONF_NONINTERACTIVE_SEEN true +FROM node:4 + EXPOSE 3001/udp EXPOSE 3000/tcp EXPOSE 3003 -RUN apt-get install -y software-properties-common && add-apt-repository ppa:chris-lea/node.js && apt-get update -RUN apt-get install -y build-essential curl postgresql-client-9.3 nodejs git - COPY . /app WORKDIR /app RUN npm install diff --git a/tests/apps/dockerfile/index.js b/tests/apps/dockerfile/index.js index 90221112fff..be6855ac323 100644 --- a/tests/apps/dockerfile/index.js +++ b/tests/apps/dockerfile/index.js @@ -11,3 +11,7 @@ app.get('/', function(request, response) { app.listen(app.get('port'), function() { console.log("Node app is running at localhost:" + app.get('port')) }) + +app.listen(3003, function() { + console.log("Node app is running at localhost:" + 3003) +}) diff --git a/tests/ci/parallel_runner.sh b/tests/ci/parallel_runner.sh index 95759ffcae0..c78b07f252d 100755 --- a/tests/ci/parallel_runner.sh +++ b/tests/ci/parallel_runner.sh @@ -23,6 +23,7 @@ setup_circle() { # sudo apt-get install -y -q "bash=$(apt-cache show bash | egrep "^Version: 4.3" | head -1 | awk -F: '{ print $2 }' | xargs)" bash --version docker version + lsb_release -a # setup .dokkurc sudo -E mkdir -p /home/dokku/.dokkurc sudo -E chown dokku:ubuntu /home/dokku/.dokkurc diff --git a/tests/unit/10_checks.bats b/tests/unit/10_checks.bats new file mode 100644 index 00000000000..3ccc7998825 --- /dev/null +++ b/tests/unit/10_checks.bats @@ -0,0 +1,45 @@ +#!/usr/bin/env bats + +load test_helper + +#!/usr/bin/env bats + +load test_helper + +setup() { + create_app +} + +teardown() { + destroy_app +} + +@test "(checks) checks" { + run bash -c "dokku checks $TEST_APP | grep -q true" + echo "output: "$output + echo "status: "$status + assert_success +} + +@test "(checks) checks:disable" { + dokku checks:disable $TEST_APP + assert_success + + run bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_ENABLED" + echo "output: "$output + echo "status: "$status + assert_output "0" +} + +@test "(checks) checks:enable" { + dokku checks:disable $TEST_APP + assert_success + + dokku checks:enable $TEST_APP + assert_success + + run bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_ENABLED" + echo "output: "$output + echo "status: "$status + assert_output "1" +} diff --git a/tests/unit/10_core_1.bats b/tests/unit/10_core_1.bats index 71ca38a794d..2f7ae644c8a 100644 --- a/tests/unit/10_core_1.bats +++ b/tests/unit/10_core_1.bats @@ -4,12 +4,12 @@ load test_helper setup() { create_app + DOCKERFILE="$BATS_TMPDIR/Dockerfile" } teardown() { rm -rf /home/dokku/$TEST_APP/tls /home/dokku/tls destroy_app - disable_tls_wildcard dokku config:unset --global DOKKU_RM_CONTAINER } @@ -120,3 +120,29 @@ build_nginx_config() { echo "status: "$status assert_success } + +@test "(core) port exposure (dockerfile raw port)" { + source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + cat< $DOCKERFILE +EXPOSE 3001/udp +EXPOSE 3003 +EXPOSE 3000/tcp +EOF + run get_dockerfile_exposed_ports $DOCKERFILE + echo "output: "$output + echo "status: "$status + assert_output "3001/udp 3003 3000/tcp" +} + +@test "(core) port exposure (dockerfile tcp port)" { + source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + cat< $DOCKERFILE +EXPOSE 3001/udp +EXPOSE 3000/tcp +EXPOSE 3003 +EOF + run get_dockerfile_exposed_ports $DOCKERFILE + echo "output: "$output + echo "status: "$status + assert_output "3001/udp 3000/tcp 3003" +} diff --git a/tests/unit/10_tar.bats b/tests/unit/10_tar.bats index 33f8753f88c..2a094238b89 100644 --- a/tests/unit/10_tar.bats +++ b/tests/unit/10_tar.bats @@ -23,7 +23,7 @@ deploy_app_tar() { @test "(tar) non-tarbomb deploy using tar:in" { deploy_app_tar nodejs-express --transform 's,^,prefix/,' - run bash -c "response=\"$(curl -s -S my-cool-guy-test-app.dokku.me)\"; echo \$response; test \"\$response\" == \"nodejs/express\"" + run bash -c "response=\"$(curl -s -S ${TEST_APP}.dokku.me)\"; echo \$response; test \"\$response\" == \"nodejs/express\"" echo "output: "$output echo "status: "$status assert_success @@ -32,7 +32,7 @@ deploy_app_tar() { @test "(tar) tarbomb deploy using tar:in" { deploy_app_tar nodejs-express - run bash -c "response=\"$(curl -s -S my-cool-guy-test-app.dokku.me)\"; echo \$response; test \"\$response\" == \"nodejs/express\"" + run bash -c "response=\"$(curl -s -S ${TEST_APP}.dokku.me)\"; echo \$response; test \"\$response\" == \"nodejs/express\"" echo "output: "$output echo "status: "$status assert_success diff --git a/tests/unit/20_client.bats b/tests/unit/20_client.bats index 786b2352453..8258758d208 100644 --- a/tests/unit/20_client.bats +++ b/tests/unit/20_client.bats @@ -70,6 +70,10 @@ teardown() { } @test "(client) domains" { + run bash -c "./contrib/dokku_client.sh domains:setup $TEST_APP" + echo "output: "$output + echo "status: "$status + assert_success run bash -c "./contrib/dokku_client.sh domains $TEST_APP | grep -q ${TEST_APP}.dokku.me" echo "output: "$output echo "status: "$status diff --git a/tests/unit/20_core_ports_1.bats b/tests/unit/20_core_ports_1.bats deleted file mode 100644 index 70cea5eb57a..00000000000 --- a/tests/unit/20_core_ports_1.bats +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bats - -load test_helper - -setup() { - [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -f "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" - [[ -f "$DOKKU_ROOT/HOSTNAME" ]] && cp -f "$DOKKU_ROOT/HOSTNAME" "$DOKKU_ROOT/HOSTNAME.bak" - DOCKERFILE="$BATS_TMPDIR/Dockerfile" -} - -teardown() { - destroy_app - [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" - [[ -f "$DOKKU_ROOT/HOSTNAME.bak" ]] && mv "$DOKKU_ROOT/HOSTNAME.bak" "$DOKKU_ROOT/HOSTNAME" -} - - -check_urls() { - local PATTERN="$1" - run bash -c "dokku --quiet urls $TEST_APP | egrep \"${1}\"" - echo "output: "$output - echo "status: "$status - assert_success -} - -@test "(core) port exposure (with global VHOST)" { - echo "dokku.me" > "$DOKKU_ROOT/VHOST" - deploy_app - CONTAINER_ID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) - run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep -q '[0-9]*'" - echo "output: "$output - echo "status: "$status - assert_failure - - check_urls http://${TEST_APP}.dokku.me -} - -@test "(core) port exposure (without global VHOST and real HOSTNAME)" { - rm "$DOKKU_ROOT/VHOST" - echo "${TEST_APP}.dokku.me" > "$DOKKU_ROOT/HOSTNAME" - deploy_app - CONTAINER_ID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) - run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep -q '[0-9]*'" - echo "output: "$output - echo "status: "$status - assert_success - - HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") - check_urls http://${HOSTNAME}:[0-9]+ -} - -@test "(core) port exposure (with NO_VHOST set)" { - deploy_app - dokku config:set $TEST_APP NO_VHOST=1 - CONTAINER_ID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) - run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep -q '[0-9]*'" - echo "output: "$output - echo "status: "$status - assert_success - - HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") - check_urls http://${HOSTNAME}:[0-9]+ -} - -@test "(core) port exposure (without global VHOST and IPv4 address as HOSTNAME)" { - rm "$DOKKU_ROOT/VHOST" - echo "127.0.0.1" > "$DOKKU_ROOT/HOSTNAME" - deploy_app - CONTAINER_ID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) - run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep -q '[0-9]*'" - echo "output: "$output - echo "status: "$status - assert_success - - HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") - check_urls http://${HOSTNAME}:[0-9]+ -} - -@test "(core) port exposure (without global VHOST and IPv6 address as HOSTNAME)" { - rm "$DOKKU_ROOT/VHOST" - echo "fda5:c7db:a520:bb6d::aabb:ccdd:eeff" > "$DOKKU_ROOT/HOSTNAME" - deploy_app - CONTAINER_ID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) - run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep -q '[0-9]*'" - echo "output: "$output - echo "status: "$status - assert_success - - HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") - check_urls http://${HOSTNAME}:[0-9]+ -} diff --git a/tests/unit/20_domains.bats b/tests/unit/20_domains.bats index 7538f4028fa..488ffd5d5a5 100644 --- a/tests/unit/20_domains.bats +++ b/tests/unit/20_domains.bats @@ -3,14 +3,19 @@ load test_helper setup() { + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + [[ -f "$DOKKU_ROOT/HOSTNAME" ]] && cp -fp "$DOKKU_ROOT/HOSTNAME" "$DOKKU_ROOT/HOSTNAME.bak" create_app } teardown() { destroy_app + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + [[ -f "$DOKKU_ROOT/HOSTNAME.bak" ]] && mv "$DOKKU_ROOT/HOSTNAME.bak" "$DOKKU_ROOT/HOSTNAME" && chown dokku:dokku "$DOKKU_ROOT/HOSTNAME" } @test "(domains) domains" { + dokku domains:setup $TEST_APP run bash -c "dokku domains $TEST_APP | grep ${TEST_APP}.dokku.me" echo "output: "$output echo "status: "$status @@ -35,6 +40,18 @@ teardown() { assert_success } +@test "(domains) domains:add (duplicate)" { + run dokku domains:add $TEST_APP test.app.dokku.me + echo "output: "$output + echo "status: "$status + assert_success + + run dokku domains:add $TEST_APP test.app.dokku.me + echo "output: "$output + echo "status: "$status + assert_failure +} + @test "(domains) domains:remove" { run dokku domains:add $TEST_APP test.app.dokku.me echo "output: "$output @@ -67,3 +84,15 @@ teardown() { echo "status: "$status assert_success } + +@test "(domains) domains:set-global" { + run dokku domains:set-global global.dokku.me + echo "output: "$output + echo "status: "$status + assert_success + + run bash -c "dokku domains | grep -q global.dokku.me" + echo "output: "$output + echo "status: "$status + assert_success +} diff --git a/tests/unit/20_nginx-vhosts_1.bats b/tests/unit/20_nginx-vhosts_1.bats new file mode 100644 index 00000000000..95d98b5536b --- /dev/null +++ b/tests/unit/20_nginx-vhosts_1.bats @@ -0,0 +1,124 @@ +#!/usr/bin/env bats + +load test_helper +source "$PLUGIN_CORE_AVAILABLE_PATH/config/functions" + +setup() { + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + [[ -f "$DOKKU_ROOT/HOSTNAME" ]] && cp -fp "$DOKKU_ROOT/HOSTNAME" "$DOKKU_ROOT/HOSTNAME.bak" +} + +teardown() { + destroy_app + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + [[ -f "$DOKKU_ROOT/HOSTNAME.bak" ]] && mv "$DOKKU_ROOT/HOSTNAME.bak" "$DOKKU_ROOT/HOSTNAME" && chown dokku:dokku "$DOKKU_ROOT/HOSTNAME" +} + +@test "(nginx-vhosts) nginx:build-config (domains:disable/enable)" { + deploy_app + dokku domains:disable $TEST_APP + + HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") + check_urls http://${HOSTNAME}:[0-9]+ + + NGINX_PORT="$(config_get $TEST_APP DOKKU_NGINX_PORT)" + assert_http_success http://${HOSTNAME}:${NGINX_PORT} + + dokku domains:enable $TEST_APP + check_urls http://${TEST_APP}.dokku.me + assert_http_success http://${TEST_APP}.dokku.me +} + +@test "(nginx-vhosts) nginx:build-config (domains:add pre deploy)" { + create_app + run dokku domains:add $TEST_APP www.test.app.dokku.me + echo "output: "$output + echo "status: "$status + assert_success + + deploy_app + sleep 5 # wait for nginx to reload + + check_urls http://www.test.app.dokku.me + assert_http_success http://www.test.app.dokku.me +} + +@test "(nginx-vhosts) nginx:build-config (with global VHOST)" { + echo "dokku.me" > "$DOKKU_ROOT/VHOST" + deploy_app + + check_urls http://${TEST_APP}.dokku.me + assert_http_success http://${TEST_APP}.dokku.me +} + +@test "(nginx-vhosts) nginx:build-config (without global VHOST but real HOSTNAME)" { + rm "$DOKKU_ROOT/VHOST" + echo "${TEST_APP}.dokku.me" > "$DOKKU_ROOT/HOSTNAME" + deploy_app + + HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") + check_urls http://${HOSTNAME}:[0-9]+ + + NGINX_PORT="$(config_get $TEST_APP DOKKU_NGINX_PORT)" + assert_http_success http://${HOSTNAME}:${NGINX_PORT} +} + +@test "(nginx-vhosts) nginx:build-config (without global VHOST and IPv4 address set as HOSTNAME)" { + rm "$DOKKU_ROOT/VHOST" + echo "127.0.0.1" > "$DOKKU_ROOT/HOSTNAME" + deploy_app + + HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") + check_urls http://${HOSTNAME}:[0-9]+ + + NGINX_PORT="$(config_get $TEST_APP DOKKU_NGINX_PORT)" + assert_http_success http://${HOSTNAME}:${NGINX_PORT} +} + +@test "(nginx-vhosts) nginx:build-config (without global VHOST and IPv6 address set as HOSTNAME)" { + rm "$DOKKU_ROOT/VHOST" + echo "fda5:c7db:a520:bb6d::aabb:ccdd:eeff" > "$DOKKU_ROOT/HOSTNAME" + deploy_app + + HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") + check_urls http://${HOSTNAME}:[0-9]+ +} + +@test "(nginx-vhosts) nginx:build-config (without global VHOST and domains:add pre deploy)" { + rm "$DOKKU_ROOT/VHOST" + create_app + add_domain "www.test.app.dokku.me" + deploy_app + assert_nonssl_domain "www.test.app.dokku.me" +} + +@test "(nginx-vhosts) nginx:build-config (without global VHOST and domains:add post deploy)" { + rm "$DOKKU_ROOT/VHOST" + deploy_app + add_domain "www.test.app.dokku.me" + check_urls http://www.test.app.dokku.me + assert_http_success http://www.test.app.dokku.me +} + +@test "(nginx-vhosts) nginx:build-config (xip.io style hostnames)" { + echo "127.0.0.1.xip.io" > "$DOKKU_ROOT/VHOST" + deploy_app + + check_urls http://${TEST_APP}.127.0.0.1.xip.io + assert_http_success http://${TEST_APP}.127.0.0.1.xip.io +} + +@test "(nginx-vhosts) nginx:build-config (dockerfile expose)" { + deploy_app dockerfile + + add_domain "www.test.app.dokku.me" + check_urls http://${TEST_APP}.dokku.me:3000 + check_urls http://${TEST_APP}.dokku.me:3003 + check_urls http://www.test.app.dokku.me:3000 + check_urls http://www.test.app.dokku.me:3003 + assert_http_success http://${TEST_APP}.dokku.me:3000 + assert_http_success http://${TEST_APP}.dokku.me:3003 + assert_http_success http://www.test.app.dokku.me:3000 + assert_http_success http://www.test.app.dokku.me:3003 + +} diff --git a/tests/unit/30_core_ports_2.bats b/tests/unit/30_core_ports_2.bats deleted file mode 100644 index 8883532df1d..00000000000 --- a/tests/unit/30_core_ports_2.bats +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env bats - -load test_helper - -setup() { - [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -f "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" - [[ -f "$DOKKU_ROOT/HOSTNAME" ]] && cp -f "$DOKKU_ROOT/HOSTNAME" "$DOKKU_ROOT/HOSTNAME.bak" - DOCKERFILE="$BATS_TMPDIR/Dockerfile" -} - -teardown() { - destroy_app - [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" - [[ -f "$DOKKU_ROOT/HOSTNAME.bak" ]] && mv "$DOKKU_ROOT/HOSTNAME.bak" "$DOKKU_ROOT/HOSTNAME" -} - - -check_urls() { - local PATTERN="$1" - run bash -c "dokku --quiet urls $TEST_APP | egrep \"${1}\"" - echo "output: "$output - echo "status: "$status - assert_success -} - -@test "(core) port exposure (pre-deploy domains:add)" { - create_app - run dokku domains:add $TEST_APP www.test.app.dokku.me - echo "output: "$output - echo "status: "$status - assert_success - - deploy_app - sleep 5 # wait for nginx to reload - - CONTAINER_ID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) - run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep -q '[0-9]*'" - echo "output: "$output - echo "status: "$status - assert_failure - - run bash -c "response=\"$(curl -s -S www.test.app.dokku.me)\"; echo \$response; test \"\$response\" == \"nodejs/express\"" - echo "output: "$output - echo "status: "$status - assert_success - - check_urls http://www.test.app.dokku.me -} - -@test "(core) port exposure (no global VHOST and domains:add post deploy)" { - rm "$DOKKU_ROOT/VHOST" - deploy_app - - run dokku domains:add $TEST_APP www.test.app.dokku.me - echo "output: "$output - echo "status: "$status - assert_success - - run dokku ps:restart $TEST_APP - echo "output: "$output - echo "status: "$status - assert_success - - CONTAINER_ID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) - run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep -q '[0-9]*'" - echo "output: "$output - echo "status: "$status - assert_failure - - run bash -c "response=\"$(curl -s -S www.test.app.dokku.me)\"; echo \$response; test \"\$response\" == \"nodejs/express\"" - echo "output: "$output - echo "status: "$status - assert_success - - check_urls http://www.test.app.dokku.me -} - -@test "(core) port exposure (xip.io style hostnames)" { - echo "127.0.0.1.xip.io" > "$DOKKU_ROOT/VHOST" - deploy_app - - run bash -c "response=\"$(curl -s -S my-cool-guy-test-app.127.0.0.1.xip.io)\"; echo \$response; test \"\$response\" == \"nodejs/express\"" - echo "output: "$output - echo "status: "$status - assert_success - - check_urls http://my-cool-guy-test-app.127.0.0.1.xip.io -} - -@test "(core) dockerfile port exposure" { - deploy_app dockerfile - run bash -c "grep -A1 upstream $DOKKU_ROOT/$TEST_APP/nginx.conf | grep -q 3000" - echo "output: "$output - echo "status: "$status - assert_success - - check_urls http://${TEST_APP}.dokku.me -} - -@test "(core) port exposure (dockerfile raw port)" { - source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - cat< $DOCKERFILE -EXPOSE 3001/udp -EXPOSE 3003 -EXPOSE 3000/tcp -EOF - run get_dockerfile_exposed_port $DOCKERFILE - echo "output: "$output - echo "status: "$status - assert_output 3003 -} - -@test "(core) port exposure (dockerfile tcp port)" { - source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - cat< $DOCKERFILE -EXPOSE 3001/udp -EXPOSE 3000/tcp -EXPOSE 3003 -EOF - run get_dockerfile_exposed_port $DOCKERFILE - echo "output: "$output - echo "status: "$status - assert_output 3000 -} diff --git a/tests/unit/40_core_2.bats b/tests/unit/40_core_2.bats index 3220325748a..3c0a2ee8fe3 100644 --- a/tests/unit/40_core_2.bats +++ b/tests/unit/40_core_2.bats @@ -9,7 +9,6 @@ setup() { teardown() { rm -rf /home/dokku/$TEST_APP/tls /home/dokku/tls destroy_app - disable_tls_wildcard } assert_urls() { @@ -17,6 +16,7 @@ assert_urls() { run dokku urls $TEST_APP echo "output: "$output echo "status: "$status + echo "urls:" $(tr ' ' '\n' <<< "${urls}") assert_output < <(tr ' ' '\n' <<< "${urls}") } @@ -50,27 +50,27 @@ build_nginx_config() { build_nginx_config assert_urls "http://${TEST_APP}.dokku.me" add_domain "test.dokku.me" - assert_urls "http://${TEST_APP}.dokku.me" "http://test.dokku.me" + assert_urls "http://test.dokku.me" "http://${TEST_APP}.dokku.me" } @test "(core) urls (app ssl)" { setup_test_tls assert_urls "https://dokku.me" build_nginx_config - assert_urls "http://${TEST_APP}.dokku.me" + assert_urls "https://${TEST_APP}.dokku.me" add_domain "test.dokku.me" - assert_urls "http://${TEST_APP}.dokku.me" "http://test.dokku.me" + assert_urls "https://test.dokku.me" "https://${TEST_APP}.dokku.me" } @test "(core) urls (wildcard ssl)" { - setup_test_tls_wildcard + setup_test_tls wildcard assert_urls "https://dokku.me" build_nginx_config assert_urls "https://${TEST_APP}.dokku.me" add_domain "test.dokku.me" - assert_urls "https://${TEST_APP}.dokku.me" "https://test.dokku.me" + assert_urls "https://test.dokku.me" "https://${TEST_APP}.dokku.me" add_domain "dokku.example.com" - assert_urls "https://${TEST_APP}.dokku.me" "https://test.dokku.me" "http://dokku.example.com" + assert_urls "https://dokku.example.com" "https://test.dokku.me" "https://${TEST_APP}.dokku.me" } @test "(core) git-remote (off-port)" { diff --git a/tests/unit/40_nginx-vhosts.bats b/tests/unit/40_nginx-vhosts_2.bats similarity index 55% rename from tests/unit/40_nginx-vhosts.bats rename to tests/unit/40_nginx-vhosts_2.bats index b9791950fec..5b91548584f 100644 --- a/tests/unit/40_nginx-vhosts.bats +++ b/tests/unit/40_nginx-vhosts_2.bats @@ -1,56 +1,18 @@ #!/usr/bin/env bats load test_helper +source "$PLUGIN_CORE_AVAILABLE_PATH/config/functions" setup() { - [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -f "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" - [[ -f "$DOKKU_ROOT/HOSTNAME" ]] && cp -f "$DOKKU_ROOT/HOSTNAME" "$DOKKU_ROOT/HOSTNAME.bak" + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + [[ -f "$DOKKU_ROOT/HOSTNAME" ]] && cp -fp "$DOKKU_ROOT/HOSTNAME" "$DOKKU_ROOT/HOSTNAME.bak" create_app } teardown() { destroy_app 0 $TEST_APP - [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" - [[ -f "$DOKKU_ROOT/HOSTNAME.bak" ]] && mv "$DOKKU_ROOT/HOSTNAME.bak" "$DOKKU_ROOT/HOSTNAME" - disable_tls_wildcard -} - -assert_ssl_domain() { - local domain=$1 - assert_app_domain "${domain}" - assert_http_redirect "http://${domain}" "https://${domain}:443/" - assert_http_success "https://${domain}" -} - -assert_nonssl_domain() { - local domain=$1 - assert_app_domain "${domain}" - assert_http_success "http://${domain}" -} - -assert_app_domain() { - local domain=$1 - run /bin/bash -c "dokku domains $TEST_APP | grep -xF ${domain}" - echo "output: "$output - echo "status: "$status - assert_output "${domain}" -} - -assert_http_redirect() { - local from=$1 - local to=$2 - run curl -kSso /dev/null -w "%{redirect_url}" "${from}" - echo "output: "$output - echo "status: "$status - assert_output "${to}" -} - -assert_http_success() { - local url=$1 - run curl -kSso /dev/null -w "%{http_code}" "${url}" - echo "output: "$output - echo "status: "$status - assert_output "200" + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + [[ -f "$DOKKU_ROOT/HOSTNAME.bak" ]] && mv "$DOKKU_ROOT/HOSTNAME.bak" "$DOKKU_ROOT/HOSTNAME" && chown dokku:dokku "$DOKKU_ROOT/HOSTNAME" } assert_access_log() { @@ -80,38 +42,26 @@ assert_error_log() { } @test "(nginx-vhosts) nginx:build-config (wildcard SSL)" { - setup_test_tls_wildcard - add_domain "wildcard1.dokku.me" - add_domain "wildcard2.dokku.me" - add_domain "www.test.dokku.me" - deploy_app - assert_ssl_domain "wildcard1.dokku.me" - assert_ssl_domain "wildcard2.dokku.me" - assert_nonssl_domain "www.test.dokku.me" -} - -@test "(nginx-vhosts) nginx:build-config (wildcard SSL & custom nginx template)" { - setup_test_tls_wildcard + setup_test_tls wildcard add_domain "wildcard1.dokku.me" add_domain "wildcard2.dokku.me" - custom_ssl_nginx_template deploy_app + cat /home/dokku/${TEST_APP}/nginx.conf assert_ssl_domain "wildcard1.dokku.me" assert_ssl_domain "wildcard2.dokku.me" - assert_http_success "customssltemplate.dokku.me" } @test "(nginx-vhosts) nginx:build-config (wildcard SSL & unrelated domain)" { destroy_app TEST_APP="${TEST_APP}.example.com" - setup_test_tls_wildcard + setup_test_tls wildcard deploy_app nodejs-express dokku@dokku.me:$TEST_APP run /bin/bash -c "egrep '*.dokku.me' $DOKKU_ROOT/${TEST_APP}/nginx.conf | wc -l" assert_output "0" } @test "(nginx-vhosts) nginx:build-config (with SSL and Multiple SANs)" { - setup_test_tls_with_sans + setup_test_tls sans add_domain "test.dokku.me" add_domain "www.test.dokku.me" add_domain "www.test.app.dokku.me" @@ -121,27 +71,36 @@ assert_error_log() { assert_ssl_domain "www.test.app.dokku.me" } -@test "(nginx-vhosts) nginx:build-config (custom nginx template)" { +@test "(nginx-vhosts) nginx:build-config (wildcard SSL & custom nginx template)" { + setup_test_tls wildcard + add_domain "wildcard1.dokku.me" + add_domain "wildcard2.dokku.me" + deploy_app nodejs-express dokku@dokku.me:$TEST_APP custom_ssl_nginx_template + assert_ssl_domain "wildcard1.dokku.me" + assert_ssl_domain "wildcard2.dokku.me" + assert_http_redirect "http://${CUSTOM_TEMPLATE_SSL_DOMAIN}" "https://${CUSTOM_TEMPLATE_SSL_DOMAIN}:443/" + assert_http_success "https://${CUSTOM_TEMPLATE_SSL_DOMAIN}" +} + +@test "(nginx-vhosts) nginx:build-config (custom nginx template - default path)" { add_domain "www.test.app.dokku.me" - custom_nginx_template - deploy_app + deploy_app nodejs-express dokku@dokku.me:$TEST_APP custom_nginx_template assert_nonssl_domain "www.test.app.dokku.me" assert_http_success "customtemplate.dokku.me" } -@test "(nginx-vhosts) nginx:build-config (no global VHOST and domains:add)" { - destroy_app - rm "$DOKKU_ROOT/VHOST" - create_app +@test "(nginx-vhosts) nginx:build-config (custom nginx template - custom path)" { add_domain "www.test.app.dokku.me" - deploy_app + dokku config:set --no-restart $TEST_APP NGINX_CUSTOM_TEMPLATE_PATH=/app/.dokku + echo "output: "$output + echo "status: "$status + deploy_app nodejs-express dokku@dokku.me:$TEST_APP custom_nginx_template .dokku/ assert_nonssl_domain "www.test.app.dokku.me" + assert_http_success "customtemplate.dokku.me" } -@test "(nginx-vhosts) nginx:build-config (validate_nginx)" { - deploy_app - echo "some lame nginx config" > "$DOKKU_ROOT/$TEST_APP/nginx.conf.template" - run /bin/bash -c "dokku nginx:build-config $TEST_APP" +@test "(nginx-vhosts) nginx:build-config (failed validate_nginx)" { + run deploy_app nodejs-express dokku@dokku.me:$TEST_APP bad_custom_nginx_template echo "output: "$output echo "status: "$status assert_failure diff --git a/tests/unit/40_proxy.bats b/tests/unit/40_proxy.bats new file mode 100644 index 00000000000..572d98a4e48 --- /dev/null +++ b/tests/unit/40_proxy.bats @@ -0,0 +1,64 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + [[ -f "$DOKKU_ROOT/HOSTNAME" ]] && cp -fp "$DOKKU_ROOT/HOSTNAME" "$DOKKU_ROOT/HOSTNAME.bak" + create_app +} + +teardown() { + destroy_app 0 $TEST_APP + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + [[ -f "$DOKKU_ROOT/HOSTNAME.bak" ]] && mv "$DOKKU_ROOT/HOSTNAME.bak" "$DOKKU_ROOT/HOSTNAME" && chown dokku:dokku "$DOKKU_ROOT/HOSTNAME" +} + +assert_nonssl_domain() { + local domain=$1 + assert_app_domain "${domain}" + assert_http_success "http://${domain}" +} + +assert_app_domain() { + local domain=$1 + run /bin/bash -c "dokku domains $TEST_APP | grep -xF ${domain}" + echo "output: "$output + echo "status: "$status + assert_output "${domain}" +} + +assert_external_port() { + local CID="$1"; local exit_status="$2" + local EXTERNAL_PORT_COUNT=$(docker port $CID | wc -l) + run /bin/bash -c "[[ $EXTERNAL_PORT_COUNT -gt 0 ]]" + if [[ "$exit_status" == "success" ]]; then + assert_success + else + assert_failure + fi +} + +@test "(proxy) proxy:enable/disable" { + deploy_app + assert_nonssl_domain "${TEST_APP}.dokku.me" + + run dokku proxy:disable $TEST_APP + echo "output: "$output + echo "status: "$status + assert_success + + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do + assert_external_port $(< $CID_FILE) success + done + + run dokku proxy:enable $TEST_APP + echo "output: "$output + echo "status: "$status + assert_success + assert_http_success "${TEST_APP}.dokku.me" + + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do + assert_external_port $(< $CID_FILE) failure + done +} diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index 2bee6796c21..1756830bdd9 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -8,7 +8,8 @@ PLUGIN_AVAILABLE_PATH=${PLUGIN_AVAILABLE_PATH:="$PLUGIN_PATH/available"} PLUGIN_ENABLED_PATH=${PLUGIN_ENABLED_PATH:="$PLUGIN_PATH/enabled"} PLUGIN_CORE_PATH=${PLUGIN_CORE_PATH:="$DOKKU_LIB_ROOT/core-plugins"} PLUGIN_CORE_AVAILABLE_PATH=${PLUGIN_CORE_AVAILABLE_PATH:="$PLUGIN_CORE_PATH/available"} -TEST_APP=my-cool-guy-test-app +TEST_APP=testsuiteapp +CUSTOM_TEMPLATE_SSL_DOMAIN=customssltemplate.dokku.me # test functions flunk() { @@ -21,6 +22,7 @@ flunk() { # ShellCheck doesn't know about $status from Bats # shellcheck disable=SC2154 +# shellcheck disable=SC2120 assert_success() { if [[ "$status" -ne 0 ]]; then flunk "command failed with exit status $status" @@ -115,8 +117,7 @@ create_app() { destroy_app() { local RC="$1"; local RC=${RC:=0} - local TEST_APP="$2"; local TEST_APP=${TEST_APP:=my-cool-guy-test-app} - echo $TEST_APP | dokku apps:destroy $TEST_APP + dokku --force apps:destroy $TEST_APP return $RC } @@ -124,12 +125,62 @@ add_domain() { dokku domains:add $TEST_APP $1 } +# shellcheck disable=SC2119 +check_urls() { + local PATTERN="$1" + run bash -c "dokku --quiet urls $TEST_APP | egrep \"${1}\"" + echo "output: "$output + echo "status: "$status + assert_success +} + +assert_http_success() { + local url=$1 + run curl -kSso /dev/null -w "%{http_code}" "${url}" + echo "output: "$output + echo "status: "$status + assert_output "200" +} + +assert_ssl_domain() { + local domain=$1 + assert_app_domain "${domain}" + assert_http_redirect "http://${domain}" "https://${domain}:443/" + assert_http_success "https://${domain}" +} + +assert_nonssl_domain() { + local domain=$1 + assert_app_domain "${domain}" + assert_http_success "http://${domain}" +} + +assert_app_domain() { + local domain=$1 + run /bin/bash -c "dokku domains $TEST_APP | grep -xF ${domain}" + echo "output: "$output + echo "status: "$status + assert_output "${domain}" +} + +assert_http_redirect() { + local from=$1 + local to=$2 + run curl -kSso /dev/null -w "%{redirect_url}" "${from}" + echo "output: "$output + echo "status: "$status + assert_output "${to}" +} + deploy_app() { - APP_TYPE="$1"; APP_TYPE=${APP_TYPE:="nodejs-express"} - GIT_REMOTE="$2"; GIT_REMOTE=${GIT_REMOTE:="dokku@dokku.me:$TEST_APP"} - TMP=$(mktemp -d -t "dokku.me.XXXXX") + local APP_TYPE="$1"; local APP_TYPE=${APP_TYPE:="nodejs-express"} + local GIT_REMOTE="$2"; local GIT_REMOTE=${GIT_REMOTE:="dokku@dokku.me:$TEST_APP"} + local CUSTOM_TEMPLATE="$3"; local TMP=$(mktemp -d -t "dokku.me.XXXXX") + local CUSTOM_PATH="$4" + rmdir $TMP && cp -r ./tests/apps/$APP_TYPE $TMP cd $TMP || exit 1 + [[ -n "$CUSTOM_TEMPLATE" ]] && $CUSTOM_TEMPLATE $TEST_APP $TMP/$CUSTOM_PATH git init git config user.email "robot@example.com" git config user.name "Test Robot" @@ -143,7 +194,7 @@ deploy_app() { } setup_client_repo() { - TMP=$(mktemp -d -t "dokku.me.XXXXX") + local TMP=$(mktemp -d -t "dokku.me.XXXXX") rmdir $TMP && cp -r ./tests/apps/nodejs-express $TMP cd $TMP || exit 1 git init @@ -156,93 +207,114 @@ setup_client_repo() { } setup_test_tls() { - TLS="/home/dokku/$TEST_APP/tls" + local TLS_TYPE="$1"; local TLS="/home/dokku/$TEST_APP/tls" mkdir -p $TLS - tar xf $BATS_TEST_DIRNAME/server_ssl.tar -C $TLS - sudo chown -R dokku:dokku $TLS -} -setup_test_tls_with_sans() { - TLS="/home/dokku/$TEST_APP/tls" - mkdir -p $TLS - tar xf $BATS_TEST_DIRNAME/server_ssl_sans.tar -C $TLS - sudo chown -R dokku:dokku $TLS -} - -setup_test_tls_wildcard() { - TLS="/home/dokku/tls" - mkdir -p $TLS - tar xf $BATS_TEST_DIRNAME/server_ssl_wildcard.tar -C $TLS - sudo chown -R dokku:dokku $TLS - sed -i -e "s:^# ssl_certificate $DOKKU_ROOT/tls/server.crt;:ssl_certificate $DOKKU_ROOT/tls/server.crt;:g" \ - -e "s:^# ssl_certificate_key $DOKKU_ROOT/tls/server.key;:ssl_certificate_key $DOKKU_ROOT/tls/server.key;:g" /etc/nginx/conf.d/dokku.conf - kill -HUP "$(< /var/run/nginx.pid)"; sleep 5 -} - -disable_tls_wildcard() { - TLS="/home/dokku/tls" - rm -rf $TLS - sed -i -e "s:^ssl_certificate $DOKKU_ROOT/tls/server.crt;:# ssl_certificate $DOKKU_ROOT/tls/server.crt;:g" \ - -e "s:^ssl_certificate_key $DOKKU_ROOT/tls/server.key;:# ssl_certificate_key $DOKKU_ROOT/tls/server.key;:g" /etc/nginx/conf.d/dokku.conf - kill -HUP "$(< /var/run/nginx.pid)"; sleep 5 + case "$TLS_TYPE" in + wildcard) + local TLS_ARCHIVE=server_ssl_wildcard.tar + ;; + sans) + local TLS_ARCHIVE=server_ssl_sans.tar + ;; + *) + local TLS_ARCHIVE=server_ssl.tar + ;; + esac + tar xf $BATS_TEST_DIRNAME/$TLS_ARCHIVE -C $TLS + sudo chown -R dokku:dokku ${TLS}/.. } custom_ssl_nginx_template() { - APP="$1" - [[ -z "$APP" ]] && APP="$TEST_APP" -cat< $DOKKU_ROOT/$APP/nginx.conf.template + local APP="$1"; local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + mkdir -p $APP_REPO_DIR + + echo "injecting custom_ssl_nginx_template -> $APP_REPO_DIR/nginx.conf.sigil" +cat< "$APP_REPO_DIR/nginx.conf.sigil" server { - listen [::]:\$NGINX_PORT; - listen \$NGINX_PORT; - server_name \$NOSSL_SERVER_NAME customssltemplate.dokku.me; - return 301 https://\\\$host:\$NGINX_SSL_PORT\\\$request_uri; + listen [::]:{{ .NGINX_PORT }}; + listen {{ .NGINX_PORT }}; + server_name {{ .NOSSL_SERVER_NAME }} $CUSTOM_TEMPLATE_SSL_DOMAIN; + return 301 https://\$host:{{ .NGINX_SSL_PORT }}\$request_uri; } server { - listen [::]:\$NGINX_SSL_PORT ssl spdy; - listen \$NGINX_SSL_PORT ssl spdy; - server_name \$SSL_SERVER_NAME customssltemplate.dokku.me; -\$SSL_DIRECTIVES + listen [::]:{{ .NGINX_SSL_PORT }} ssl spdy; + listen {{ .NGINX_SSL_PORT }} ssl spdy; + server_name \$SSL_SERVER_NAME $CUSTOM_TEMPLATE_SSL_DOMAIN; + ssl_certificate {{ .APP_SSL_PATH }}/server.crt; + ssl_certificate_key {{ .APP_SSL_PATH }}/server.key; keepalive_timeout 70; - add_header Alternate-Protocol \$NGINX_SSL_PORT:npn-spdy/2; + add_header Alternate-Protocol {{ .NGINX_SSL_PORT }}:npn-spdy/2; location / { - proxy_pass http://\$APP; + proxy_pass http://{{ .APP }}; proxy_http_version 1.1; - proxy_set_header Upgrade \\\$http_upgrade; + proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection "upgrade"; - proxy_set_header Host \\\$http_host; - proxy_set_header X-Forwarded-Proto \\\$scheme; - proxy_set_header X-Forwarded-For \\\$remote_addr; - proxy_set_header X-Forwarded-Port \\\$server_port; - proxy_set_header X-Request-Start \\\$msec; + proxy_set_header Host \$http_host; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_set_header X-Forwarded-For \$remote_addr; + proxy_set_header X-Forwarded-Port \$server_port; + proxy_set_header X-Request-Start \$msec; } - include \$DOKKU_ROOT/\$APP/nginx.conf.d/*.conf; + include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; } +{{ if .DOKKU_APP_LISTENERS }} +upstream {{ .APP }} { +{{ range .DOKKU_APP_LISTENERS | split " " }} server {{ . }}; +{{ end }}} +{{ else if .PASSED_LISTEN_IP_PORT }} +upstream {{ .APP }} { + server {{ .DOKKU_APP_LISTEN_IP }}:{{ .DOKKU_APP_LISTEN_PORT }}; +} +{{ end }} EOF } custom_nginx_template() { - APP="$1" - [[ -z "$APP" ]] && APP="$TEST_APP" -cat< $DOKKU_ROOT/$APP/nginx.conf.template + local APP="$1"; local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + mkdir -p $APP_REPO_DIR + + echo "injecting custom_nginx_template -> $APP_REPO_DIR/nginx.conf.sigil" +cat< "$APP_REPO_DIR/nginx.conf.sigil" server { - listen [::]:\$NGINX_PORT; - listen \$NGINX_PORT; - server_name \$NOSSL_SERVER_NAME customtemplate.dokku.me; + listen [::]:{{ .NGINX_PORT }}; + listen {{ .NGINX_PORT }}; + server_name {{ .NOSSL_SERVER_NAME }} customtemplate.dokku.me; location / { - proxy_pass http://\$APP; + proxy_pass http://{{ .APP }}; proxy_http_version 1.1; - proxy_set_header Upgrade \\\$http_upgrade; + proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection "upgrade"; - proxy_set_header Host \\\$http_host; - proxy_set_header X-Forwarded-Proto \\\$scheme; - proxy_set_header X-Forwarded-For \\\$remote_addr; - proxy_set_header X-Forwarded-Port \\\$server_port; - proxy_set_header X-Request-Start \\\$msec; + proxy_set_header Host \$http_host; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_set_header X-Forwarded-For \$remote_addr; + proxy_set_header X-Forwarded-Port \$server_port; + proxy_set_header X-Request-Start \$msec; } - include \$DOKKU_ROOT/\$APP/nginx.conf.d/*.conf; + include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; +} +{{ if .DOKKU_APP_LISTENERS }} +upstream {{ .APP }} { +{{ range .DOKKU_APP_LISTENERS | split " " }} server {{ . }}; +{{ end }}} +{{ else if .PASSED_LISTEN_IP_PORT }} +upstream {{ .APP }} { + server {{ .DOKKU_APP_LISTEN_IP }}:{{ .DOKKU_APP_LISTEN_PORT }}; } +{{ end }} +EOF +} + +bad_custom_nginx_template() { + local APP="$1"; local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "injecting bad_custom_nginx_template -> $APP_REPO_DIR/nginx.conf.sigil" +cat< "$APP_REPO_DIR/nginx.conf.sigil" +some lame nginx config EOF }